diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 000000000..32bf867c8 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,32 @@ +codecov: + notify: + require_ci_to_pass: yes + +coverage: + precision: 2 + round: down + range: "70...100" + + status: + project: yes + patch: yes + changes: no + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no + +comment: + layout: "header, diff" + behavior: default + require_changes: no + +ignore: + - "*Errors.go" # ignore error files + - "test/.*" + - "*_test.go" + - "examples/.*" diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..cd2083e25 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +* @adamdecaf +* @vxio diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..f251519bb --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,11 @@ + + +ACH Version: `` + +**What were you trying to do?** + +**What did you expect to see?** + +**What did you see?** + +**How can we reproduce the problem?** diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml new file mode 100644 index 000000000..d6be9e52e --- /dev/null +++ b/.github/workflows/codeql.yaml @@ -0,0 +1,27 @@ +name: CodeQL Analysis + +on: + push: + pull_request: + schedule: + - cron: '0 10 * * 0' + +jobs: + CodeQL-Build: + strategy: + fail-fast: false + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: go + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + env: + GOOS: js + GOARCH: wasm diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 000000000..becca787b --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,51 @@ +name: Go + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + name: Go Build + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - name: Set up Go 1.x + uses: actions/setup-go@v2 + with: + go-version: ^1.17 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Install make (Windows) + if: runner.os == 'Windows' + run: choco install -y make mingw + + - name: Check OpenAPI + if: runner.os == 'Linux' + run: make check-openapi + + - name: Generate OpenAPI + if: runner.os == 'Linux' + run: | + go get -u golang.org/x/oauth2 + make client + + - name: Check + run: make check + env: + GOLANGCI_LINTERS: gosec + + - name: Upload Code Coverage + if: runner.os == 'Linux' + run: bash <(curl -s https://codecov.io/bash) + + - name: Docker Build + if: runner.os == 'Linux' + run: make docker && make test-integration && make clean-integration diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..4a8e4765f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,183 @@ +name: Create Release + +on: + push: + tags: [ "v*.*.*" ] + +jobs: + testing: + name: Testing + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - name: Set up Go 1.x + uses: actions/setup-go@v2 + with: + go-version: ^1.17 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Check + run: make check + + create_release: + name: Create Release + needs: [testing] + runs-on: ubuntu-latest + steps: + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + prerelease: true + + - name: Output Release URL File + run: echo "${{ steps.create_release.outputs.upload_url }}" > release_url.txt + + - name: Save Release URL File for publish + uses: actions/upload-artifact@v1 + with: + name: release_url + path: release_url.txt + + publish: + name: Publish + needs: [testing, create_release] + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - name: Set up Go 1.x + uses: actions/setup-go@v2 + with: + go-version: ^1.14 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Load Release URL File from release job + uses: actions/download-artifact@v1 + with: + name: release_url + + - name: Distribute + run: make dist + + - name: Get Release File Name & Upload URL + id: get_release_info + shell: bash + run: | + value=`cat release_url/release_url.txt` + echo ::set-output name=upload_url::$value + env: + TAG_REF_NAME: ${{ github.ref }} + REPOSITORY_NAME: ${{ github.repository }} + + - name: Upload Linux Server Binary + if: runner.os == 'Linux' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_release_info.outputs.upload_url }} + asset_path: ./bin/ach-linux-amd64 + asset_name: ach-linux-amd64 + asset_content_type: application/octet-stream + + - name: Upload Linux achcli Binary + if: runner.os == 'Linux' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_release_info.outputs.upload_url }} + asset_path: ./bin/achcli-linux-amd64 + asset_name: achcli-linux-amd64 + asset_content_type: application/octet-stream + + - name: Upload macOS Server Binary + if: runner.os == 'macOS' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_release_info.outputs.upload_url }} + asset_path: ./bin/ach-darwin-amd64 + asset_name: ach-darwin-amd64 + asset_content_type: application/octet-stream + + - name: Upload macOS achcli Binary + if: runner.os == 'macOS' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_release_info.outputs.upload_url }} + asset_path: ./bin/achcli-darwin-amd64 + asset_name: achcli-darwin-amd64 + asset_content_type: application/octet-stream + + - name: Upload Windows Server Binary + if: runner.os == 'Windows' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_release_info.outputs.upload_url }} + asset_path: ./bin/ach.exe + asset_name: ach.exe + asset_content_type: application/octet-stream + + - name: Upload Windows achcli Binary + if: runner.os == 'Windows' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_release_info.outputs.upload_url }} + asset_path: ./bin/achcli.exe + asset_name: achcli.exe + asset_content_type: application/octet-stream + + docker: + name: Docker + needs: [testing, create_release] + runs-on: ubuntu-latest + steps: + - name: Set up Go 1.x + uses: actions/setup-go@v2 + with: + go-version: ^1.14 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Docker + run: make docker + + - name: Docker Push + run: |+ + echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin + make release-push + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + + - name: Quay.io Push + run: |+ + echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin quay.io + make quay-push + env: + DOCKER_USERNAME: ${{ secrets.QUAY_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.QUAY_PASSWORD }} diff --git a/.gitignore b/.gitignore index 44842dc21..2b26051a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ #### joe made this: http://goel.io/joe +.DS_Store + #####=== Go ===##### # Compiled Object files, Static and Dynamic libs (Shared Objects) @@ -30,6 +32,33 @@ _cgo_export.* _testmain.go +*.out *.exe *.test *.prof + +.vscode/launch.json + +# code coverage +coverage.html +cover.out +coverage.txt +misspell* +staticcheck* +/lint-project.sh +bin/ +tmp/ + +# fuzzing +test/fuzz-reader/crashes +test/fuzz-reader/suppressions +test/fuzz-reader/*.zip +test/fuzz-reader/corpus/*.tar.gz + +go-licenser*.tar.gz +go-licenser + +openapi-generator*.jar +/cmd/webui/assets/ach.wasm +/.idea/* +/client/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c1557d8e0..000000000 --- a/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: go -sudo: false -go: - - tip -before_install: - - go get github.com/mattn/goveralls -script: - - $HOME/gopath/bin/goveralls -service=travis-ci -ignore=main,main.go,/example/,./example/ diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..89ce3233f --- /dev/null +++ b/AUTHORS @@ -0,0 +1,36 @@ +# This file lists all individuals having contributed content to the repository. +# For how it is generated, see `make AUTHORS`. + +Adam Shannon +Adele Lopez +Andrew +Bob Hitch +Brandon Johnson +Brooke Kline +Chuck Phipps +ckbaum +Connor Kirschbaum +Dartths +eduardo.pereira +Eric Weise +Evan Marcey <42551883+emarcey@users.noreply.github.com> +fossabot +GrahamMcBain +Ian Berryman +Ian Bibby +Isaac Bremseth +Jordan Brown +Mark Alexander +Matias Insaurralde +Matthew Tom-Wolverton +Nathan Lakritz +Prasad Mahendra +Ray Johnson +rayjlinden <42587610+rayjlinden@users.noreply.github.com> +Rex Salisbury +sam +Sean Hildebrand +Shaik Nazeer Hussain +Vincent Xiao +Vinicius Miana +Wade Arnold diff --git a/AUTHORS.md b/AUTHORS.md deleted file mode 100644 index c6d6c4c87..000000000 --- a/AUTHORS.md +++ /dev/null @@ -1 +0,0 @@ -- Wade Arnold (@wadearnold) diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..58ed25b27 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,880 @@ +## v1.19.3 (Released 2022-09-01) + +Moov ACH v1.19.3 fixes an issue where Entry Hash values were not properly calculated. +- IATBatch hash values would not overflow properly. +- If `RDFIIdentification` was not properly trimmed to the "ABA 8" characters. + +IMPROVEMENTS + +- fix: calculate EntryHash from ABA8, properly format string value + +## v1.19.2 (Released 2022-08-30) + +IMPROVEMENTS + +- Allow MixedDebitsAndCredits in ARC, BOC, CIE, POP, RCK, TRC, TRC, XCK entries + +BUILD + +- fix(deps): update golang.org/x/oauth2 digest to 0ebed06 + +## v1.19.1 (Released 2022-08-22) + +This is the same release as v1.19.0 but with deployment issues fixed. + +## v1.19.0 (Released 2022-08-22) + +IMPROVEMENTS + +- Remove recordType and reserved fields from all models +- docs: update OpenAPI spec with /segment endpoint +- feat: add endpoint to segment files without a fileID +- feat: read fileID path parameter on creation +- feat: return ACH file with creation, flatten, and segment endpoints + +## v1.18.6 (Released 2022-08-11) + +IMPROVEMENTS + +- docs: clarify TEL/WEB payment type code field +- fix: contested return addenda parsing of dishonored returns +- server: verify fileID specified in JSON is kept + +BUILD + +- build: remove deprecated ioutil functions, gofmt from go1.19 +- fix(deps): update golang.org/x/oauth2 digest to 128564f +- fix(deps): update module github.com/aws/aws-lambda-go to v1.34.1 + +## v1.18.5 (Released 2022-07-19) + +IMPROVEMENTS + +- docs: update ppd-valid.json ID values +- feat: add R36 return code + +## v1.18.4 (Released 2022-07-14) + +IMPROVEMENTS + +- Fix entry category assignment in Reader + +## v1.18.3 (Released 2022-07-13) + +IMPROVEMENTS + +- feat: add R62 and R77 return codes + +BUILD + +- fix(deps): update module github.com/moov-io/base to v0.33.0 + +## v1.18.2 (Released 2022-07-07) + +IMPROVEMENTS + +- fix: reduce memory usage during MergeFiles + +BUILD + +- fix(deps): update module github.com/aws/aws-lambda-go to v1.32.1 +- fix(deps): update module github.com/moov-io/base to v0.32.0 +- fix(deps): update module github.com/stretchr/testify to v1.8.0 + +## v1.18.1 (Released 2022-06-15) + +IMPROVEMENTS + +- feat: support Refused COR/NOC codes + +BUILD + +- fix(deps): update module github.com/moov-io/base to v0.31.1 + +## v1.18.0 (Released 2022-06-07) + +IMPROVEMENTS + +- Export the SettlementDate field in BatchHeader and IATBatchHeader + +BUILD + +- fix(deps): update module github.com/moov-io/base to v0.30.0 +- fix(deps): update module github.com/stretchr/testify to v1.7.2 + +## v1.17.0 (Released 2022-06-02) + +ADDITIONS + +- feat: add build file endpoint + +IMPROVEMENTS + +- cmd/webui: update wasm_exec.js bundle +- cmd/webui: write library version to page +- feat: set Addenda trace numbers in (EntryDetail).SetTraceNumber + +## v1.16.1 (Released 2022-05-23) + +IMPROVEMENTS + +- server: read unorderedBatchNumbers query param + +## v1.16.0 (Released 2022-05-23) + +ADDITIONS + +- Add `AllowUnorderedBatchNumbers` validate option to skip ascending batch sequence validation + +IMPROVEMENTS + +- api: add missing validate opts query params, Addenda99 Dishonored and Contested returns +- batch: skip addendaFieldInclusion with Offset entries + +BUILD + +- build: update base images +- build: update codeql action +- build(deps): bump nokogiri from 1.13.4 to 1.13.6 in /docs +- fix(deps): update module github.com/aws/aws-lambda-go to v1.32.0 +- fix(deps): update module github.com/go-kit/log to v0.2.1 +- fix(deps): update module github.com/moov-io/base to v0.29.2 +- fix(deps): update module github.com/prometheus/client_golang to v1.12.2 + +## v1.15.1 (Released 2022-05-09) + +IMPROVEMENTS + +- feat: improve performance of getTraceNumbers + +## v1.15.0 (Released 2022-05-03) + +ADDITIONS + +- merge: allow splitting files on their maximum dollar amounts + +IMPROVEMENTS + +- docs: provide specific actions of CustomTraceNumbers +- fix(deps): update module github.com/aws/aws-lambda-go to v1.31.1 + +## v1.14.0 (Released 2022-03-30) + +ADDITIONS + +- feat: add support for "Dishonored Returns" with addenda records +- feat: support parsing out Contested Dishonored Returns + +BUILD + +- build: install git in builder image +- meta: update moov-io/base and our logging library + +## v1.13.1 (Released 2022-02-28) + +IMPROVEMENTS + +- fix: better date validation + +BUILD + +- build(deps): bump nokogiri from 1.12.5 to 1.13.3 in /docs + +## v1.13.0 (Released 2022-01-27) + +ADDITIONS + +- file: include ValidateOpts with JSON representations + +IMPROVEMENTS + +- merge: carry through ValidateOpts from files +- server: verify balanced file validates + +BUILD + +- build: enforce a coverage threshold + +## v1.12.2 (Released 2021-09-23) + +BUG FIXES + +- batch: check ValidateOpts prior to TraceNumber ascending verification +- file: pass through ValidateOpts for inner JSON unmarshals + +BUILD + +- fix(deps): update module github.com/moov-io/base to v0.24.0 + +## v1.12.1 (Released 2021-09-10) + +BUG FIXES + +- batch: check ValidateOpts for bypassing trace number ordering requirement + +BUILD + +- fix(deps): update module github.com/mattn/go-isatty to v0.0.14 + +## v1.12.0 (Released 2021-09-02) + +ADDITIONS + +- file: during creation check ValidateOpts for allowing empty headers +- server: include missing FileHeader and missing FileControl query params + +IMPROVEMENTS + +- file: allow for missing file header and control + +BUG FIXES + +- file: unmarshal with ValidateOpts if set + +## v1.11.0 (Released 2021-08-11) + +ADDITIONS + +- batch: allow unequal ServiceClassCodes to be used +- server: allow unequalServiceClassCode query param + +IMPROVEMENTS + +- addenda99: Use updated [R17 title / description from Nacha update](https://www.nacha.org/rules/reversals-and-enforcement) (#971) + +BUILD + +- fix(deps): update golang.org/x/oauth2 commit hash to faf39c7 +- fix(deps): update module github.com/aws/aws-lambda-go to v1.26.0 (#968) +- fix(deps): update module github.com/moov-io/base to v0.22.0 +- meta: fixup from gosec linter + +## v1.10.1 (Released 2021-07-26) + +BUG FIXES + +- cmd/achcli/describe: include EffectiveEntryDate on human readable output (#967) + +## v1.10.0 (Released 2021-07-23) + +ADDITIONS + +- Implementing CustomReturnCode validation (#966) +- Keep settlement date when parsing files. (#960) +- file,server: add method for parsing JSON with custom validation + +IMPROVEMENTS + +- file: update docs on FileFromJSON validation + +BUILD + +- build: use debian stable's slim image in webui +- build(deps): bump addressable from 2.7.0 to 2.8.0 in /docs +- fix(deps): update github.com/juju/ansiterm commit hash to 9283cdf (#952) +- fix(deps): update module github.com/aws/aws-lambda-go to v1.25.0 +- fix(deps): update module github.com/go-kit/kit to v0.11.0 +- fix(deps): update module github.com/moov-io/base to v0.21.1 + +## v1.9.3 (Released 2021-06-25) + +BUG FIXES + +- api: fixup openapi spec and generate client in CI + +BUILD + +- fix(deps): update module github.com/moov-io/base to v0.20.1 (#947) + +## v1.9.2 (Released 2021-06-17) + +BUG FIXES + +- ADV: fix entry hash err msg (#939) +- CTX: allow batches to be pre-notifications + +IMPROVEMENTS + +- docs: clean up api documentation (#938) + +BUILD + +- fix(deps): update module github.com/aws/aws-lambda-go to v1.24.0 (#935) +- fix(deps): update module github.com/mattn/go-isatty to v0.0.13 (#937) +- fix(deps): update module github.com/prometheus/client_golang to v1.11.0 (#941) + +## v1.9.1 (Released 2021-05-20) + +ADDITIONS + +- webui: Add human-readable ACH parsing (#933) + +BUILD + +- build(deps): bump nokogiri from 1.11.3 to 1.11.5 in /docs (#934) + +## v1.9.0 (Released 2021-05-13) + +ADDITIONS + +- reader: add `ReadFiles(...)` method for consuming a slice of filepaths (#932) + +BUG FIXES + +- set batchID from header on POST to create file (#926) + +IMPROVEMENTS + +- merge: combine entries together when Batch Headers match +- merge: don't include TraceNumbers that collide in merged files + +BUILD + +- build(deps): bump kramdown from 2.3.0 to 2.3.1 in /docs +- fix(deps): update module github.com/moov-io/base to v0.19.0 (#929) + +## v1.8.1 (Released 2021-05-06) + +IMPROVEMENTS + +- cmd/achcli: include last error message in -reformat + +## v1.8.0 (Released 2021-05-03) + +BUG FIXES + +- reader: reset the `currentBatch` when a `BatchControl` is read to avoid setting EntryDetail records in the wrong batch +- file: force a 10-digit entry hash in `File.Create` and `File.calculateEntryHash` to ensure the file assembled into a valid state + +ADDITIONS + +- writer: add `BypassValidation` option +- converters: add method to return the least significant digits of a number +- server: accept `customTraceNumbers` as a query param to set the corresponding validation option + +IMPROVEMENTS + +- file: update documentation for `Read`, `Create`, and `Validate/ValidateWith` to clarify behavior +- docs: describe validation query params on the HTTP request to create a file +- docs: remove function parentheses from code comments +- tests: add a regression test for issue #915 + +## v1.7.1 (Released 2021-04-16) + +There are no changes from v1.7.0. This release fixes issues during the release process. + +## v1.7.0 (Released 2021-04-16) + +ADDITIONS + +- webui: add button to support JSON to ACH conversions +- achcli: add `describe` package to allow human-readable printing to be reused in other tools +- batch: add validation option, `bypassCompanyIdentificationMatch`, to allow batches in which the `CompanyIdentification` field in the batch header and control do not match + +## v1.6.5 (Released 2021-04-15) + +IMPROVEMENTS + +- merge: optimize performance of `lineCount(..)` and reduce errors returned + +## v1.6.4 (Released 2021-04-14) + +ADDITIONS + +- Add option to create a file with zero batches (#884) + +IMPROVEMENTS + +- iso3166,iso4217: generate lists again after long hiatus + +BUG FIXES + +- file: sort EntryDetail records when flattening batches + +BUILD + +- fix(deps): update module github.com/prometheus/client_golang to v1.10.0 (#875) +- fix(deps): update module github.com/moov-io/base to v0.18.2 + +## v1.6.3 (Released 2021-03-19) + +BUG FIXES + +- file: updated `File.MergeFiles(..)` to override batch numbers in each file to prevent them from colliding + +IMPROVEMENTS + +- file: optimized and refactored `File.FlattenBatches(..)` and added benchmarks +- api: added missing required fields in models in the OpenAPI spec and removed unnecessary `omitempty` tags for required fields in Go models +- docs: removed extra spaces in the request body for creating a file to fix broken example +- docs: added a section about where to find Nacha Operating Rules and Nacha's ACH guide for developers +- docs: updated links in README to point to [project documentation site](https://moov-io.github.io/ach) + +## v1.6.2 (Released 2021-02-11) + +IMPROVEMENTS + +- api: update OpenAPI spec to include more details about data types +- file: ability to set custom batch sequence numbers +- docs: new [project documentation site](https://moov-io.github.io/ach) with updated styles and navigation +- docs: add page about Notification of Change (NOC) files +- docs: add read and write examples for CIE batches +- docs: update R10 and R11 return code descriptions +- docs: additional fixes and improvements to the README + +## v1.6.1 (Released 2021-01-25) + +BUG FIXES + +- fileHeader: `ImmediateDestination` and `ImmediateOrigin` should be separated by a space unless bypass validation is set and the routing number is 10 digits + +IMPROVEMENTS + +- docs: improvements to README by adding a new header, table of contents, related projects, and FAQ +- docs: clarified code comments related to `File.ValidateWith(..)` and fixed minor documentation errors + +## v1.6.0 (Released 2021-01-13) + +BUG FIXES + +- fileHeader: fix `ImmediateDestination` formatting when `BypassDestinationValidation` is set + +ADDITIONS + +- batch: allow addenda in CIE batches to be optional +- batch: allow POS batches to contain credits +- reader: allow backtick in the valid character set +- build: add tagged-release script to automate the release process + +IMPROVEMENTS + +- reader: report error when extra characters are found in one-line ACH files +- webui: display error when parsing fails +- deps: remove moov-io/customers and moov-io/paygate as dependencies +- docs: improvements to README by fixing typos, dead links, and formatting + +## v1.5.2 (Released 2020-11-16) + +BUG FIXES + +- server: pass validation options through to ACH reader and underlying batch + +IMPROVEMENTS + +- build: push ach-webui image and split up commands for each docker build + +## v1.5.1 (Released 2020-11-12) + +There are no changes from v1.5.0. This release fixes issues during the release process. + +## v1.5.0 (Released 2020-11-12) + +ADDITIONS + +- cmd/webui: initial setup for client-side ACH file parsing to their JSON forms +- entries: allow custom `TransactionCode` validation +- entries: allow custom `TraceNumber` values +- writer: allow setting `Writer.LineEnding` to use custom values for ending each outputted +- server: allow `ValidateOpts` to be set through HTTP query parameters + +IMPROVEMENTS + +- build: check and lint OpenAPI spec in CI +- api: update summaries of endpoints +- api: clean up OpenAPI spec after running Speccy +- docs: update content for moov-io.github.io/ach/ +- docs: add page on return files +- docs: add link to Moov's ACH blog post +- docs: fix filepath example in segmented files +- github: request version in bug reports +- github: add codeowners +- chore(deps): update golang docker tag to v1.15 + +BUG FIXES + +- server: fix batch additions by shimming JSON reading + +## v1.4.4 (Released 2020-08-04) + +BUG FIXES + +- entries: detect overflow when printing large amounts +- reader: only parse IAT entries when we're accumulating an IAT batch + +IMPROVEMENTS + +- achcli: print BatchControl records as well +- achcli: update help/usage text + +## v1.4.3 (Released 2020-07-23) + +BUILD + +- build: upload achcli binaries on each release + +## v1.4.2 (Released 2020-07-23) + +IMPROVEMENTS + +- api: add example plaintext file for create route +- docs: flip Usage section priority, link to godocs and examples +- file: include the struct field in Unmarshal errors + +BUG FIXES + +- api: clarify batchNumber in BatchHeader is an integer +- api: include missing CompanyIdentification field on BatchHeader docs +- api: include missing `CompanyIdentification` field on `BatchHeader` docs +- api: quote number-looking example values + +## v1.4.1 (Released 2020-07-09) + +BUG FIXES + +- batch: error if our offset account type is unknown + +BUILD + +- build: add openshift docker image +- build: enable codeql via github actions +- build: release via Actions, not TravisCI + +## v1.4.0 (Released 2020-06-29) + +Version v1.4.0 of ACH adds several notable features such as custom validation, a command-line tool `achcli` to describe files, and improvements for verifying NACHA compatibility on slightly malformed files. This release also contains enhanced testing and documentation improvements. + +**Custom Validation** + +The ACH library (and HTTP server) now supports custom validation with the [`ValidateOpts`](https://godoc.org/github.com/moov-io/ach#ValidateOpts) struct by calling `File.SetValidation(..)` and `Reader.SetValidation(...)`. This offers various options: + +- `RequireABAOrigin bool`: Enable or disable routing number validation over the `ImmediateOrigin` file header field +- `BypassOriginValidation`: Skip validation for the `ImmediateOrigin` file header field and allow custom `TraceNumber` values +- `BypassDestinationValidation`: Skip validation for the `ImmediateDestination` file header field and allow custom `TraceNumber` values + +The HTTP server also supports reading this struct with camel-cased names when calling the validation route. + +**achcli** + +`achcli` is a command-line utility for viewing ACH files in a more human readable format. This tool also allows masking `DFIAccountNumber` values with the `-mask` flag. + +``` +$ achcli -mask 20200601-1002-01.ach +Describing ACH file '20200601-1002-01.ach' + + Origin OriginName Destination DestinationName FileCreationDate FileCreationTime + 691000134 ASF APPLICATION SUPERVI 091400606 FIRST BANK & TRUST 181017 0306 + + BatchNumber SECCode ServiceClassCode CompanyName CompanyDiscretionaryData CompanyIdentification CompanyEntryDescription + 1 WEB 200 CoinLion 123456789 TRANSFER + + TransactionCode RDFIIdentification AccountNumber Amount Name TraceNumber Category + 26 09140060 *******89 12354 Paul Jones 091000017611242 Return + + Addenda99 + ReturnCode OriginalTrace DateOfDeath OriginalDFI AddendaInformation TraceNumber + R01 091400600000001 09100001 091000017611242 + + BatchCount BlockCount EntryAddendaCount TotalDebitAmount TotalCreditAmount + 1 1 1 12354 0 +``` + +**Malformed Files** + +ACH files with lines that are not 94 characters are now adjusted in-memory (missing or extra spaces) in an attempt to comply with NACHA standards. The underlying file on disk is not modified during this reading. + +----- + +ADDITIONS + +- batches: Add `LiftEffectiveEntryDate()` to offer parsed `time.Time` values of `EffectiveEntryDate` +- cmd/server: add version handler to admin HTTP server +- file: add BypassDestinationValidation to ValidateOpts +- file: add `ValidateWith` to override specific default validations +- file: support setting ValidateOpts on struct for calling Create() +- reader: morph lines to 94 characters if they end in spaces +- server: read `ValidateOpts` in HTTP validate route +- server: return fileID on create errors, enforce marshaled errors as strings +- file: support setting `ValidateOpts` on struct for calling `Create()` +- file: struct unmarshaling works again, it was depreciated for a couple releases +- reader: morph lines to 94 characters with spaces if they are some other length +- reader: allow setting ValidateOpts +- cmd/ach: initial setup of CLI tool to pretty print ACH files + +BUG FIXES + +- all: replace `Ç` with `C` across the project +- all: use filepath.Join instead of unix paths +- api: fixup flatten files OpenAPI spec +- api: note POST /files/create can return an error +- file: don't validate before flattening batches +- file: keep TraceNumbers when segmenting files +- server: fix segment OpenAPI spec and accept config body +- server: read empty SegmentFileConfiguration +- file: don't validate before flattening batches + +IMPROVEMENTS + +- api: used shared Error model +- api: use shorter summaries +- api: include AddendaXX, ADV, and IAT records that were missing from OpenAPI spec +- chore(deps): update module prometheus/client_golang to v1.4.1 +- chore(deps): update module gorilla/mux to v1.7.4 +- reader: write a test for what partial file comes back from invalid EntryDetails +- reader: allow zero-entry files if their controls signify as such +- server: use FoundABug error with mismatched routing +- validators: ensure alpha routing number check digit is invalid +- all: use filepath.Join instead of unix paths +- reader: append a lingering batch even if there's no batch control + +BUILD + +- chore(deps): update golang docker tag to v1.14 +- build: run sonatype-nexus-community/nancy in CI +- build: leverage moov-io/infra's Go linter script + +## v1.3.1 (Released 2020-01-22) + +BUG FIXES + +- api,client: There was a mistaken character in the OpenAPI docs `Ç` which should have been `C`. + +IMPROVEMENTS + +- build: upgrade golang.org/x/crypto + +BUILD + +- build: run sonatype-nexus-community/nancy in CI + +## v1.3.0 (Released 2020-01-20) + +BREAKING CHANGES + +- `ImmediateOrigin` values are written with a leading space instead of a zero (`0`) due to post-2013 NACHA guidelines. + +BUG FIXES + +- `addenda98` fix parsing with no spaces between routing and account number + +ADDITIONS + +- Add random names, amounts, and memo lines in test file generation script `cmd/writeACH/main.go` + +## v1.2.1 (Released 2019-10-11) + +BUG FIXES + +- fileHeader: Remove requirement of ImmediateOrigin to be a routing number + +## v1.2.0 (Released 2019-10-07) + +ADDITIONS + +- Add `FlattenBatches() (*File, error)` to `ach.File` + - FlattenBatches [minimizes File Batches by consolidating them](./docs/flatten-batches.md) with the same BatchHeader data into one batch. +- Add `POST /files/:id/flatten` which calls `FlattenBatches()` on a specific ACH file +- Add `POST /files/:id/balance` to [add Offset records](./docs/balanced-offset.md) onto each Batch in an ACH File. +- Addenda98: Add `ChangeCodeField()` for detailed information about a NOC/COR change file ([`ChangeCode`](https://godoc.org/github.com/moov-io/ach#ChangeCode)) +- Addenda99: Add `ReturnCodeField()` for detailed information about file returns ([`ReturnCode`](https://godoc.org/github.com/moov-io/ach#ReturnCode)) + +BUG FIXES + +- reader: set EntryDetail.Category to Return when Addenda99 is present +- batch: inspect Entrydetail records for Category +- batch: check ADV entries for Category +- reader: set EntryDetail.Category to NOC when Addenda98 is present +- file: Validate files after reading them from their JSON representation +- server: actaully render new Credit and Debit files from segmentation + +IMPROVEMENTS + +- created example files for HTTP routes +- file: parse ISO8601 and RFC3339 timestamps in JSON blobs + +BUILD + +- upgrade to Go 1.13 +- build: download CI tools rather than install +- build: update staticcheck with Go 1.13 + +## v1.1.0 (Released 2019-08-19) + +BREAKING CHANGES + +In our OpenAPI we've renamed fields generated as `Id` to `ID`, which is more in-line with Go's style conventions. + +BUG FIXES + +- fileHeader: allow immediate origin to be a 10 digit value (See: [#513](https://github.com/moov-io/ach/pull/513) by [@eduardev](https://github.com/eduardev)) +- Fix JSON omitempty typo in `ADVEntryDetail` +- fileHeader: trim padded 0's from ImmediateOriginField() and fixup docs +- batch: only check DNE specifics if the SEC code is DNE +- files: FileHeader validate CheckRoutingNumber +- files: on empty FileCreation dates or times render today's value +- reader: return ErrFileAddendaOutsideBatch from parseAddenda when currentBatch is nil + +ADDITIONS + +- batch: add Equal method +- Addenda99: Add `ReturnCodeField()` for detailed information about a returned file +- files: support arbitrary merging of ACH files (See [#529](https://github.com/moov-io/ach/issues/529)) +- entryDetail: validate that Amount is non-negative +- batch: create Debit and Credit EntryDetail offset records if needed (via `WithOffset`) +- addenda types: Add RuneCountInString check to Parse(record string) function +- file: create debit ach file and credit ach file from a mixed debit and credit ach file (via `SegmentFile`) (see [#528](https://github.com/moov-io/ach/issues/528)) +- cmd/server: add environment variables to override command line flags (`-http.addr` and `-log.format`) +- file: support ADV and IAT files in (*File).SegmentFile(...) +- cmd/server: bind HTTP server with TLS if HTTPS_* variables are defined +- cmd/server: add endpoints for segmenting files into debits and credits + +BUILD + +- vendor: update dependencies +- Fix `moov/achfuzz` docker image build +- api: inline parameters to fix codegen crash +- build: push moov/ach:latest and update docs accordingly +- chore(deps): update module prometheus/client_golang to v1.1.0 + +## v1.0.0 (Released 2019-03-26) + +- No changes from v0.6.0 + +## v0.6.0 (Released 2019-03-26) + +BREAKING CHANGES + +- `file.NotificationOfChange` accepts `Batcher` instead of `*BatchCOR` to comply with linter errors + +ADDITIONS + +- Add const values for `BatchHeader.StandardEntryClassCode` (See [#392](https://github.com/moov-io/ach/issues/392)) +- Add const values for `BatchHeader.ServiceClassCode` and `BatchControl.ServiceClassCode`. (See [#391](https://github.com/moov-io/ach/issues/391)) +- Add const values for `EntryDetail.TransactionCode` (See [#363](https://github.com/moov-io/ach/issues/363)) +- server: Record `ach_files_deleted` metric. (See: [#408](https://github.com/moov-io/ach/pull/408)) +- server: log x-request-id header if present. (See: [#407](https://github.com/moov-io/ach/pull/407)) +- server: Delete old `ach.File` objects from in-memory repository according to `ACH_FILE_TTL` env variable. +- server: Support `-log.format=json` for JSON formatted logs + +BUG FIXES + +- Accept File's without an ID specified. Generate a random ID. (See: [#405](https://github.com/moov-io/ach/pull/405)) +- server: Fix nil panics. (See: [#406](https://github.com/moov-io/ach/pull/406)) +- server: Fix type-casting panics. (See: [#423](https://github.com/moov-io/ach/pull/423)) +- server: validate file endpoint returns 400 instead of 500 (See: [#488](https://github.com/moov-io/ach/pull/488)) +- server: set CORS headers on `GET /ping` route + +BUILD + +- `megacheck` is deprecated. staticcheck should be used instead. (See [#430](https://github.com/moov-io/ach/issues/430)) +- Automate releases with Docker and binary uploads to release page. +- Update dependencies to their latest versions +- Update to Go 1.12 + +## v0.5.0 (Released 2018-11-29) + +BREAKING CHANGES + +- `TraceNumber` has been changed from `int` to a `string`. (See [#366](https://github.com/moov-io/ach/issues/366)) + - Previously zero-prefixed ABA routing numbers would have their leading zero truncated. +- `OriginalTrace` has been changed from `int` to a `string`. (See [#366](https://github.com/moov-io/ach/issues/366)) + +ADDITIONS + +- Support `StandardEntryClassCode` (Batch types): + - ADV (See [#340](https://github.com/moov-io/ach/issues/340)) + - TRC (See [#346](https://github.com/moov-io/ach/issues/346)) + - TRX (See [#372](https://github.com/moov-io/ach/issues/372)) + - XCK (See [#347](https://github.com/moov-io/ach/issues/347)) +- `TransactionCode` match `ServiceClassCode` (See [#56](https://github.com/moov-io/ach/issues/56)) +- `Addenda02.TerminalState` validation for BatchPOS and BatchSHR (See [#375](https://github.com/moov-io/ach/issues/375)) + +REMOVALS + +- Remove deprecated functions from `EntryDetail` (See [#385](https://github.com/moov-io/ach/issues/385)) + +## v0.4.0 (Released 2018-11-06) + +BREAKING CHANGES + +- `EntryDetail.Addendum` has been broken out into `Addenda02`, `Addenda05`, `Addenda98`, and `Addenda99` fields on `EntryDetail`. +- IAT `EntryDetail.Addendum` has been broken out into Addenda 10-18, 98 and 99. + +ADDITIONS + +- Support `StandardEntryClassCode` (Batch types): + - ACK (See [#327](https://github.com/moov-io/ach/issues/327)) + - ATX (See [#327](https://github.com/moov-io/ach/issues/327)) + - DNE (See [#342](https://github.com/moov-io/ach/issues/342)) + - ENR (See [#343](https://github.com/moov-io/ach/issues/343)) +- Support NOC for IAT Entries (See [#328](https://github.com/moov-io/ach/issues/328)) +- Add `FileFromJson` for reading `File` objects as JSON. +- Add `X-Total-Count` response headers on `GET /files/:id/batches` (See [#280](https://github.com/moov-io/ach/issues/280)) + +IMPROVEMENTS + +- Multiple parsing errors are returned in `Reader.Read() error`'s error message. +- IAT: Validate ISODestinationCountryCode according to ISO 3166 +- IAT: Validate ISOOriginatingCurrencyCode and ISODestinationCurrencyCode according to ISO 4217 +- build: Run CI tests against Windows +- Verify record lengths in [IAT] BatchHeader, [IAT] BatchControl, FileControl, FileHeader, and [IAT] EntryDetail. + +BUG FIXES +- `cmd/server`: don't expect trailing slash on endpoints +- `cmd/server`: Grab write lock on delete requests +- Several panics are fixed from fuzzing +- Reject invalid ABA routing numbers + +## v0.3.3 (Released 2018-10-08) + +ADDITIONS + +- Expose `TypeCode` on Addenda records + +IMPROVEMENTS + +- Run as unprivileged user in Docker image +- `cmd/readACH`: add -json to print `File` as JSON +- `cmd/writeACH`: validate file before writing +- `cmd/writeACH`: add -json to output `File` as JSON + +BUG FIXES + +- Fix reading batches out from JSON endpoint +- Fix plaintext ACH file rendering endpoint + +## v0.3.2 (Released 2018-10-05) + +ADDITIONS + +- Handle pre-flight requests and proxy back CORS headers + +## v0.3.1 (Released 2018-10-05) + +ADDITIONS + +- Add `X-Total-Count` on GET responses +- Proxy back CORS headers if sent on the request + +BUG FIXES + +- Drop requirement for /-suffix on GET /files +- Don't trample content-type in encodeResponse + +## v0.3.0 (Released 2018-09-26) + +FEATURES + +- Added HTTP Server +- SEC Code CIE (Customer-Initiated Entry) ([#209](https://github.com/moov-io/ach/issues/209)) +- Support IAT ([#211](https://github.com/moov-io/ach/issues/211)) +- IAT Returns ([#233](https://github.com/moov-io/ach/issues/233)) +- Support CTX ([#212](https://github.com/moov-io/ach/issues/212)) + +IMPROVEMENTS + +- Added admin HTTP service. +- Added `GET /ping` route (unauthed) + +BUG FIXES + +- Fixes to parsing and validation. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..3f089c799 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +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. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +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. + +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. + +## Scope + +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. + +## Enforcement + +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. + +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. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..8d9f84668 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,208 @@ +# Contributing + +Wow, we really appreciate that you even looked at this section! We are trying to make the worlds best atomic building blocks for financial services that accelerate innovation in banking and we need your help! + +You only have a fresh set of eyes once! The easiest way to contribute is to give feedback on the documentation that you are reading right now. This can be as simple as sending a message to our Google Group with your feedback or updating the markdown in this documentation and issuing a pull request. + +Stability is the hallmark of any good software. If you find an edge case that isn't handled please open an GitHub issue with the example data so that we can make our software more robust for everyone. We also welcome pull requests if you want to get your hands dirty. + +Have a use case that we don't handle; or handle well! Start the discussion on our Google Group or open a GitHub Issue. We want to make the project meet the needs of the community and keeps you using our code. + +Please review our [Code of Conduct](CODE_OF_CONDUCT.md) to ensure you agree with the values of this project. + +We use GitHub to manage reviews of pull requests. + +* If you have a trivial fix or improvement, go ahead and create a pull request, addressing (with `@...`) one or more of the maintainers (see [AUTHORS.md](AUTHORS.md)) in the description of the pull request. + +* If you plan to do something more involved, first propose your ideas in a Github issue. This will avoid unnecessary work and surely give you and us a good deal of inspiration. + +* Relevant coding style guidelines are the [Go Code Review Comments](https://code.google.com/p/go-wiki/wiki/CodeReviewComments) and the _Formatting and style_ section of Peter Bourgon's [Go: Best Practices for Production Environments](http://peter.bourgon.org/go-in-production/#formatting-and-style). + +* When in doubt follow the [Go Proverbs](https://go-proverbs.github.io/) + +* Checkout this [Overview of Go Tooling](https://www.alexedwards.net/blog/an-overview-of-go-tooling) by Alex Edwards + +## Getting the code + +We recommend using additional git remote's for pushing/pulling code. Go cares about where the `ach` project lives relative to `GOPATH`. + +First, pull down our source code: + +``` +$ git clone git@github.com:moov-io/ach.git +``` + +Then, add your (or another user's) fork. + +``` +$ cd $GOPATH/src/github.com/moov-io/ach + +$ git remote add $user git@github.com:$user/ach.git + +$ git fetch $user +``` + +Now, feel free to branch and push (`git push $user $branch`) to your remote and send us Pull Requests! + +## Pull Requests + +A good quality PR will have the following characteristics: + +* It will be a complete piece of work that adds value in some way. +* It will have a title that reflects the work within, and a summary that helps to understand the context of the change. +* There will be well written commit messages, with well crafted commits that tell the story of the development of this work. +* Ideally it will be small and easy to understand. Single commit PRs are usually easy to submit, review, and merge. +* The code contained within will meet the best practices set by the team wherever possible. +* The code is able to be merged. +* A PR does not end at submission though. A code change is not made until it is merged and used in production. + +A good PR should be able to flow through a peer review system easily and quickly. + +Our Build pipeline utilizes [Travis-CI](https://travis-ci.org/moov-io/ach) to enforce many tools that you should add to your editor before issuing a pull request. Learn more about these tools on our [Go Report card](https://goreportcard.com/report/github.com/moov-io/ach) + + +## Additional SEC (Standard Entry Class) code batch types. + +SEC type's in the Batch Header record define the payment type of the following Entry Details and Addenda. The format of the records in the batch is the same between all payment types but NACHA defines different rules for the values that are held in each record field. To add support for an additional SEC type you will need to implement NACHA rules for that type. The vast majority of rules are implemented in ach.batch and then composed into Batch(SEC) for reuse. All Batch(SEC) types must be a ach.Batcher. + +2. Create an issue with the NACHA rules and record layout for the batch type. +3. Create a new struct of the batch type. In the following example we will use MTE (Machine Transfer Entry) as our example. +4. The following code would be place in a new file batchMTE.go next to the existing batch types. +5. The code is stub code and the MTE type is not implemented. For concrete examples review the existing batch types in the source. + +Create a new struct and compose ach.batch + +```go +type BatchMTE struct { + batch +} +``` +Add the ability for the new type to be created. + +```go +func NewBatchMTE(bh *BatchHeader) *BatchMTE { + batch := new(BatchMTE) + batch.setControl(NewBatchControl) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} +``` + +To support the Batcher interface you must add the following functions that are not implemented in `ach.Batch`. + +- `Validate() error` +- `Create() error` + +Validate is designed to enforce the NACHA rules for the MTE payment type. Validate is run after a batch of this type is read from a file. If you are creating a batch from code call validate afterwards. + +```go +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the batch. +func (batch *BatchMTE) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + // Add configuration based validation for this type. + // ... batch.isAddendaCount(1) + // Add type specific validation. + // ... + return nil +} +``` +Create takes the Batch Header and Entry details and creates the proper sequence number and batch control. If additional logic specific to the SEC type is required it building a batch file it should be added here. + +```go +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchMTE) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + // Additional steps specific to batch type + // ... + + if err := batch.Validate(); err != nil { + return err + } + return nil +} +``` + +Finally add the batch type to the NewBatch factory in batch.go. + +```go +//... +case MTE: + return NewBatchMTE(bh), nil +//... +``` + +In order for the code to be merged with a Pull requests we require a `batchMTE_test.go` test file that covers the logic of the type. Refer to the [Go blog post on code coverage metrics](https://blog.golang.org/cover). + +## Command Line tools + +We have written two command line tools ([`readACH`](github.com/moov-io/ach/cmd/readACH) and [`writeACH`](github.com/moov-io/ach/cmd/writeACH)) that work with ACH files. + +#### readACH + +`readACH` will output the details of an ACH file to the terminal, but `readACH` can also emit a JSON representation of the file following our `ach.File` type. + +``` +$ readACH -help +Usage of readACH: + -fPath string + File Path (default "201805101354.ach") + -json + Output ACH File in JSON to stdout + +$ readACH -fPath test/testdata/ppd-debit.ach -json | jq . +{"id":"","fileHeader":{"id":"","immediateDestination":"076401251","immediateOrigin":"076401251", ... +``` + +#### writeACH + +`writeACH` creates an ACH file with 4 batches each containing 1250 detail and addenda records. A custom output filepath can be specified with `-fPath`. + + +## Benchmarks + +Running benchmarks can be ran with `go test`. Typically machines running benchmarks are idle except for the benchmarked code. Please report all machine hardware specs and OS/Go versions when reporting benchmarks. Please refer to Dave Cheny's [benchmarking buide](https://dave.cheney.net/2013/06/30/how-to-write-benchmarks-in-go). + +Example: + +``` +$ cd ach/ # This project + +$ go test -bench=BenchmarkWEBDebitRead -count=10000 > BenchmarkWEBDebitRead.txt +$ go test ./cmd/readACH -bench=BenchmarkTestFileRead -count=10000 > BenchmarkTestFileRead.txt +``` + +## References + +* [Wikipeda: Automated Clearing House](http://en.wikipedia.org/wiki/Automated_Clearing_House) +* [Nacha ACH Network: How it Works](https://www.nacha.org/ach-network) +* [Federal ACH Directory](https://www.frbservices.org/EPaymentsDirectory/search.html) + +## Format Specification + +* [NACHA ACH File Formatting](https://www.nacha.org/system/files/resources/AAP201%20-%20ACH%20File%20Formatting.pdf) +* [PNC ACH File Specification](http://content.pncmc.com/live/pnc/corporate/treasury-management/ach-conversion/ACH-File-Specifications.pdf) +* [Thomson Reuters ACH FIle Structure](http://cs.thomsonreuters.com/ua/acct_pr/acs/cs_us_en/pr/dd/ach_file_structure_and_content.htm) +* [Gusto: How ACH Works: A developer perspective](http://engineering.gusto.com/how-ach-works-a-developer-perspective-part-4/) + +![ACH File Layout](https://github.com/moov-io/ach/blob/master/documentation/ach_file_structure_shg.gif) + +## Inspiration + +* [ACH:Builder - Tools for Building ACH](http://search.cpan.org/~tkeefer/ACH-Builder-0.03/lib/ACH/Builder.pm) +* [mosscode / ach](https://github.com/mosscode/ach) +* [Helper for building ACH files in Ruby](https://github.com/jm81/ach) +* [Glenselle / nACH2](https://github.com/glenselle/nACH2) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..db99b62f1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM golang:1.19-alpine as builder +WORKDIR /go/src/github.com/moov-io/ach +RUN apk add -U git make +RUN adduser -D -g '' --shell /bin/false moov +COPY . . +RUN make build +USER moov + +FROM scratch +LABEL maintainer="Moov " + +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY --from=builder /go/src/github.com/moov-io/ach/bin/server /bin/server +COPY --from=builder /etc/passwd /etc/passwd + +USER moov +EXPOSE 8080 +EXPOSE 9090 +ENTRYPOINT ["/bin/server"] diff --git a/Dockerfile-fuzz b/Dockerfile-fuzz new file mode 100644 index 000000000..3859ed5f5 --- /dev/null +++ b/Dockerfile-fuzz @@ -0,0 +1,10 @@ +FROM golang:1.19 as builder +LABEL maintainer="Moov " +RUN apt-get update -qq && apt-get install -y git make +WORKDIR /go/src/github.com/moov-io/ach +COPY . . +RUN go mod download && go mod vendor +WORKDIR /go/src/github.com/moov-io/ach/test/fuzz-reader +RUN make install +RUN make fuzz-build +ENTRYPOINT make fuzz-run diff --git a/Dockerfile-openshift b/Dockerfile-openshift new file mode 100644 index 000000000..c21ec294d --- /dev/null +++ b/Dockerfile-openshift @@ -0,0 +1,17 @@ +# Step one: build scapresults +FROM registry.access.redhat.com/ubi8/go-toolset as builder +COPY . . +RUN make build + +FROM registry.access.redhat.com/ubi8/ubi-minimal + +ARG VERSION=unknown +LABEL maintainer="Moov " +LABEL name="ach" +LABEL version=$VERSION + +COPY --from=builder /opt/app-root/src/bin/server /bin/server + +EXPOSE 8080 +EXPOSE 9090 +ENTRYPOINT ["/bin/server"] diff --git a/Dockerfile-webui b/Dockerfile-webui new file mode 100644 index 000000000..afd8af024 --- /dev/null +++ b/Dockerfile-webui @@ -0,0 +1,19 @@ +FROM golang:1.19 as builder +WORKDIR /go/src/github.com/moov-io/ach +RUN apt-get update && apt-get install make gcc g++ +COPY . . +RUN make build-webui + +FROM debian:stable-slim +LABEL maintainer="Moov " +RUN apt-get update && apt-get install -y ca-certificates + +COPY --from=builder /go/src/github.com/moov-io/ach/bin/webui /bin/webui +COPY --from=builder /go/src/github.com/moov-io/ach/cmd/webui/assets/ /assets/ +# USER moov + +ENV ASSETS_PATH=../assets/ + +EXPOSE 8083 +EXPOSE 9093 +ENTRYPOINT ["/bin/webui"] diff --git a/NOTICE b/NOTICE new file mode 100755 index 000000000..5a30a7f3c --- /dev/null +++ b/NOTICE @@ -0,0 +1,17 @@ +ach +Copyright 2018-2020 The Moov Authors + +This product includes software developed at The Moov Authors and +third-party software developed by the licenses listed below. + +========================================================================= + +github.com/moov-io/base Apache-2.0 +github.com/prometheus/client_golang Apache-2.0 +github.com/gorilla/mux BSD-3-Clause +github.com/rickar/cal BSD-3-Clause +github.com/vividcortex/gohistogram MIT +github.com/go-kit/kit MIT +github.com/hashicorp/golang-lru MPL-2.0 + +========================================================================= diff --git a/README.md b/README.md index 598b5083d..8a8c53c14 100644 --- a/README.md +++ b/README.md @@ -1,299 +1,404 @@ -moov-io/ach -=== +[![Moov Banner Logo](https://user-images.githubusercontent.com/20115216/104214617-885b3c80-53ec-11eb-8ce0-9fc745fb5bfc.png)](https://github.com/moov-io) + +

+ Project Documentation + · + API Endpoints + · + API Guide + · + Community + · + Blog +
+
+

+ [![GoDoc](https://godoc.org/github.com/moov-io/ach?status.svg)](https://godoc.org/github.com/moov-io/ach) -[![Build Status](https://travis-ci.org/moov-io/ach.svg?branch=master)](https://travis-ci.org/moov-io/ach) -[![Coverage Status](https://coveralls.io/repos/github/moov-io/ach/badge.svg?branch=master)](https://coveralls.io/github/moov-io/ach?branch=master) +[![Build Status](https://github.com/moov-io/ach/workflows/Go/badge.svg)](https://github.com/moov-io/ach/actions) +[![Coverage Status](https://codecov.io/gh/moov-io/ach/branch/master/graph/badge.svg)](https://codecov.io/gh/moov-io/ach) [![Go Report Card](https://goreportcard.com/badge/github.com/moov-io/ach)](https://goreportcard.com/report/github.com/moov-io/ach) -[![Apache 2 licensed](https://img.shields.io/badge/license-Apache2-blue.svg)](https://raw.githubusercontent.com/moov-io/ach/master/LICENSE) +[![Repo Size](https://img.shields.io/github/languages/code-size/moov-io/ach?label=project%20size)](https://github.com/moov-io/ach) +[![Apache 2 License](https://img.shields.io/badge/license-Apache2-blue.svg)](https://raw.githubusercontent.com/moov-io/ach/master/LICENSE) +[![Slack Channel](https://slack.moov.io/badge.svg?bg=e01563&fgColor=fffff)](https://slack.moov.io/) +[![Docker Pulls](https://img.shields.io/docker/pulls/moov/ach)](https://hub.docker.com/r/moov/ach) +[![GitHub Stars](https://img.shields.io/github/stars/moov-io/ach)](https://github.com/moov-io/ach) +[![Twitter](https://img.shields.io/twitter/follow/moov?style=social)](https://twitter.com/moov?lang=en) +# moov-io/ach +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. -Package 'moov-io/ach' implements a file reader and writer for parsing [ACH](https://en.wikipedia.org/wiki/Automated_Clearing_House -) Automated Clearing House files. ACH is the primary method of electronic money movement throughout the United States. +ACH implements a reader, writer, and validator for Automated Clearing House ([ACH](https://en.wikipedia.org/wiki/Automated_Clearing_House)) files. ACH is the primary method of electronic money movement throughout the United States. The HTTP server is available in a [Docker image](#docker) and the Go package `github.com/moov-io/ach` is available. -## Project Status +If you're looking for an event driven ACH engine for uploading/downloading files and operations we have built [moov-io/achgateway](https://github.com/moov-io/achgateway) and run it in production. Our article [How and When to use the Moov ACH Library](https://moov.io/blog/education/how-and-when-to-use-the-moov-ach-library/) will help to generate ACH files for upload to your ODFI. -ACH is at an early stage and under active development. Please star the project if you are interested in its progress. +## Table of contents -* Library currently supports the reading and writing - * PPD (Prearranged payment and deposits) - * WEB (Internet-initiated Entries ) - * CCD (Corporate credit or debit) +- [Project status](#project-status) +- [Usage](#usage) + - As an API + - [Docker](#docker) ([Config](#configuration-settings)) + - [Google Cloud](#google-cloud-run-button) ([Config](#configuration-settings)) + - [HTTP API](#http-api) ([Config](#configuration-settings)) + - [Data persistence](#data-persistence) + - [As a Go module](#go-library) + - [As a command line tool](#command-line) + - [As an in-browser parser](##in-browser-ach-file-parser) +- [OpenAPI SDKs](#sdks) +- [Learn about ACH](#learn-about-ach) +- [FAQ](#faq) +- [Getting help](#getting-help) +- [Supported and tested platforms](#supported-and-tested-platforms) +- [Contributing](#contributing) +- [Related projects](#related-projects) +## Project status -## Project Roadmap -* Additional SEC codes will be added based on library users needs. Please open an issue with a valid test file. -* Review the project issues for more detailed information +Moov ACH is actively used in multiple production environments. Please star the project if you are interested in its progress. The project supports generating and parsing all Standard Entry Class (SEC) codes. If you have layers above ACH to simplify tasks, perform business operations, or found bugs we would appreciate an issue or pull request. Thanks! -## Usage and examples -Examples exist in projects [example](https://github.com/moov-io/ach/tree/master/example) folder. The following is based on [simple file creation](https://github.com/moov-io/ach/tree/master/example/simple-file-creation) +## Usage - To create a file - - ```go - file := ach.NewFile(ach.FileParam{ - ImmediateDestination: "0210000890", - ImmediateOrigin: "123456789", - ImmediateDestinationName: "Your Bank", - ImmediateOriginName: "Your Company", - ReferenceCode: "#00000A1"}) -``` +The ACH project implements an HTTP server and Go library for creating and modifying ACH files. There are client libraries available for both [Go](https://pkg.go.dev/github.com/moov-io/ach) and [Node/JavaScript](https://github.com/moov-io/ach-node-sdk). We also have an extensive list of [examples](https://pkg.go.dev/github.com/moov-io/ach/examples) of the reader and writer applied to various ACH transaction types. -To create a batch +### Docker -Errors only if payment type is not supported +We publish a [public Docker image `moov/ach`](https://hub.docker.com/r/moov/ach/) from Docker Hub or use this repository. No configuration is required to serve on `:8080` and metrics at `:9090/metrics` in Prometheus format. We also have Docker images for [OpenShift](https://quay.io/repository/moov/ach?tab=tags) published as `quay.io/moov/ach`. - ```go - batch := ach.NewBatch(ach.BatchParam{ - ServiceClassCode: "220", - CompanyName: "Your Company", - StandardEntryClass: "PPD", - CompanyIdentification: "123456789", - CompanyEntryDescription: "Trans. Description", - CompanyDescriptiveDate: "Oct 23", - ODFIIdentification: "123456789"}) +Pull & start the Docker image: +``` +docker pull moov/ach:latest +docker run -p 8080:8080 -p 9090:9090 moov/ach:latest ``` -To create an entry - - ```go - entry := ach.NewEntryDetail(ach.EntryParam{ - ReceivingDFI: "102001017", - RDFIAccount: "5343121", - Amount: "17500", - TransactionCode: "27", - IDNumber: "ABC##jvkdjfuiwn", - IndividualName: "Bob Smith", - DiscretionaryData: "B1"}) +List files stored in-memory: +``` +curl localhost:8080/files +``` +``` +{"files":[],"error":null} ``` -To add one or more optional addenda records for an entry +Create a file on the HTTP server: +``` +curl -X POST --data-binary "@./test/testdata/ppd-debit.ach" http://localhost:8080/files/create +``` +``` +{"id":"","error":null} +``` - ```go - addenda := ach.NewAddenda(ach.AddendaParam{ - PaymentRelatedInfo: "bonus pay for amazing work on #OSS"}) - entry.AddAddenda(addenda) +Read the ACH file (in JSON form): +``` +curl http://localhost:8080/files/ ``` +``` +{"file":{"id":"","fileHeader":{"id":"","immediateDestination":"231380104","immediateOrigin":"121042882", ... +``` + +### Google Cloud Run button -Entries are added to batches like so: +To get started in a hosted environment you can deploy this project to the Google Cloud Platform. - ```go - batch.AddEntry(entry) +From your [Google Cloud dashboard](https://console.cloud.google.com/home/dashboard) create a new project and call it: ``` +moov-ach-demo +``` + +Click the button below to deploy this project to Google Cloud. -When all of the Entries are added to the batch we can create the batch. +[![Run on Google Cloud](https://deploy.cloud.run/button.svg)](https://deploy.cloud.run/?git_repo=https://github.com/moov-io/ach&revision=master) - ```go - if err := batch.Create(); err != nil { - fmt.Printf("%T: %s", err, err) - } - ``` +> **Note**: If you get an error about the image being marked as "Do Not Trust" follow the below steps. -And batches are added to files much the same way: +
+Error: You launched this custom Cloud Shell image as "Do not trust" - ```go - file.AddBatch(batch) +``` +$ cloudshell_open --repo_url "https://github.com/moov-io/ach" --page "shell" --git_branch "master" +Error: You launched this custom Cloud Shell image as "Do not trust". +In this mode, your credentials are not available and this experience +cannot deploy to Cloud Run. Start over and "Trust" the image. +Error: aborting due to untrusted cloud shell environment ``` -Now add a new batch for accepting payments on the web +This error occurs when some security settings on your account / cloud shell are locked down. To run ACH you need to trust the image, so in the top-right click to restart this image as Trusted. -```go - batch2, _ := ach.NewBatch(ach.BatchParam{ - ServiceClassCode: "220", - CompanyName: "Your Company", - StandardEntryClass: "WEB", - CompanyIdentification: "123456789", - CompanyEntryDescription: "subscr", - CompanyDescriptiveDate: "Oct 23", - ODFIIdentification: "123456789"}) +![](./docs/images/gcp-run-button/1-image-trust-settings.png) + +Click to "Return to default" + +![](./docs/images/gcp-run-button/2-confirm-prompt.png) + +Then you'll need to clone down and launch ACH. Pick option #3 to clone this project. + +``` +cloudshell_open --repo_url "https://github.com/moov-io/ach" --page "shell" --git_branch "master" ``` -Add an entry and define if it is a single or reoccurring payment. The following is a reoccurring payment for $7.99 +Start the ACH server inside the cloned repository. +``` +go run ./cmd/serverr +``` + +Connect to the web preview (e.g. `https://YOUR-ACH-APP-URL.a.run.app:8080/files`) +![](./docs/images/gcp-run-button/3-web-preview.png) + +
-```go - entry2 := ach.NewEntryDetail(ach.EntryParam{ - ReceivingDFI: "102001017", - RDFIAccount: "5343121", - Amount: "799", - TransactionCode: "22", - IDNumber: "#123456", - IndividualName: "Wade Arnold", - DiscretionaryData: "R"}) +
- addenda2 := ach.NewAddenda(ach.AddendaParam{ - PaymentRelatedInfo: "Monthly Membership Subscription"}) + +In the cloud shell you should be prompted with: +``` +Choose a project to deploy this application: ``` -Add the entry to the batch -```go - entry2.AddAddenda(addenda2) +Using the arrow keys select: +``` +moov-ach-demo ``` -Create and add the second batch -```go - batch2.AddEntry(entry2) - if err := batch2.Create(); err != nil { - fmt.Printf("%T: %s", err, err) - } - file.AddBatch(batch2) +You'll then be prompted to choose a region, use the arrow keys to select the region closest to you and hit enter. ``` +Choose a region to deploy this application: +``` + -Once we added all our batches we must build the file - ```go - if err := file.Create(); err != nil { - fmt.Printf("%T: %s", err, err) - } +Upon a successful build you will be given a URL where the API has been deployed: +``` +https://YOUR-ACH-APP-URL.a.run.app ``` -Finally we wnt to write the file to an io.Writer +From the cloud shell you need to cd into the `ach` folder: +``` +cd ach +``` - ```go - w := ach.NewWriter(os.Stdout) - if err := w.Write(file); err != nil { - fmt.Printf("%T: %s", err, err) - } - w.Flush() -} +Now you can list files stored in-memory: +``` +curl https://YOUR-ACH-APP-URL.a.run.app/files +``` +You should get this response: +``` +{"files":[],"error":null} ``` -Which will generate a well formed ACH flat file. -```text -101 210000890 1234567891708290000A094101Your Bank Your Company #00000A1 -5200Your Company 123456789 PPDTrans. DesOct 23010101 1234567890000001 -6271020010175343121 0000017500#456789 Bob Smith B11234567890000001 -705bonus pay for amazing work on #OSS 00010000001 -82000000020010200101000000017500000000000000123456789 234567890000001 -5220Your Company 123456789 WEBsubscr Oct 23010101 1234567890000002 -6221020010175343121 0000000799#123456 Wade Arnold R 1234567890000001 -705Monthly Membership Subscription 00010000001 -82200000020010200101000000000000000000000799123456789 234567890000002 -9000002000001000000040020400202000000017500000000000799 +Create a file on the server: +``` +curl -X POST --data-binary "@./test/testdata/ppd-debit.ach" https://YOUR-ACH-APP-URL.a.run.app/files/create ``` +You should get this response: +``` +{"id":"","error":null} +``` + -# Contributing +Finally, read the contents of the file you've just posted: +``` +curl https://YOUR-ACH-APP-URL.a.run.app/files/ +``` + +You should get this response: +``` +{"file":{"id":"","fileHeader":{"id":"...","immediateDestination":"231380104","immediateOrigin":"121042882", ... +``` -We use GitHub to manage reviews of pull requests. +### HTTP API -* If you have a trivial fix or improvement, go ahead and create a pull - request, addressing (with `@...`) one or more of the maintainers - (see [AUTHORS.md](AUTHORS.md)) in the description of the pull request. +The package [`github.com/moov-io/ach/server`](https://pkg.go.dev/github.com/moov-io/ach/server) offers an HTTP and JSON API for creating and editing files. If you're using Go the `ach.File` type can be used, otherwise you can send properly formatted JSON. We have an [example JSON file](test/testdata/ppd-valid.json), but each SEC type will generate different JSON. -* If you plan to do something more involved, first propose your ideas - in a Github issue. This will avoid unnecessary work and surely give - you and us a good deal of inspiration. +Examples: [Go](examples/http/main.go) | [Ruby](https://github.com/moov-io/ruby-ach-demo) -* Relevant coding style guidelines are the [Go Code Review - Comments](https://code.google.com/p/go-wiki/wiki/CodeReviewComments) - and the _Formatting and style_ section of Peter Bourgon's [Go: Best - Practices for Production - Environments](http://peter.bourgon.org/go-in-production/#formatting-and-style). +- [Create an ACH file for a payment and get the raw file](https://github.com/moov-io/ruby-ach-demo) -# Additional SEC (Standard Entry Class) code batch types. -SEC type's in the Batch Header record define the payment type of the following Entry Details and Addenda. The format of the records in the batch is the same between all payment types but NACHA defines different rules for the values that are held in each record field. To add support for an additional SEC type you will need to implement NACHA rules for that type. The vast majority of rules are implemented in ach.batch and then composed into Batch(SEC) for reuse. All Batch(SEC) types must be a ach.Batcher. -1. Create a milestone for the new SEC type that you want supported. -2. Add issues to that milestone to meet the NACHA rules for the batch type. -3. Create a new struct of the batch type. In the following example we will use MTE(Machine Transfer Entry) as our example. -4. The following code would be place in a new file batchMTE.go next to the existing batch types. -5. The code is stub code and the MTE type is not implemented. For concrete examples review the existing batch types in the source. - -Create a new struct and compose ach.batch +### Configuration settings -```go -type BatchMTE struct { - batch -} +| Environmental Variable | Description | Default | +|-----|-----|-----| +| `ACH_FILE_TTL` | Time to live (TTL) for `*ach.File` objects stored in the in-memory repository. | 0 = No TTL / Never delete files (Example: `240m`) | +| `LOG_FORMAT` | Format for logging lines to be written as. | Options: `json`, `plain` - Default: `plain` | +| `HTTP_BIND_ADDRESS` | Address for ACH to bind its HTTP server on. This overrides the command-line flag `-http.addr`. | Default: `:8080` | +| `HTTP_ADMIN_BIND_ADDRESS` | Address for ACH to bind its admin HTTP server on. This overrides the command-line flag `-admin.addr`. | Default: `:9090` | +| `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 | +| `HTTPS_KEY_FILE` | Filepath of a private key matching the leaf certificate from `HTTPS_CERT_FILE`. | Empty | + +### Data persistence +By design ACH **does not persist** (save) any data about the files, batches, or entry details created. The only storage occurs in memory of the process and upon restart ACH will have no files, batches, or data saved. Also, no in memory encryption of the data is performed. + + +### Go library + +This project uses [Go Modules](https://github.com/golang/go/wiki/Modules) and Go v1.14 or higher. See [Golang's install instructions](https://golang.org/doc/install) for help in setting up Go. You can download the source code and we offer [tagged and released versions](https://github.com/moov-io/ach/releases/latest) as well. We highly recommend you use a tagged release for production. + +``` +# Pull down into the Go Module cache +$ go get -u github.com/moov-io/ach + +# Show the documentation for the BatchHeader package +$ go doc github.com/moov-io/ach BatchHeader ``` -Add the ability for the new type to be created. - -```go -func NewBatchMTE(params ...BatchParam) *BatchMTE { - batch := new(BatchMTE) - batch.setControl(NewBatchControl) - if len(params) > 0 { - bh := NewBatchHeader(params[0]) - bh.StandardEntryClassCode = "MTE" - batch.SetHeader(bh) - return batch - } - bh := NewBatchHeader() - bh.StandardEntryClassCode = "MTE" - batch.SetHeader(bh) - return batch -} +The package [`github.com/moov-io/ach`](https://pkg.go.dev/github.com/moov-io/ach) offers a Go-based ACH file reader and writer. To get started, check out a specific example: + +
+Supported Standard Entry Class (SEC) codes + +| SEC Code | Description | Example | Read | Write | +|----------|---------------------------------------|------------------------------------------|-----------------------------------|------------------------------------| +| ACK | Acknowledgment Entry for CCD | [Credit](examples/testdata/ack-read.ach) | [ACK Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-AckRead) | [ACK Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-AckWrite) | +| ADV | Automated Accounting Advice | [Prenote Debit](test/ach-adv-read/adv-read.ach) | [ADV Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-AdvRead) | [ADV Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-AdvWrite) | +| ARC | Accounts Receivable Entry | [Debit](test/ach-arc-read/arc-debit.ach) | [ARC Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-ArcReadDebit) | [ARC Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-ArcWriteDebit) | +| ATX | Acknowledgment Entry for CTX | [Credit](test/ach-atx-read/atx-read.ach) | [ATX Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-AtxRead) | [ATX Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-AtxWrite) | +| BOC | Back Office Conversion | [Debit](test/ach-boc-read/boc-debit.ach) | [BOC Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-BocReadDebit) | [BOC Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-BocWriteDebit) | +| CCD | Corporate credit or debit | [Debit](test/ach-ccd-read/ccd-debit.ach) | [CCD Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CcdReadDebit) | [CCD Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CcdWriteDebit) | +| CIE | Customer-Initiated Entry | [Credit](test/ach-cie-read/cie-credit.ach) | [CIE Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CieRead) | [CIE Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CieWrite) | +| COR | Automated Notification of Change(NOC) | [NOC](test/ach-cor-read/cor-read.ach) | [COR Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CorReadCredit) | [COR Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CorWriteCredit) | +| CTX | Corporate Trade Exchange | [Debit](test/ach-ctx-read/ctx-debit.ach) | [CTX Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CtxReadDebit) | [CTX Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CtxWriteDebit) | +| DNE | Death Notification Entry | [DNE](test/ach-dne-read/dne-read.ach) | [DNE Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-DneRead) | [DNE Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-DneWrite) | +| ENR | Automatic Enrollment Entry | [ENR](test/ach-enr-read/enr-read.ach) | [ENR Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-EnrRead) | [ENR Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-EnrWrite) | +| IAT | International ACH Transactions | [Credit](test/ach-iat-read/iat-credit.ach) | [IAT Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-IatReadMixedCreditDebit) | [IAT Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-IatWriteMixedCreditDebit) | +| MTE | Machine Transfer Entry | [Credit](test/ach-mte-read/mte-read.ach) | [MTE Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-MteReadDebit) | [MTE Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-MteWriteDebit) | +| POP | Point of Purchase | [Debit](test/ach-pop-read/pop-debit.ach) | [POP Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PopReadDebit) | [POP Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PopWriteDebit) | +| POS | Point of Sale | [Debit](test/ach-pos-read/pos-debit.ach) | [POS Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PosReadDebit) | [POS Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PosWriteDebit) | +| PPD | Prearranged payment and deposits | [Debit](test/ach-ppd-read/ppd-debit.ach) [Credit](test/ach-ppd-read/ppd-credit.ach) | [PPD Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PpdReadCredit) | [PPD Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PpdWriteCredit) | +| RCK | Represented Check Entries | [Debit](test/ach-rck-read/rck-debit.ach) | [RCK Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-RckReadDebit) | [RCK Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-RckWriteDebit) | +| SHR | Shared Network Entry | [Debit](test/ach-shr-read/shr-debit.ach) | [SHR Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-ShrReadDebit) | [SHR Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-ShrWrite) | +| TEL | Telephone-Initiated Entry | [Debit](test/ach-tel-read/tel-debit.ach) | [TEL Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-TelReadDebit) | [TEL Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-TelWriteDebit) | +| TRC | Truncated Check Entry | [Debit](test/ach-trc-read/trc-debit.ach) | [TRC Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-TrcReadDebit) | [TRC Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-TrcWriteDebit) | +| TRX | Check Truncation Entries Exchange | [Debit](test/ach-trx-read/trx-debit.ach) | [TRX Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-TrxReadDebit) | [TRX Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-TrxWriteDebit) | +| WEB | Internet-initiated Entries | [Credit](test/ach-web-read/web-credit.ach) | [WEB Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-WebReadCredit) | [WEB Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-WebWriteCredit) | +| XCK | Destroyed Check Entry | [Debit](test/ach-xck-read/xck-debit.ach) | [XCK Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-XckReadDebit) | [XCK Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-XckWriteDebit) | + +
+ +
+Segment Files + +| SEC Code | Name | Example | Read | Write | +|----------|---------------------------------------|------------------------------------------|-----------------------------------|------------------------------------| +| IAT | International ACH Transactions | [Credit](test/ach-iat-read/iat-credit.ach) | [IAT Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-IatReadMixedCreditDebit) | [IAT Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-IatWriteMixedCreditDebit) | +| PPD | Prearranged payment and deposits | [Debit](test/ach-ppd-read/ppd-debit.ach) [Credit](test/ach-ppd-read/ppd-credit.ach) | [PPD Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PpdReadSegmentFile) | [PPD Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PpdWriteSegmentFile) | + +
+ +### Command line + +On each release there's an `achcli` utility released. This tool can display ACH files in a human-readable format which is easier to read than their plaintext format. It also allows masking `DFIAccountNumber` values with the `-mask` flag. + ``` +$ wget -O ./achcli https://github.com/moov-io/ach/releases/download/v1.6.3/achcli-darwin-amd64 && chmod +x ./achcli + +$ achcli test/testdata/ppd-debit.ach +Describing ACH file 'test/testdata/ppd-debit.ach' + + Origin OriginName Destination DestinationName FileCreationDate FileCreationTime + 121042882 My Bank Name 231380104 Federal Reserve Bank 190624 0000 + + BatchNumber SECCode ServiceClassCode CompanyName DiscretionaryData Identification EntryDescription DescriptiveDate + 1 PPD 225 (Debits Only) Name on Account 121042882 REG.SALARY + + TransactionCode RDFIIdentification AccountNumber Amount Name TraceNumber Category + 27 (Checking Debit) 23138010 12345678 100000000 Receiver Account Name 121042880000001 + + ServiceClassCode EntryAddendaCount EntryHash TotalDebits TotalCredits MACCode ODFIIdentification BatchNumber + 225 (Debits Only) 1 23138010 100000000 0 12104288 1 + + BatchCount BlockCount EntryAddendaCount TotalDebitAmount TotalCreditAmount + 1 1 1 100000000 0 +``` + +### In-browser ACH file parser +Using our [in-browser utility](http://oss.moov.io/ach/), you can instantly convert ACH files into JSON. Either paste in ACH file content directly or choose a file from your local machine. This tool is particularly useful if you're handling sensitive PII or want perform some quick tests, as operations are fully client-side with nothing stored in memory. + +### SDKs + +Below are some SDKs generated from the API documentation: + +- [Node SDK](https://www.npmjs.com/package/ach-node-sdk) | [GitHub](https://github.com/moov-io/ach-node-sdk) + +- TODO - OpenAPI Guide + +## Learn about ACH + +- [Official Nacha ACH Guide for Developers](https://dev-ach-guide.pantheonsite.io/) +- [Intro to ACH](https://moov-io.github.io/ach/intro/) +- [Create an ACH File](https://moov-io.github.io/ach/create-file/) +- [ACH File Structure](https://moov-io.github.io/ach/file-structure/) +- [Balanced Offset Files](https://moov-io.github.io/ach/balanced-offset/) +- [Merging Files](https://moov-io.github.io/ach/merging-files/) + +## FAQ +
+Is there an in-browser tool for converting ACH files into JSON? +Yes! You can find our browser utility at http://oss.moov.io/ach/. +
+
+Is my data being saved somewhere? +No, we do not save any data related to files, batch, or entry details. All processing is done in-memory. +
+
+What ACH transaction types are supported? +We support generating and parsing all Standard Entry Class (SEC) codes. +
+
+Where can I find the official Nacha Operating Rules? +You can purchase the most recent Nacha Operating Rules and Guidelines resource directly from their webstore. Additionally, Nacha has published a free ACH guide for developers. +
+ +## Getting help + +If you have ACH-specific questions, NACHA (National Automated Clearing House Association) has their [complete specification](docs/2013-Corporate-Rules-and-Guidelines.pdf) for all file formats and message types. + + channel | info + ------- | ------- + [Project Documentation](https://moov-io.github.io/ach/) | Our project documentation available online. +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. +[GitHub Issue](https://github.com/moov-io/ach/issues/new) | If you are able to reproduce a problem please open a GitHub Issue under the specific project that caused the error. +[moov-io slack](https://slack.moov.io/) | Join our slack channel to have an interactive discussion about the development of the project. + +## Supported and tested platforms + +- 64-bit Linux (Ubuntu, Debian), macOS, and Windows +- Raspberry Pi + +Note: 32-bit platforms have known issues and are not supported. + +## Contributing + +Yes please! Please review our [Contributing guide](CONTRIBUTING.md) and [Code of Conduct](CODE_OF_CONDUCT.md) to get started! Check out our [issues for first time contributors](https://github.com/moov-io/ach/contribute) for something to help out with. + +This project uses [Go Modules](https://github.com/golang/go/wiki/Modules) and uses Go v1.14 or higher. 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/ach/releases/latest) as well. We highly recommend you use a tagged release for production. + +### Releasing + +To make a release of ach simply open a pull request with `CHANGELOG.md` and `version.go` updated with the next version number and details. You'll also need to push the tag (i.e. `git push origin v1.0.0`) to origin in order for CI to make the release. + +### Testing + +We maintain a comprehensive suite of unit tests and recommend table-driven testing when a particular function warrants several very similar test cases. To run all test files in the current directory, use `go test`. Current overall coverage can be found on [Codecov](https://app.codecov.io/gh/moov-io/ach/). + +### Fuzzing + +We currently run fuzzing over ACH in the form of a [`moov/achfuzz`](https://hub.docker.com/r/moov/achfuzz) Docker image. You can [read more](./test/fuzz-reader/README.md) or run the image and report crasher examples to [`security@moov.io`](mailto:security@moov.io). Thanks! + + +## Related projects +As part of Moov's initiative to offer open source fintech infrastructure, we have a large collection of active projects you may find useful: + +- [Moov Watchman](https://github.com/moov-io/watchman) offers search functions over numerous trade sanction lists from the United States and European Union. + +- [Moov Fed](https://github.com/moov-io/fed) implements utility services for searching the United States Federal Reserve System such as ABA routing numbers, financial institution name lookup, and FedACH and Fedwire routing information. + +- [Moov Wire](https://github.com/moov-io/wire) implements an interface to write files for the Fedwire Funds Service, a real-time gross settlement funds transfer system operated by the United States Federal Reserve Banks. + +- [Moov Image Cash Letter](https://github.com/moov-io/imagecashletter) implements Image Cash Letter (ICL) files used for Check21, X.9 or check truncation files for exchange and remote deposit in the U.S. + +- [Moov Metro 2](https://github.com/moov-io/metro2) provides a way to easily read, create, and validate Metro 2 format, which is used for consumer credit history reporting by the United States credit bureaus. -To support the Batcher interface you must add the following functions that are not implemented in ach.batch. -* Validate() error -* Create() error - -Validate is designed to enforce the NACHA rules for the MTE payment type. Validate is run after a batch of this type is read from a file. If you are creating a batch from code call validate afterwards. - -```go -// Validate checks valid NACHA batch rules. Assumes properly parsed records. -func (batch *BatchMTE) Validate() error { - // basic verification of the batch before we validate specific rules. - if err := batch.verify(); err != nil { - return err - } - // Add configuration based validation for this type. - // ... batch.isAddendaCount(1) - // Add type specific validation. - // ... - return nil -} -``` -Create takes the Batch Header and Entry details and creates the proper sequence number and batch control. If additional logic specific to the SEC type is required it building a batch file it should be added here. - -```go -// Create takes Batch Header and Entries and builds a valid batch -func (batch *BatchMTE) Create() error { - // generates sequence numbers and batch control - if err := batch.build(); err != nil { - return err - } - // Additional steps specific to batch type - // ... - - if err := batch.Validate(); err != nil { - return err - } - return nil -} -``` - -Finally add the batch type to the NewBatch factory in batch.go. - -```go -//... -case "MTE": - return NewBatchMTE(bp), nil -//... -``` - -Pull request require a batchMTE_test.go file that covers the logic of the type. - -## References -* [Wikipeda: Automated Clearing House](http://en.wikipedia.org/wiki/Automated_Clearing_House) -* [Nacha ACH Network: How it Works](https://www.nacha.org/ach-network) -* [Federal ACH Directory](https://www.frbservices.org/EPaymentsDirectory/search.html) - -## Format Specification -* [NACHA ACH File Formatting](https://www.nacha.org/system/files/resources/AAP201%20-%20ACH%20File%20Formatting.pdf) -* [PNC ACH File Specification](http://content.pncmc.com/live/pnc/corporate/treasury-management/ach-conversion/ACH-File-Specifications.pdf) -* [Thomson Reuters ACH FIle Structure](http://cs.thomsonreuters.com/ua/acct_pr/acs/cs_us_en/pr/dd/ach_file_structure_and_content.htm) -* [Gusto: How ACH Works: A developer perspective](http://engineering.gusto.com/how-ach-works-a-developer-perspective-part-4/) - -![ACH File Layout](https://github.com/moov-io/ach/blob/master/documentation/ach_file_structure_shg.gif) - -## Insperation -* [ACH:Builder - Tools for Building ACH](http://search.cpan.org/~tkeefer/ACH-Builder-0.03/lib/ACH/Builder.pm) -* [mosscode / ach](https://github.com/mosscode/ach) -* [Helper for building ACH files in Ruby](https://github.com/jm81/ach) -* [Glenselle / nACH2](https://github.com/glenselle/nACH2) ## License -Apache License 2.0 See [LICENSE](LICENSE) for details. + +Apache License 2.0 - See [LICENSE](LICENSE) for details. diff --git a/addenda.go b/addenda.go deleted file mode 100644 index 661877f35..000000000 --- a/addenda.go +++ /dev/null @@ -1,134 +0,0 @@ -package ach - -import ( - "flag" - "fmt" - "strings" -) - -func init() { - flag.Lookup("alsologtostderr").Value.Set("true") -} - -// Addenda provides business transaction information in a machine -// readable format. It is usually formatted according to ANSI, ASC, X12 Standard -type Addenda struct { - // RecordType defines the type of record in the block. entryAddendaPos 7 - recordType string - // TypeCode Addenda types code '05' - TypeCode string - // PaymentRelatedInformation - PaymentRelatedInformation string - // SequenceNumber is consecutively assigned to each Addenda Record following - // an Entry Detail Record. The first addenda sequence number must always - // be a "1". - SequenceNumber int - // EntryDetailSequenceNumber contains the ascending sequence number section of the Entry - // Detail or Corporate Entry Detail Record's trace number This number is - // the same as the last seven digits of the trace number of the related - // Entry Detail Record or Corporate Entry Detail Record. - EntryDetailSequenceNumber int - // validator is composed for data validation - validator - // converters is composed for ACH to GoLang Converters - converters -} - -// AddendaParam is the minimal fields required to make a ach addenda -type AddendaParam struct { - PaymentRelatedInfo string `json:"payment_related_info"` -} - -// NewAddenda returns a new Addenda with default values for none exported fields -func NewAddenda(params ...AddendaParam) Addenda { - addenda := Addenda{ - recordType: "7", - TypeCode: "05", - SequenceNumber: 1, - EntryDetailSequenceNumber: 1, - } - - if len(params) > 0 { - addenda.PaymentRelatedInformation = params[0].PaymentRelatedInfo - return addenda - } - return addenda -} - -// Parse takes the input record string and parses the Addenda values -func (addenda *Addenda) Parse(record string) { - // 1-1 Always "7" - addenda.recordType = "7" - // 2-3 Defines the specific explanation and format for the addenda information contained in the same record - addenda.TypeCode = record[1:3] - // 4-83 Based on the information entered (04-83) 80 alphanumeric - addenda.PaymentRelatedInformation = strings.TrimSpace(record[3:83]) - // 84-87 SequenceNumber is consecutively assigned to each Addenda Record following - // an Entry Detail Record - addenda.SequenceNumber = addenda.parseNumField(record[83:87]) - // 88-94 Contains the last seven digits of the number entered in the Trace Number field in the corresponding Entry Detail Record - addenda.EntryDetailSequenceNumber = addenda.parseNumField(record[87:94]) -} - -// String writes the Addenda struct to a 94 character string. -func (addenda *Addenda) String() string { - return fmt.Sprintf("%v%v%v%v%v", - addenda.recordType, - addenda.TypeCode, - addenda.PaymentRelatedInformationField(), - addenda.SequenceNumberField(), - addenda.EntryDetailSequenceNumberField()) -} - -// Validate performs NACHA format rule checks on the record and returns an error if not Validated -// The first error encountered is returned and stops that parsing. -func (addenda *Addenda) Validate() error { - if err := addenda.fieldInclusion(); err != nil { - return err - } - if addenda.recordType != "7" { - msg := fmt.Sprintf(msgRecordType, 7) - return &FieldError{FieldName: "recordType", Value: addenda.recordType, Msg: msg} - } - if err := addenda.isTypeCode(addenda.TypeCode); err != nil { - return &FieldError{FieldName: "TypeCode", Value: addenda.TypeCode, Msg: err.Error()} - } - if err := addenda.isAlphanumeric(addenda.PaymentRelatedInformation); err != nil { - return &FieldError{FieldName: "PaymentRelatedInformation", Value: addenda.PaymentRelatedInformation, Msg: err.Error()} - } - - return nil -} - -// fieldInclusion validate mandatory fields are not default values. If fields are -// invalid the ACH transfer will be returned. -func (addenda *Addenda) fieldInclusion() error { - if addenda.recordType == "" { - return &FieldError{FieldName: "recordType", Value: addenda.recordType, Msg: msgFieldInclusion} - } - if addenda.TypeCode == "" { - return &FieldError{FieldName: "TypeCode", Value: addenda.TypeCode, Msg: msgFieldInclusion} - } - if addenda.SequenceNumber == 0 { - return &FieldError{FieldName: "SequenceNumber", Value: addenda.SequenceNumberField(), Msg: msgFieldInclusion} - } - if addenda.EntryDetailSequenceNumber == 0 { - return &FieldError{FieldName: "EntryDetailSequenceNumber", Value: addenda.EntryDetailSequenceNumberField(), Msg: msgFieldInclusion} - } - return nil -} - -// PaymentRelatedInformationField returns a zero padded PaymentRelatedInformation string -func (addenda *Addenda) PaymentRelatedInformationField() string { - return addenda.alphaField(addenda.PaymentRelatedInformation, 80) -} - -// SequenceNumberField returns a zero padded SequenceNumber string -func (addenda *Addenda) SequenceNumberField() string { - return addenda.numericField(addenda.SequenceNumber, 4) -} - -// EntryDetailSequenceNumberField returns a zero padded EntryDetailSequenceNumber string -func (addenda *Addenda) EntryDetailSequenceNumberField() string { - return addenda.numericField(addenda.EntryDetailSequenceNumber, 7) -} diff --git a/addenda02.go b/addenda02.go new file mode 100644 index 000000000..a3a718376 --- /dev/null +++ b/addenda02.go @@ -0,0 +1,256 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "unicode/utf8" +) + +// Addenda02 is a Addendumer addenda which provides business transaction information for Addenda Type +// Code 02 in a machine readable format. It is usually formatted according to ANSI, ASC, X12 Standard. +// It is used for following StandardEntryClassCode: MTE, POS, and SHR. +type Addenda02 struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // TypeCode Addenda02 type code '02' + TypeCode string `json:"typeCode"` + // ReferenceInformationOne may be used for additional reference numbers, identification numbers, + // or codes that the merchant needs to identify the particular transaction or customer. + ReferenceInformationOne string `json:"referenceInformationOne,omitempty"` + // ReferenceInformationTwo may be used for additional reference numbers, identification numbers, + // or codes that the merchant needs to identify the particular transaction or customer. + ReferenceInformationTwo string `json:"referenceInformationTwo,omitempty"` + // TerminalIdentificationCode identifies an Electronic terminal with a unique code that allows + // a terminal owner and/or switching network to identify the terminal at which an Entry originated. + TerminalIdentificationCode string `json:"terminalIdentificationCode"` + // TransactionSerialNumber is assigned by the terminal at the time the transaction is originated. The + // number, with the Terminal Identification Code, serves as an audit trail for the transaction and is + // usually assigned in ascending sequence. + TransactionSerialNumber string `json:"transactionSerialNumber"` + // TransactionDate expressed MMDD identifies the date on which the transaction occurred. + TransactionDate string `json:"transactionDate"` + // AuthorizationCodeOrExpireDate indicates the code that a card authorization center has + // furnished to the merchant. + AuthorizationCodeOrExpireDate string `json:"authorizationCodeOrExpireDate,omitempty"` + // Terminal Location identifies the specific location of a terminal (i.e., street names of an + // intersection, address, etc.) in accordance with the requirements of Regulation E. + TerminalLocation string `json:"terminalLocation"` + // TerminalCity Identifies the city in which the electronic terminal is located. + TerminalCity string `json:"terminalCity"` + // TerminalState Identifies the state in which the electronic terminal is located + TerminalState string `json:"terminalState"` + // TraceNumber Standard Entry Detail Trace Number + // + // Use TraceNumberField for a properly formatted string representation. + TraceNumber string `json:"traceNumber,omitempty"` + // validator is composed for data validation + validator + // converters is composed for ACH to GoLang Converters + converters +} + +// NewAddenda02 returns a new Addenda02 with default values for none exported fields +func NewAddenda02() *Addenda02 { + addenda02 := new(Addenda02) + addenda02.TypeCode = "02" + return addenda02 +} + +// Parse takes the input record string and parses the Addenda02 values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. +func (addenda02 *Addenda02) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + // 1-1 Always 7 + // 2-3 Always 02 + addenda02.TypeCode = record[1:3] + // 4-10 Based on the information entered (04-10) 7 alphanumeric + addenda02.ReferenceInformationOne = strings.TrimSpace(record[3:10]) + // 11-13 Based on the information entered (11-13) 3 alphanumeric + addenda02.ReferenceInformationTwo = strings.TrimSpace(record[10:13]) + // 14-19 + addenda02.TerminalIdentificationCode = strings.TrimSpace(record[13:19]) + // 20-25 + addenda02.TransactionSerialNumber = strings.TrimSpace(record[19:25]) + // 26-29 + addenda02.TransactionDate = strings.TrimSpace(record[25:29]) + // 30-35 + addenda02.AuthorizationCodeOrExpireDate = strings.TrimSpace(record[29:35]) + // 36-62 + addenda02.TerminalLocation = strings.TrimSpace(record[35:62]) + // 63-77 + addenda02.TerminalCity = strings.TrimSpace(record[62:77]) + // 78-79 + addenda02.TerminalState = strings.TrimSpace(record[77:79]) + // 80-94 + addenda02.TraceNumber = strings.TrimSpace(record[79:94]) +} + +// String writes the Addenda02 struct to a 94 character string. +func (addenda02 *Addenda02) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(entryAddendaPos) + buf.WriteString(addenda02.TypeCode) + buf.WriteString(addenda02.ReferenceInformationOneField()) + buf.WriteString(addenda02.ReferenceInformationTwoField()) + buf.WriteString(addenda02.TerminalIdentificationCodeField()) + buf.WriteString(addenda02.TransactionSerialNumberField()) + buf.WriteString(addenda02.TransactionDateField()) + buf.WriteString(addenda02.AuthorizationCodeOrExpireDateField()) + buf.WriteString(addenda02.TerminalLocationField()) + buf.WriteString(addenda02.TerminalCityField()) + buf.WriteString(addenda02.TerminalStateField()) + buf.WriteString(addenda02.TraceNumberField()) + return buf.String() +} + +// Validate performs NACHA format rule checks on the record and returns an error if not Validated +// The first error encountered is returned and stops that parsing. +func (addenda02 *Addenda02) Validate() error { + if err := addenda02.fieldInclusion(); err != nil { + return err + } + if err := addenda02.isTypeCode(addenda02.TypeCode); err != nil { + return fieldError("TypeCode", err, addenda02.TypeCode) + } + // Type Code must be 02 + if addenda02.TypeCode != "02" { + return fieldError("TypeCode", ErrAddendaTypeCode, addenda02.TypeCode) + } + if err := addenda02.isAlphanumeric(addenda02.ReferenceInformationOne); err != nil { + return fieldError("ReferenceInformationOne", err, addenda02.ReferenceInformationOne) + } + if err := addenda02.isAlphanumeric(addenda02.ReferenceInformationTwo); err != nil { + return fieldError("ReferenceInformationTwo", err, addenda02.ReferenceInformationTwo) + } + if err := addenda02.isAlphanumeric(addenda02.TerminalIdentificationCode); err != nil { + return fieldError("TerminalIdentificationCode", err, addenda02.TerminalIdentificationCode) + } + if err := addenda02.isAlphanumeric(addenda02.TransactionSerialNumber); err != nil { + return fieldError("TransactionSerialNumber", err, addenda02.TransactionSerialNumber) + } + + // TransactionDate Addenda02 ACH File format is MMDD. Validate MM is 01-12 and day for the + // month 01-31 depending on month. + mm := addenda02.parseStringField(addenda02.TransactionDateField()[0:2]) + dd := addenda02.parseStringField(addenda02.TransactionDateField()[2:4]) + if err := addenda02.isMonth(mm); err != nil { + return fieldError("TransactionDate", ErrValidMonth, mm) + } + if err := addenda02.isDay(mm, dd); err != nil { + return fieldError("TransactionDate", ErrValidDay, mm) + } + + if err := addenda02.isAlphanumeric(addenda02.AuthorizationCodeOrExpireDate); err != nil { + return fieldError("AuthorizationCodeOrExpireDate", err, addenda02.AuthorizationCodeOrExpireDate) + } + if err := addenda02.isAlphanumeric(addenda02.TerminalLocation); err != nil { + return fieldError("TerminalLocation", err, addenda02.TerminalLocation) + } + if err := addenda02.isAlphanumeric(addenda02.TerminalCity); err != nil { + return fieldError("TerminalCity", err, addenda02.TerminalCity) + } + if err := addenda02.isAlphanumeric(addenda02.TerminalState); err != nil { + return fieldError("TerminalState", err, addenda02.TerminalState) + } + return nil +} + +// fieldInclusion validate mandatory fields are not default values and required fields are defined. If fields are +// invalid the ACH transfer will be returned. + +func (addenda02 *Addenda02) fieldInclusion() error { + if addenda02.TypeCode == "" { + return fieldError("TypeCode", ErrConstructor, addenda02.TypeCode) + } + // Required Fields + if addenda02.TerminalIdentificationCode == "" { + return fieldError("TerminalIdentificationCode", ErrFieldRequired, addenda02.TerminalIdentificationCode) + } + if addenda02.TransactionSerialNumber == "" { + return fieldError("TransactionSerialNumber", ErrFieldRequired, addenda02.TransactionSerialNumber) + } + if addenda02.TransactionDate == "" { + return fieldError("TransactionDate", ErrFieldRequired, addenda02.TransactionDate) + } + if addenda02.TerminalLocation == "" { + return fieldError("TerminalLocation", ErrFieldRequired, addenda02.TerminalLocation) + } + if addenda02.TerminalCity == "" { + return fieldError("TerminalCity", ErrFieldRequired, addenda02.TerminalCity) + } + if addenda02.TerminalState == "" { + return fieldError("TerminalState", ErrFieldRequired, addenda02.TerminalState) + } + return nil +} + +// ReferenceInformationOneField returns a space padded ReferenceInformationOne string +func (addenda02 *Addenda02) ReferenceInformationOneField() string { + return addenda02.alphaField(addenda02.ReferenceInformationOne, 7) +} + +// ReferenceInformationTwoField returns a space padded ReferenceInformationTwo string +func (addenda02 *Addenda02) ReferenceInformationTwoField() string { + return addenda02.alphaField(addenda02.ReferenceInformationOne, 3) +} + +// TerminalIdentificationCodeField returns a space padded TerminalIdentificationCode string +func (addenda02 *Addenda02) TerminalIdentificationCodeField() string { + return addenda02.alphaField(addenda02.TerminalIdentificationCode, 6) +} + +// TransactionSerialNumberField returns a zero padded TransactionSerialNumber string +func (addenda02 *Addenda02) TransactionSerialNumberField() string { + return addenda02.alphaField(addenda02.TransactionSerialNumber, 6) +} + +// TransactionDateField returns TransactionDate MMDD string +func (addenda02 *Addenda02) TransactionDateField() string { + return addenda02.alphaField(addenda02.TransactionDate, 4) +} + +// AuthorizationCodeOrExpireDateField returns a space padded AuthorizationCodeOrExpireDate string +func (addenda02 *Addenda02) AuthorizationCodeOrExpireDateField() string { + return addenda02.alphaField(addenda02.AuthorizationCodeOrExpireDate, 6) +} + +// TerminalLocationField returns a space padded TerminalLocation string +func (addenda02 *Addenda02) TerminalLocationField() string { + return addenda02.alphaField(addenda02.TerminalLocation, 27) +} + +// TerminalCityField returns a space padded TerminalCity string +func (addenda02 *Addenda02) TerminalCityField() string { + return addenda02.alphaField(addenda02.TerminalCity, 15) +} + +// TerminalStateField returns a space padded TerminalState string +func (addenda02 *Addenda02) TerminalStateField() string { + return addenda02.alphaField(addenda02.TerminalState, 2) +} + +// TraceNumberField returns a space padded TraceNumber string +func (addenda02 *Addenda02) TraceNumberField() string { + return addenda02.stringField(addenda02.TraceNumber, 15) +} diff --git a/addenda02_test.go b/addenda02_test.go new file mode 100644 index 000000000..5c28843ee --- /dev/null +++ b/addenda02_test.go @@ -0,0 +1,617 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockAddenda02 creates a mock Addenda02 record +func mockAddenda02() *Addenda02 { + addenda02 := NewAddenda02() + addenda02.ReferenceInformationOne = "REFONEA" + addenda02.ReferenceInformationTwo = "REF" + addenda02.TerminalIdentificationCode = "TERM02" + addenda02.TransactionSerialNumber = "100049" + addenda02.TransactionDate = "0612" + addenda02.AuthorizationCodeOrExpireDate = "123456" + addenda02.TerminalLocation = "Target Store 0049" + addenda02.TerminalCity = "PHILADELPHIA" + addenda02.TerminalState = "PA" + addenda02.TraceNumber = "121042880000123" + return addenda02 +} + +// TestMockAddenda02 validates mockAddenda02 +func TestMockAddenda02(t *testing.T) { + addenda02 := mockAddenda02() + if err := addenda02.Validate(); err != nil { + t.Error("mockAddenda02 does not validate and will break other tests") + } +} + +// testAddenda02ValidTypeCode validates Addenda02 TypeCode +func testAddenda02ValidTypeCode(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TypeCode = "65" + err := addenda02.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda02ValidTypeCode tests validating Addenda02 TypeCode +func TestAddenda02ValidTypeCode(t *testing.T) { + testAddenda02ValidTypeCode(t) +} + +// BenchmarkAddenda02ValidTypeCode benchmarks validating Addenda02 TypeCode +func BenchmarkAddenda02ValidTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda02ValidTypeCode(b) + } +} + +// testAddenda02TypeCode02 TypeCode is 02 if TypeCode is a valid TypeCode +func testAddenda02TypeCode02(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TypeCode = "05" + err := addenda02.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda02TypeCode02 tests TypeCode is 02 if TypeCode is a valid TypeCode +func TestAddenda02TypeCode02(t *testing.T) { + testAddenda02TypeCode02(t) +} + +// BenchmarkAddenda02TypeCode02 benchmarks TypeCode is 02 if TypeCode is a valid TypeCode +func BenchmarkAddenda02TypeCode02(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda02TypeCode02(b) + } +} + +// testAddenda02FieldInclusionTypeCode validates TypeCode fieldInclusion +func testAddenda02FieldInclusionTypeCode(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TypeCode = "" + err := addenda02.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda02FieldInclusionTypeCode tests validating TypeCode fieldInclusion +func TestAddenda02FieldInclusionTypeCode(t *testing.T) { + testAddenda02FieldInclusionTypeCode(t) +} + +// BenchmarkAddenda02FieldInclusionTypeCode benchmarks validating TypeCode fieldInclusion +func BenchmarkAddenda02FieldInclusionTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda02FieldInclusionTypeCode(b) + } +} + +// testAddenda02TerminalIdentificationCode validates TerminalIdentificationCode is required +func testAddenda02TerminalIdentificationCode(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TerminalIdentificationCode = "" + err := addenda02.Validate() + if !base.Match(err, ErrFieldRequired) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda02TerminalIdentificationCode tests validating TerminalIdentificationCode is required +func TestAddenda02TerminalIdentificationCode(t *testing.T) { + testAddenda02TerminalIdentificationCode(t) +} + +// BenchmarkAddenda02TerminalIdentificationCode benchmarks validating TerminalIdentificationCode is required +func BenchmarkAddenda02TerminalIdentificationCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda02TerminalIdentificationCode(b) + } +} + +// testAddenda02TransactionSerialNumber validates TransactionSerialNumber is required +func testAddenda02TransactionSerialNumber(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TransactionSerialNumber = "" + err := addenda02.Validate() + if !base.Match(err, ErrFieldRequired) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda02TransactionSerialNumber tests validating TransactionSerialNumber is required +func TestAddenda02TransactionSerialNumber(t *testing.T) { + testAddenda02TransactionSerialNumber(t) +} + +// BenchmarkAddenda02TransactionSerialNumber benchmarks validating TransactionSerialNumber is required +func BenchmarkAddenda02TransactionSerialNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda02TransactionSerialNumber(b) + } +} + +// testAddenda02TransactionDate validates TransactionDate is required +func testAddenda02TransactionDate(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TransactionDate = "" + err := addenda02.Validate() + if !base.Match(err, ErrFieldRequired) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda02TransactionDate tests validating TransactionDate is required +func TestAddenda02TransactionDate(t *testing.T) { + testAddenda02TransactionDate(t) +} + +// BenchmarkAddenda02TransactionDate benchmarks validating TransactionDate is required +func BenchmarkAddenda02TransactionDate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda02TransactionDate(b) + } +} + +// testAddenda02TerminalLocation validates TerminalLocation is required +func testAddenda02TerminalLocation(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TerminalLocation = "" + err := addenda02.Validate() + if !base.Match(err, ErrFieldRequired) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda02TerminalLocation tests validating TerminalLocation is required +func TestAddenda02TerminalLocation(t *testing.T) { + testAddenda02TerminalLocation(t) +} + +// BenchmarkAddenda02TerminalLocation benchmarks validating TerminalLocation is required +func BenchmarkAddenda02TerminalLocation(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda02TerminalLocation(b) + } +} + +// testAddenda02TerminalCity validates TerminalCity is required +func testAddenda02TerminalCity(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TerminalCity = "" + err := addenda02.Validate() + if !base.Match(err, ErrFieldRequired) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda02TerminalCity tests validating TerminalCity is required +func TestAddenda02TerminalCity(t *testing.T) { + testAddenda02TerminalCity(t) +} + +// BenchmarkAddenda02TerminalCity benchmarks validating TerminalCity is required +func BenchmarkAddenda02TerminalCity(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda02TerminalCity(b) + } +} + +// testAddenda02TerminalState validates TerminalState is required +func testAddenda02TerminalState(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TerminalState = "" + err := addenda02.Validate() + if !base.Match(err, ErrFieldRequired) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda02TerminalState tests validating TerminalState is required +func TestAddenda02TerminalState(t *testing.T) { + testAddenda02TerminalState(t) +} + +// BenchmarkAddenda02TerminalState benchmarks validating TerminalState is required +func BenchmarkAddenda02TerminalState(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda02TerminalState(b) + } +} + +// TestAddenda02String validates that a known parsed Addenda02 record can be return to a string of the same value +func testAddenda02String(t testing.TB) { + addenda02 := NewAddenda02() + var line = "702REFONEAREFTERM021000490612123456Target Store 0049 PHILADELPHIA PA121042880000123" + addenda02.Parse(line) + if addenda02.String() != line { + t.Errorf("Strings do not match") + } + if addenda02.TypeCode != "02" { + t.Errorf("TypeCode Expected 02 got: %v", addenda02.TypeCode) + } +} + +// TestAddenda02String tests validating that a known parsed Addenda02 record can be return to a string of the same value +func TestAddenda02String(t *testing.T) { + testAddenda02String(t) +} + +// BenchmarkAddenda02String benchmarks validating that a known parsed Addenda02 record can be return to a string of the same value +func BenchmarkAddenda02String(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda02String(b) + } +} + +// testAddenda02TransactionDateMonth validates the month is valid for transactionDate +func testAddenda02TransactionDateMonth(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TransactionDate = "1306" + err := addenda02.Validate() + if !base.Match(err, ErrValidMonth) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda02TransactionDateMonth tests validating the month is valid for transactionDate +func TestAddenda02TransactionDateMonth(t *testing.T) { + testAddenda02TransactionDateMonth(t) +} + +// BenchmarkAddenda02TransactionDateMonth benchmarks validating the month is valid for transactionDate +func BenchmarkAddenda02TransactionDateMonth(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda02TransactionDateMonth(b) + } +} + +// testAddenda02TransactionDateDay validates the day is valid for transactionDate +func testAddenda02TransactionDateDay(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TransactionDate = "0205" + err := addenda02.Validate() + // no error expected + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda02TransactionDateDay tests validating the day is valid for transactionDate +func TestAddenda02TransactionDateDay(t *testing.T) { + testAddenda02TransactionDateDay(t) +} + +// BenchmarkAddenda02TransactionDateDay benchmarks validating the day is valid for transactionDate +func BenchmarkAddenda02TransactionDateDay(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda02TransactionDateDay(b) + } +} + +// testAddenda02TransactionDateFeb validates the day is valid for transactionDate +func testAddenda02TransactionDateFeb(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TransactionDate = "0230" + err := addenda02.Validate() + if !base.Match(err, ErrValidDay) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda02TransactionDateFeb tests validating the day is valid for transactionDate +func TestAddenda02TransactionDateFeb(t *testing.T) { + testAddenda02TransactionDateFeb(t) +} + +// BenchmarkAddenda02TransactionDateFeb benchmarks validating the day is valid for transactionDate +func BenchmarkAddenda02TransactionDateFeb(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda02TransactionDateFeb(b) + } +} + +// testAddenda02TransactionDate30Day validates the day is valid for transactionDate +func testAddenda02TransactionDate30Day(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TransactionDate = "0630" + err := addenda02.Validate() + // no error expected + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda02TransactionDate30Day tests validating the day is valid for transactionDate +func TestAddenda02TransactionDate30Day(t *testing.T) { + testAddenda02TransactionDate30Day(t) +} + +// BenchmarkAddenda02TransactionDate30Day benchmarks validating the day is valid for transactionDate +func BenchmarkAddenda02TransactionDate30Day(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda02TransactionDate30Day(b) + } +} + +// testAddenda02TransactionDate31Day validates the day is valid for transactionDate +func testAddenda02TransactionDate31Day(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TransactionDate = "0131" + err := addenda02.Validate() + // no error expected + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda02TransactionDate31Day tests validating the day is valid for transactionDate +func TestAddenda02TransactionDate31Day(t *testing.T) { + testAddenda02TransactionDate31Day(t) +} + +// BenchmarkAddenda02TransactionDate31Day benchmarks validating the day is valid for transactionDate +func BenchmarkAddenda02TransactionDate31Day(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda02TransactionDate31Day(b) + } +} + +// testAddenda02TransactionDateInvalidDay validates the day is invalid for transactionDate +func testAddenda02TransactionDateInvalidDay(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TransactionDate = "1039" + err := addenda02.Validate() + if !base.Match(err, ErrValidDay) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda02TransactionDateInvalidDay tests validating the day is invalid for transactionDate +func TestAddenda02TransactionDateInvalidDay(t *testing.T) { + testAddenda02TransactionDateInvalidDay(t) +} + +// BenchmarkAddenda02TransactionDateInvalidDay benchmarks validating the day is invalid for transactionDate +func BenchmarkAddenda02TransactionDateInvalidDay(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda02TransactionDateInvalidDay(b) + } +} + +// testReferenceInformationOneAlphaNumeric validates ReferenceInformationOne is alphanumeric +func testReferenceInformationOneAlphaNumeric(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.ReferenceInformationOne = "®" + err := addenda02.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestReferenceInformationOneAlphaNumeric tests validating ReferenceInformationOne is alphanumeric +func TestReferenceInformationOneAlphaNumeric(t *testing.T) { + testReferenceInformationOneAlphaNumeric(t) +} + +// BenchmarkReferenceInformationOneAlphaNumeric benchmarks validating ReferenceInformationOne is alphanumeric +func BenchmarkReferenceInformationOneAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testReferenceInformationOneAlphaNumeric(b) + } +} + +// testReferenceInformationTwoAlphaNumeric validates ReferenceInformationTwo is alphanumeric +func testReferenceInformationTwoAlphaNumeric(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.ReferenceInformationTwo = "®" + err := addenda02.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestReferenceInformationTwoAlphaNumeric tests validating ReferenceInformationTwo is alphanumeric +func TestReferenceInformationTwoAlphaNumeric(t *testing.T) { + testReferenceInformationTwoAlphaNumeric(t) +} + +// BenchmarkReferenceInformationTwoAlphaNumeric benchmarks validating ReferenceInformationTwo is alphanumeric +func BenchmarkReferenceInformationTwoAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testReferenceInformationTwoAlphaNumeric(b) + } +} + +// testTerminalIdentificationCodeAlphaNumeric validates TerminalIdentificationCode is alphanumeric +func testTerminalIdentificationCodeAlphaNumeric(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TerminalIdentificationCode = "®" + err := addenda02.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestTerminalIdentificationCodeAlphaNumeric tests validating TerminalIdentificationCode is alphanumeric +func TestTerminalIdentificationCodeAlphaNumeric(t *testing.T) { + testTerminalIdentificationCodeAlphaNumeric(t) +} + +// BenchmarkTerminalIdentificationCodeAlphaNumeric benchmarks validating TerminalIdentificationCode is alphanumeric +func BenchmarkTerminalIdentificationCodeAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testTerminalIdentificationCodeAlphaNumeric(b) + } +} + +// testTransactionSerialNumberAlphaNumeric validates TransactionSerialNumber is alphanumeric +func testTransactionSerialNumberAlphaNumeric(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TransactionSerialNumber = "®" + err := addenda02.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestTransactionSerialNumberAlphaNumeric tests validating TransactionSerialNumber is alphanumeric +func TestTransactionSerialNumberAlphaNumeric(t *testing.T) { + testTransactionSerialNumberAlphaNumeric(t) +} + +// BenchmarkTransactionSerialNumberAlphaNumeric benchmarks validating TransactionSerialNumber is alphanumeric +func BenchmarkTransactionSerialNumberAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testTransactionSerialNumberAlphaNumeric(b) + } +} + +// testAuthorizationCodeOrExpireDateAlphaNumeric validates AuthorizationCodeOrExpireDate is alphanumeric +func testAuthorizationCodeOrExpireDateAlphaNumeric(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.AuthorizationCodeOrExpireDate = "®" + err := addenda02.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAuthorizationCodeOrExpireDateAlphaNumeric tests validating AuthorizationCodeOrExpireDate is alphanumeric +func TestAuthorizationCodeOrExpireDateAlphaNumeric(t *testing.T) { + testAuthorizationCodeOrExpireDateAlphaNumeric(t) +} + +// BenchmarkAuthorizationCodeOrExpireDateAlphaNumeric benchmarks validating AuthorizationCodeOrExpireDate is alphanumeric +func BenchmarkAuthorizationCodeOrExpireDateAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAuthorizationCodeOrExpireDateAlphaNumeric(b) + } +} + +// testTerminalLocationAlphaNumeric validates TerminalLocation is alphanumeric +func testTerminalLocationAlphaNumeric(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TerminalLocation = "®" + err := addenda02.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestTerminalLocationAlphaNumeric tests validating TerminalLocation is alphanumeric +func TestTerminalLocationAlphaNumeric(t *testing.T) { + testTerminalLocationAlphaNumeric(t) +} + +// BenchmarkTerminalLocationAlphaNumeric benchmarks validating TerminalLocation is alphanumeric +func BenchmarkTerminalLocationAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testTerminalLocationAlphaNumeric(b) + } +} + +// testTerminalCityAlphaNumeric validates TerminalCity is alphanumeric +func testTerminalCityAlphaNumeric(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TerminalCity = "®" + err := addenda02.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestTerminalCityAlphaNumeric tests validating TerminalCity is alphanumeric +func TestTerminalCityAlphaNumeric(t *testing.T) { + testTerminalCityAlphaNumeric(t) +} + +// BenchmarkTerminalCityAlphaNumeric benchmarks validating TerminalCity is alphanumeric +func BenchmarkTerminalCityAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testTerminalCityAlphaNumeric(b) + } +} + +// testTerminalStateAlphaNumeric validates TerminalState is alphanumeric +func testTerminalStateAlphaNumeric(t testing.TB) { + addenda02 := mockAddenda02() + addenda02.TerminalState = "®" + err := addenda02.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestTerminalStateAlphaNumeric tests validating TerminalState is alphanumeric +func TestTerminalStateAlphaNumeric(t *testing.T) { + testTerminalStateAlphaNumeric(t) +} + +// BenchmarkTerminalStateAlphaNumeric benchmarks validating TerminalState is alphanumeric +func BenchmarkTerminalStateAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testTerminalStateAlphaNumeric(b) + } +} + +// TestAddenda02RuneCountInString validates RuneCountInString +func TestAddenda02RuneCountInString(t *testing.T) { + addenda02 := NewAddenda02() + var line = "702REFONEAREFTERM021000490612123456Target Store 0049 PHILADELPHIA" + addenda02.Parse(line) + + if addenda02.ReferenceInformationOne != "" { + t.Error("Parsed with an invalid RuneCountInString not equal to 94") + } +} diff --git a/addenda05.go b/addenda05.go new file mode 100644 index 000000000..4dce7a6a4 --- /dev/null +++ b/addenda05.go @@ -0,0 +1,138 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "unicode/utf8" +) + +// Addenda05 is a Addendumer addenda which provides business transaction information for Addenda Type +// Code 05 in a machine readable format. It is usually formatted according to ANSI, ASC, X12 Standard. +// It is used for the following StandardEntryClassCode: ACK, ATX, CCD, CIE, CTX, DNE, ENR, WEB, PPD, TRX. +type Addenda05 struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // TypeCode Addenda05 types code '05' + TypeCode string `json:"typeCode"` + // PaymentRelatedInformation + PaymentRelatedInformation string `json:"paymentRelatedInformation"` + // SequenceNumber is consecutively assigned to each Addenda05 Record following + // an Entry Detail Record. The first addenda05 sequence number must always + // be a "1". + SequenceNumber int `json:"sequenceNumber"` + // EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + // Detail or Corporate Entry Detail Record's trace number This number is + // the same as the last seven digits of the trace number of the related + // Entry Detail Record or Corporate Entry Detail Record. + EntryDetailSequenceNumber int `json:"entryDetailSequenceNumber"` + // validator is composed for data validation + validator + // converters is composed for ACH to GoLang Converters + converters +} + +// NewAddenda05 returns a new Addenda05 with default values for none exported fields +func NewAddenda05() *Addenda05 { + addenda05 := new(Addenda05) + addenda05.TypeCode = "05" + return addenda05 +} + +// Parse takes the input record string and parses the Addenda05 values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. +func (addenda05 *Addenda05) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + // 1-1 Always 7 + // 2-3 Always 05 + addenda05.TypeCode = record[1:3] + // 4-83 Based on the information entered (04-83) 80 alphanumeric + addenda05.PaymentRelatedInformation = strings.TrimSpace(record[3:83]) + // 84-87 SequenceNumber is consecutively assigned to each Addenda05 Record following + // an Entry Detail Record + addenda05.SequenceNumber = addenda05.parseNumField(record[83:87]) + // 88-94 Contains the last seven digits of the number entered in the Trace Number field in the corresponding Entry Detail Record + addenda05.EntryDetailSequenceNumber = addenda05.parseNumField(record[87:94]) +} + +// String writes the Addenda05 struct to a 94 character string. +func (addenda05 *Addenda05) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(entryAddendaPos) + buf.WriteString(addenda05.TypeCode) + buf.WriteString(addenda05.PaymentRelatedInformationField()) + buf.WriteString(addenda05.SequenceNumberField()) + buf.WriteString(addenda05.EntryDetailSequenceNumberField()) + return buf.String() +} + +// Validate performs NACHA format rule checks on the record and returns an error if not Validated +// The first error encountered is returned and stops that parsing. +func (addenda05 *Addenda05) Validate() error { + if err := addenda05.fieldInclusion(); err != nil { + return err + } + + if err := addenda05.isTypeCode(addenda05.TypeCode); err != nil { + return fieldError("TypeCode", err, addenda05.TypeCode) + } + // Type Code must be 05 + if addenda05.TypeCode != "05" { + return fieldError("TypeCode", ErrAddendaTypeCode, addenda05.TypeCode) + } + if err := addenda05.isAlphanumeric(addenda05.PaymentRelatedInformation); err != nil { + return fieldError("PaymentRelatedInformation", err, addenda05.PaymentRelatedInformation) + } + + return nil +} + +// fieldInclusion validate mandatory fields are not default values. If fields are +// invalid the ACH transfer will be returned. +func (addenda05 *Addenda05) fieldInclusion() error { + if addenda05.TypeCode == "" { + return fieldError("TypeCode", ErrConstructor, addenda05.TypeCode) + } + if addenda05.SequenceNumber == 0 { + return fieldError("SequenceNumber", ErrConstructor, addenda05.SequenceNumberField()) + } + if addenda05.EntryDetailSequenceNumber == 0 { + return fieldError("EntryDetailSequenceNumber", ErrConstructor, addenda05.EntryDetailSequenceNumberField()) + } + return nil +} + +// PaymentRelatedInformationField returns a zero padded PaymentRelatedInformation string +func (addenda05 *Addenda05) PaymentRelatedInformationField() string { + return addenda05.alphaField(addenda05.PaymentRelatedInformation, 80) +} + +// SequenceNumberField returns a zero padded SequenceNumber string +func (addenda05 *Addenda05) SequenceNumberField() string { + return addenda05.numericField(addenda05.SequenceNumber, 4) +} + +// EntryDetailSequenceNumberField returns a zero padded EntryDetailSequenceNumber string +func (addenda05 *Addenda05) EntryDetailSequenceNumberField() string { + return addenda05.numericField(addenda05.EntryDetailSequenceNumber, 7) +} diff --git a/addenda05_test.go b/addenda05_test.go new file mode 100644 index 000000000..e1f44549c --- /dev/null +++ b/addenda05_test.go @@ -0,0 +1,197 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "testing" + + "github.com/moov-io/base" +) + +func mockAddenda05() *Addenda05 { + addenda05 := NewAddenda05() + addenda05.SequenceNumber = 1 + addenda05.PaymentRelatedInformation = "This is an Addenda05" + addenda05.EntryDetailSequenceNumber = 0000001 + return addenda05 +} + +func TestMockAddenda05(t *testing.T) { + addenda05 := mockAddenda05() + if err := addenda05.Validate(); err != nil { + t.Error("mockAddenda05 does not validate and will break other tests") + } + if addenda05.EntryDetailSequenceNumber != 0000001 { + t.Error("EntryDetailSequenceNumber dependent default value has changed") + } +} + +// testParseAddenda05 parses an Addenda05 record for a PPD detail entry +func testParseAddenda05(t testing.TB) { + addendaPPD := NewAddenda05() + var line = "705PPD DIEGO MAY 00010000001" + addendaPPD.Parse(line) + + r := NewReader(strings.NewReader(line)) + + //Add a new BatchPPD + r.addCurrentBatch(NewBatchPPD(mockBatchPPDHeader())) + + //Add a PPDEntryDetail + entryDetail := mockPPDEntryDetail() + + //Add an addenda to the PPD EntryDetail + entryDetail.AddAddenda05(addendaPPD) + + // add the PPD entry detail to the batch + r.currentBatch.AddEntry(entryDetail) + + record := r.currentBatch.GetEntries()[0].Addenda05[0] + + if record.TypeCode != "05" { + t.Errorf("TypeCode Expected 05 got: %v", record.TypeCode) + } + if record.PaymentRelatedInformationField() != "PPD DIEGO MAY " { + t.Errorf("PaymentRelatedInformation Expected 'PPD DIEGO MAY ' got: %v", record.PaymentRelatedInformationField()) + } + if record.SequenceNumberField() != "0001" { + t.Errorf("SequenceNumber Expected '0001' got: %v", record.SequenceNumberField()) + } + if record.EntryDetailSequenceNumberField() != "0000001" { + t.Errorf("EntryDetailSequenceNumber Expected '0000001' got: %v", record.EntryDetailSequenceNumberField()) + } +} + +// TestParseAddenda05 tests parsing an Addenda05 record for a PPD detail entry +func TestParseAddenda05(t *testing.T) { + testParseAddenda05(t) +} + +// BenchmarkParseAddenda05 benchmarks parsing an Addenda05 record for a PPD detail entry +func BenchmarkParseAddenda05(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testParseAddenda05(b) + } +} + +// testAddenda05String validates that a known parsed file can be return to a string of the same value +func testAddenda05String(t testing.TB) { + addenda05 := NewAddenda05() + var line = "705WEB DIEGO MAY 00010000001" + addenda05.Parse(line) + + if addenda05.String() != line { + t.Errorf("Strings do not match") + } +} + +// TestAddenda05 String tests validating that a known parsed file can be return to a string of the same value +func TestAddenda05String(t *testing.T) { + testAddenda05String(t) +} + +// BenchmarkAddenda05 String benchmarks validating that a known parsed file can be return to a string of the same value +func BenchmarkAddenda05String(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda05String(b) + } +} + +func TestAddenda05FieldInclusion(t *testing.T) { + addenda05 := mockAddenda05() + addenda05.EntryDetailSequenceNumber = 0 + err := addenda05.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +func TestAddenda05FieldInclusionSequenceNumber(t *testing.T) { + addenda05 := mockAddenda05() + addenda05.SequenceNumber = 0 + err := addenda05.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +func TestAddenda05PaymentRelatedInformationAlphaNumeric(t *testing.T) { + addenda05 := mockAddenda05() + addenda05.PaymentRelatedInformation = "®©" + err := addenda05.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +func testAddenda05TypeCodeNil(t testing.TB) { + addenda05 := mockAddenda05() + addenda05.TypeCode = "" + err := addenda05.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +func TestAddenda05TypeCodeNil(t *testing.T) { + testAddenda05TypeCodeNil(t) +} + +func BenchmarkAddenda05TypeCodeNil(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda05TypeCodeNil(b) + } +} + +// testAddenda05TypeCode05 TypeCode is 05 +func testAddenda05TypeCode05(t testing.TB) { + addenda05 := mockAddenda05() + addenda05.TypeCode = "99" + err := addenda05.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda05TypeCode05 tests TypeCode is 05 +func TestAddenda05TypeCode05(t *testing.T) { + testAddenda05TypeCode05(t) +} + +// BenchmarkAddenda05TypeCode05 benchmarks TypeCode is 05 +func BenchmarkAddenda05TypeCode05(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda05TypeCode05(b) + } +} + +// TestAddenda05RuneCountInString validates RuneCountInString +func TestAddenda05RuneCountInString(t *testing.T) { + addenda05 := NewAddenda05() + var line = "705WEB DIEGO MAY " + addenda05.Parse(line) + + if addenda05.PaymentRelatedInformation != "" { + t.Error("Parsed with an invalid RuneCountInString not equal to 94") + } +} diff --git a/addenda10.go b/addenda10.go new file mode 100644 index 000000000..2b8dd15ec --- /dev/null +++ b/addenda10.go @@ -0,0 +1,175 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "unicode/utf8" +) + +// Addenda10 is an addenda which provides business transaction information for Addenda Type +// Code 10 in a machine readable format. It is usually formatted according to ANSI, ASC, X12 Standard. +// +// # Addenda10 is mandatory for IAT entries +// +// The Addenda10 Record identifies the Receiver of the transaction and the dollar amount of +// the payment. +type Addenda10 struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // TypeCode Addenda10 types code '10' + TypeCode string `json:"typeCode"` + // Transaction Type Code Describes the type of payment: + // ANN = Annuity, BUS = Business/Commercial, DEP = Deposit, LOA = Loan, MIS = Miscellaneous, MOR = Mortgage + // PEN = Pension, RLS = Rent/Lease, REM = Remittance2, SAL = Salary/Payroll, TAX = Tax, TEL = Telephone-Initiated Transaction + // WEB = Internet-Initiated Transaction, ARC = Accounts Receivable Entry, BOC = Back Office Conversion Entry, + // POP = Point of Purchase Entry, RCK = Re-presented Check Entry + TransactionTypeCode string `json:"transactionTypeCode"` + // Foreign Payment Amount $$$$$$$$$$$$$$$$¢¢ + // For inbound IAT payments this field should contain the USD amount or may be blank. + ForeignPaymentAmount int `json:"foreignPaymentAmount"` + // Foreign Trace Number + ForeignTraceNumber string `json:"foreignTraceNumber,omitempty"` + // Receiving Company Name/Individual Name + Name string `json:"name"` + // EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + // Detail or Corporate Entry Detail Record's trace number This number is + // the same as the last seven digits of the trace number of the related + // Entry Detail Record or Corporate Entry Detail Record. + EntryDetailSequenceNumber int `json:"entryDetailSequenceNumber"` + // validator is composed for data validation + validator + // converters is composed for ACH to GoLang Converters + converters +} + +// NewAddenda10 returns a new Addenda10 with default values for none exported fields +func NewAddenda10() *Addenda10 { + addenda10 := new(Addenda10) + addenda10.TypeCode = "10" + return addenda10 +} + +// Parse takes the input record string and parses the Addenda10 values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. +func (addenda10 *Addenda10) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + // 1-1 Always 7 + // 2-3 Always 10 + addenda10.TypeCode = record[1:3] + // 04-06 Describes the type of payment + addenda10.TransactionTypeCode = record[3:6] + // 07-24 Payment Amount For inbound IAT payments this field should contain the USD amount or may be blank. + addenda10.ForeignPaymentAmount = addenda10.parseNumField(record[06:24]) + // 25-46 Insert blanks or zeros + addenda10.ForeignTraceNumber = strings.TrimSpace(record[24:46]) + // 47-81 Receiving Company Name/Individual Name + addenda10.Name = strings.TrimSpace(record[46:81]) + // 82-87 reserved - Leave blank + // 88-94 Contains the last seven digits of the number entered in the Trace Number field in the corresponding Entry Detail Record + addenda10.EntryDetailSequenceNumber = addenda10.parseNumField(record[87:94]) +} + +// String writes the Addenda10 struct to a 94 character string. +func (addenda10 *Addenda10) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(entryAddendaPos) + buf.WriteString(addenda10.TypeCode) + // TransactionTypeCode Validator + buf.WriteString(addenda10.TransactionTypeCode) + buf.WriteString(addenda10.ForeignPaymentAmountField()) + buf.WriteString(addenda10.ForeignTraceNumberField()) + buf.WriteString(addenda10.NameField()) + buf.WriteString(" ") + buf.WriteString(addenda10.EntryDetailSequenceNumberField()) + return buf.String() +} + +// Validate performs NACHA format rule checks on the record and returns an error if not Validated +// The first error encountered is returned and stops that parsing. +func (addenda10 *Addenda10) Validate() error { + if err := addenda10.fieldInclusion(); err != nil { + return err + } + if err := addenda10.isTypeCode(addenda10.TypeCode); err != nil { + return fieldError("TypeCode", err, addenda10.TypeCode) + } + // Type Code must be 10 + if addenda10.TypeCode != "10" { + return fieldError("TypeCode", ErrAddendaTypeCode, addenda10.TypeCode) + } + if err := addenda10.isTransactionTypeCode(addenda10.TransactionTypeCode); err != nil { + return fieldError("TransactionTypeCode", err, addenda10.TransactionTypeCode) + } + // ToDo: Foreign Payment Amount blank ? + if err := addenda10.isAlphanumeric(addenda10.ForeignTraceNumber); err != nil { + return fieldError("ForeignTraceNumber", err, addenda10.ForeignTraceNumber) + } + if err := addenda10.isAlphanumeric(addenda10.Name); err != nil { + return fieldError("Name", err, addenda10.Name) + } + return nil +} + +// fieldInclusion validate mandatory fields are not default values. If fields are +// invalid the ACH transfer will be returned. +func (addenda10 *Addenda10) fieldInclusion() error { + if addenda10.TypeCode == "" { + return fieldError("TypeCode", ErrConstructor, addenda10.TypeCode) + } + if addenda10.TransactionTypeCode == "" { + return fieldError("TransactionTypeCode", ErrFieldRequired, addenda10.TransactionTypeCode) + } + // ToDo: Commented because it appears this value can be all 000 (maybe blank?) + /* if addenda10.ForeignPaymentAmount == 0 { + return fieldError( "ForeignPaymentAmount", ErrFieldRequired, strconv.Itoa(addenda10.ForeignPaymentAmount)) + }*/ + if addenda10.Name == "" { + return fieldError("Name", ErrConstructor, addenda10.Name) + } + if addenda10.EntryDetailSequenceNumber == 0 { + return fieldError("EntryDetailSequenceNumber", ErrConstructor, addenda10.EntryDetailSequenceNumberField()) + } + return nil +} + +// ForeignPaymentAmountField returns ForeignPaymentAmount zero padded +// ToDo: Review/Add logic for blank ? +func (addenda10 *Addenda10) ForeignPaymentAmountField() string { + return addenda10.numericField(addenda10.ForeignPaymentAmount, 18) +} + +// ForeignTraceNumberField gets the Foreign TraceNumber left padded +func (addenda10 *Addenda10) ForeignTraceNumberField() string { + return addenda10.alphaField(addenda10.ForeignTraceNumber, 22) +} + +// NameField gets the name field - Receiving Company Name/Individual Name left padded +func (addenda10 *Addenda10) NameField() string { + return addenda10.alphaField(addenda10.Name, 35) +} + +// EntryDetailSequenceNumberField returns a zero padded EntryDetailSequenceNumber string +func (addenda10 *Addenda10) EntryDetailSequenceNumberField() string { + return addenda10.numericField(addenda10.EntryDetailSequenceNumber, 7) +} diff --git a/addenda10_test.go b/addenda10_test.go new file mode 100644 index 000000000..4d8f9c625 --- /dev/null +++ b/addenda10_test.go @@ -0,0 +1,354 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockAddenda10 creates a mock Addenda10 record +func mockAddenda10() *Addenda10 { + addenda10 := NewAddenda10() + addenda10.TransactionTypeCode = "ANN" + addenda10.ForeignPaymentAmount = 100000 + addenda10.ForeignTraceNumber = "928383-23938" + addenda10.Name = "BEK Enterprises" + addenda10.EntryDetailSequenceNumber = 00000001 + return addenda10 +} + +// TestMockAddenda10 validates mockAddenda10 +func TestMockAddenda10(t *testing.T) { + addenda10 := mockAddenda10() + if err := addenda10.Validate(); err != nil { + t.Error("mockAddenda10 does not validate and will break other tests") + } +} + +// testAddenda10Parse parses Addenda10 record +func testAddenda10Parse(t testing.TB) { + addenda10 := NewAddenda10() + line := "710ANN000000000000100000928383-23938 BEK Enterprises 0000001" + addenda10.Parse(line) + // walk the Addenda10 struct + if addenda10.TypeCode != "10" { + t.Errorf("expected %v got %v", "10", addenda10.TypeCode) + } + if addenda10.TransactionTypeCode != "ANN" { + t.Errorf("expected %v got %v", "ANN", addenda10.TransactionTypeCode) + } + if addenda10.ForeignPaymentAmount != 100000 { + t.Errorf("expected: %v got: %v", 100000, addenda10.ForeignPaymentAmount) + } + if addenda10.ForeignTraceNumber != "928383-23938" { + t.Errorf("expected: %v got: %v", "928383-23938", addenda10.ForeignTraceNumber) + } + if addenda10.Name != "BEK Enterprises" { + t.Errorf("expected: %s got: %s", "BEK Enterprises", addenda10.Name) + } + if addenda10.EntryDetailSequenceNumber != 0000001 { + t.Errorf("expected: %v got: %v", 0000001, addenda10.EntryDetailSequenceNumber) + } +} + +// TestAddenda10Parse tests parsing Addenda10 record +func TestAddenda10Parse(t *testing.T) { + testAddenda10Parse(t) +} + +// BenchmarkAddenda10Parse benchmarks parsing Addenda10 record +func BenchmarkAddenda10Parse(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda10Parse(b) + } +} + +// testAddenda10ValidTypeCode validates Addenda10 TypeCode +func testAddenda10ValidTypeCode(t testing.TB) { + addenda10 := mockAddenda10() + addenda10.TypeCode = "65" + err := addenda10.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda10ValidTypeCode tests validating Addenda10 TypeCode +func TestAddenda10ValidTypeCode(t *testing.T) { + testAddenda10ValidTypeCode(t) +} + +// BenchmarkAddenda10ValidTypeCode benchmarks validating Addenda10 TypeCode +func BenchmarkAddenda10ValidTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda10ValidTypeCode(b) + } +} + +// testAddenda10TypeCode10 TypeCode is 10 if TypeCode is a valid TypeCode +func testAddenda10TypeCode10(t testing.TB) { + addenda10 := mockAddenda10() + addenda10.TypeCode = "05" + err := addenda10.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda10TypeCode10 tests TypeCode is 10 if TypeCode is a valid TypeCode +func TestAddenda10TypeCode10(t *testing.T) { + testAddenda10TypeCode10(t) +} + +// BenchmarkAddenda10TypeCode10 benchmarks TypeCode is 10 if TypeCode is a valid TypeCode +func BenchmarkAddenda10TypeCode10(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda10TypeCode10(b) + } +} + +// testAddenda10TransactionTypeCode validates TransactionTypeCode +func testAddenda10TransactionTypeCode(t testing.TB) { + addenda10 := mockAddenda10() + addenda10.TransactionTypeCode = "ABC" + err := addenda10.Validate() + if !base.Match(err, ErrTransactionTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda10TransactionTypeCode tests validating TransactionTypeCode +func TestAddenda10TransactionTypeCode(t *testing.T) { + testAddenda10TransactionTypeCode(t) +} + +// BenchmarkAddenda10TransactionTypeCode benchmarks validating TransactionTypeCode +func BenchmarkAddenda10TransactionTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda10TransactionTypeCode(b) + } +} + +// testForeignTraceNumberAlphaNumeric validates ForeignTraceNumber is alphanumeric +func testForeignTraceNumberAlphaNumeric(t testing.TB) { + addenda10 := mockAddenda10() + addenda10.ForeignTraceNumber = "®" + err := addenda10.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestForeignTraceNumberAlphaNumeric tests validating ForeignTraceNumber is alphanumeric +func TestForeignTraceNumberAlphaNumeric(t *testing.T) { + testForeignTraceNumberAlphaNumeric(t) +} + +// BenchmarkForeignTraceNumberAlphaNumeric benchmarks validating ForeignTraceNumber is alphanumeric +func BenchmarkForeignTraceNumberAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testForeignTraceNumberAlphaNumeric(b) + } +} + +// testNameAlphaNumeric validates Name is alphanumeric +func testNameAlphaNumeric(t testing.TB) { + addenda10 := mockAddenda10() + addenda10.Name = "Jas®n" + err := addenda10.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestNameAlphaNumeric tests validating Name is alphanumeric +func TestNameAlphaNumeric(t *testing.T) { + testNameAlphaNumeric(t) +} + +// BenchmarkNameAlphaNumeric benchmarks validating Name is alphanumeric +func BenchmarkNameAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testNameAlphaNumeric(b) + } +} + +// testAddenda10FieldInclusionTypeCode validates TypeCode fieldInclusion +func testAddenda10FieldInclusionTypeCode(t testing.TB) { + addenda10 := mockAddenda10() + addenda10.TypeCode = "" + err := addenda10.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda10FieldInclusionTypeCode tests validating TypeCode fieldInclusion +func TestAddenda10FieldInclusionTypeCode(t *testing.T) { + testAddenda10FieldInclusionTypeCode(t) +} + +// BenchmarkAddenda10FieldInclusionTypeCode benchmarks validating TypeCode fieldInclusion +func BenchmarkAddenda10FieldInclusionTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda10FieldInclusionTypeCode(b) + } +} + +// testAddenda10FieldInclusionTransactionTypeCode validates TransactionTypeCode fieldInclusion +func testAddenda10FieldInclusionTransactionTypeCode(t testing.TB) { + addenda10 := mockAddenda10() + addenda10.TransactionTypeCode = "" + err := addenda10.Validate() + if !base.Match(err, ErrFieldRequired) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda10FieldInclusionTransactionTypeCode tests validating +// TransactionTypeCode fieldInclusion +func TestAddenda10FieldInclusionTransactionTypeCode(t *testing.T) { + testAddenda10FieldInclusionTransactionTypeCode(t) +} + +// BenchmarkAddenda10FieldInclusionTransactionTypeCode benchmarks validating +// TransactionTypeCode fieldInclusion +func BenchmarkAddenda10FieldInclusionTransactionTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda10FieldInclusionTransactionTypeCode(b) + } +} + +// testAddenda10FieldInclusionForeignPaymentAmount validates ForeignPaymentAmount fieldInclusion +func testAddenda10FieldInclusionForeignPaymentAmount(t testing.TB) { + addenda10 := mockAddenda10() + addenda10.ForeignPaymentAmount = 0 + err := addenda10.Validate() + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda10FieldInclusionForeignPaymentAmount tests validating ForeignPaymentAmount fieldInclusion +func TestAddenda10FieldInclusionForeignPaymentAmount(t *testing.T) { + testAddenda10FieldInclusionForeignPaymentAmount(t) +} + +// BenchmarkAddenda10FieldInclusionForeignPaymentAmount benchmarks validating ForeignPaymentAmount fieldInclusion +func BenchmarkAddenda10FieldInclusionForeignPaymentAmount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda10FieldInclusionForeignPaymentAmount(b) + } +} + +// testAddenda10FieldInclusionName validates Name fieldInclusion +func testAddenda10FieldInclusionName(t testing.TB) { + addenda10 := mockAddenda10() + addenda10.Name = "" + err := addenda10.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda10FieldInclusionName tests validating Name fieldInclusion +func TestAddenda10FieldInclusionName(t *testing.T) { + testAddenda10FieldInclusionName(t) +} + +// BenchmarkAddenda10FieldInclusionName benchmarks validating Name fieldInclusion +func BenchmarkAddenda10FieldInclusionName(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda10FieldInclusionName(b) + } +} + +// testAddenda10FieldInclusionEntryDetailSequenceNumber validates EntryDetailSequenceNumber fieldInclusion +func testAddenda10FieldInclusionEntryDetailSequenceNumber(t testing.TB) { + addenda10 := mockAddenda10() + addenda10.EntryDetailSequenceNumber = 0 + err := addenda10.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda10FieldInclusionEntryDetailSequenceNumber tests validating +// EntryDetailSequenceNumber fieldInclusion +func TestAddenda10FieldInclusionEntryDetailSequenceNumber(t *testing.T) { + testAddenda10FieldInclusionEntryDetailSequenceNumber(t) +} + +// BenchmarkAddenda10FieldInclusionEntryDetailSequenceNumber benchmarks validating +// EntryDetailSequenceNumber fieldInclusion +func BenchmarkAddenda10FieldInclusionEntryDetailSequenceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda10FieldInclusionEntryDetailSequenceNumber(b) + } +} + +// TestAddenda10String validates that a known parsed Addenda10 record can be return to a string of the same value +func testAddenda10String(t testing.TB) { + addenda10 := NewAddenda10() + var line = "710ANN000000000000100000928383-23938 BEK Enterprises 0000001" + addenda10.Parse(line) + + if addenda10.String() != line { + t.Errorf("Strings do not match") + } + if addenda10.TypeCode != "10" { + t.Errorf("TypeCode Expected 10 got: %v", addenda10.TypeCode) + } +} + +// TestAddenda10String tests validating that a known parsed Addenda10 record can be return to a string of the same value +func TestAddenda10String(t *testing.T) { + testAddenda10String(t) +} + +// BenchmarkAddenda10String benchmarks validating that a known parsed Addenda10 record can be return to a string of the same value +func BenchmarkAddenda10String(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda10String(b) + } +} + +// TestAddenda10RuneCountInString validates RuneCountInString +func TestAddenda10RuneCountInString(t *testing.T) { + addenda10 := NewAddenda10() + var line = "710ANN000000000000100000928383-23938 BEK Enterprises" + addenda10.Parse(line) + + if addenda10.TransactionTypeCode != "" { + t.Error("Parsed with an invalid RuneCountInString not equal to 94") + } +} diff --git a/addenda11.go b/addenda11.go new file mode 100644 index 000000000..eebe90595 --- /dev/null +++ b/addenda11.go @@ -0,0 +1,145 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "unicode/utf8" +) + +// Addenda11 is an addenda which provides business transaction information for Addenda Type +// Code 11 in a machine readable format. It is usually formatted according to ANSI, ASC, X12 Standard. +// +// # Addenda11 is mandatory for IAT entries +// +// The Addenda11 record identifies key information related to the Originator of +// the entry. +type Addenda11 struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // TypeCode Addenda11 types code '11' + TypeCode string `json:"typeCode"` + // Originator Name contains the originators name (your company name / name) + OriginatorName string `json:"originatorName"` + // Originator Street Address Contains the originators street address (your company's address / your address) + OriginatorStreetAddress string `json:"originatorStreetAddress"` + // EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + // Detail or Corporate Entry Detail Record's trace number This number is + // the same as the last seven digits of the trace number of the related + // Entry Detail Record or Corporate Entry Detail Record. + EntryDetailSequenceNumber int `json:"entryDetailSequenceNumber"` + // validator is composed for data validation + validator + // converters is composed for ACH to GoLang Converters + converters +} + +// NewAddenda11 returns a new Addenda11 with default values for none exported fields +func NewAddenda11() *Addenda11 { + addenda11 := new(Addenda11) + addenda11.TypeCode = "11" + return addenda11 +} + +// Parse takes the input record string and parses the Addenda11 values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. +func (addenda11 *Addenda11) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + // 1-1 Always 7 + // 2-3 Always 11 + addenda11.TypeCode = record[1:3] + // 4-38 + addenda11.OriginatorName = strings.TrimSpace(record[3:38]) + // 39-73 + addenda11.OriginatorStreetAddress = strings.TrimSpace(record[38:73]) + // 74-87 reserved - Leave blank + // 88-94 Contains the last seven digits of the number entered in the Trace Number field in the corresponding Entry Detail Record + addenda11.EntryDetailSequenceNumber = addenda11.parseNumField(record[87:94]) +} + +// String writes the Addenda11 struct to a 94 character string. +func (addenda11 *Addenda11) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(entryAddendaPos) + buf.WriteString(addenda11.TypeCode) + buf.WriteString(addenda11.OriginatorNameField()) + buf.WriteString(addenda11.OriginatorStreetAddressField()) + buf.WriteString(" ") + buf.WriteString(addenda11.EntryDetailSequenceNumberField()) + return buf.String() +} + +// Validate performs NACHA format rule checks on the record and returns an error if not Validated +// The first error encountered is returned and stops that parsing. +func (addenda11 *Addenda11) Validate() error { + if err := addenda11.fieldInclusion(); err != nil { + return err + } + if err := addenda11.isTypeCode(addenda11.TypeCode); err != nil { + return fieldError("TypeCode", err, addenda11.TypeCode) + } + // Type Code must be 11 + if addenda11.TypeCode != "11" { + return fieldError("TypeCode", ErrAddendaTypeCode, addenda11.TypeCode) + } + if err := addenda11.isAlphanumeric(addenda11.OriginatorName); err != nil { + return fieldError("OriginatorName", err, addenda11.OriginatorName) + } + if err := addenda11.isAlphanumeric(addenda11.OriginatorStreetAddress); err != nil { + return fieldError("OriginatorStreetAddress", err, addenda11.OriginatorStreetAddress) + } + return nil +} + +// fieldInclusion validate mandatory fields are not default values. If fields are +// invalid the ACH transfer will be returned. +func (addenda11 *Addenda11) fieldInclusion() error { + if addenda11.TypeCode == "" { + return fieldError("TypeCode", ErrConstructor, addenda11.TypeCode) + } + if addenda11.OriginatorName == "" { + return fieldError("OriginatorName", ErrConstructor, addenda11.OriginatorName) + } + if addenda11.OriginatorStreetAddress == "" { + return fieldError("OriginatorStreetAddress", ErrConstructor, addenda11.OriginatorStreetAddress) + } + if addenda11.EntryDetailSequenceNumber == 0 { + return fieldError("EntryDetailSequenceNumber", ErrConstructor, addenda11.EntryDetailSequenceNumberField()) + } + return nil +} + +// OriginatorNameField gets the OriginatorName field - Originator Company Name/Individual Name left padded +func (addenda11 *Addenda11) OriginatorNameField() string { + return addenda11.alphaField(addenda11.OriginatorName, 35) +} + +// OriginatorStreetAddressField gets the OriginatorStreetAddress field - Originator Street Address left padded +func (addenda11 *Addenda11) OriginatorStreetAddressField() string { + return addenda11.alphaField(addenda11.OriginatorStreetAddress, 35) +} + +// EntryDetailSequenceNumberField returns a zero padded EntryDetailSequenceNumber string +func (addenda11 *Addenda11) EntryDetailSequenceNumberField() string { + return addenda11.numericField(addenda11.EntryDetailSequenceNumber, 7) +} diff --git a/addenda11_test.go b/addenda11_test.go new file mode 100644 index 000000000..4ac4234a3 --- /dev/null +++ b/addenda11_test.go @@ -0,0 +1,298 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockAddenda11 creates a mock Addenda11 record +func mockAddenda11() *Addenda11 { + addenda11 := NewAddenda11() + addenda11.OriginatorName = "BEK Solutions" + addenda11.OriginatorStreetAddress = "15 West Place Street" + addenda11.EntryDetailSequenceNumber = 00000001 + return addenda11 +} + +// TestMockAddenda11 validates mockAddenda11 +func TestMockAddenda11(t *testing.T) { + addenda11 := mockAddenda11() + if err := addenda11.Validate(); err != nil { + t.Error("mockAddenda11 does not validate and will break other tests") + } +} + +// testAddenda11Parse parses Addenda11 record +func testAddenda11Parse(t testing.TB) { + Addenda11 := NewAddenda11() + line := "711BEK Solutions 15 West Place Street 0000001" + Addenda11.Parse(line) + // walk the Addenda11 struct + if Addenda11.TypeCode != "11" { + t.Errorf("expected %v got %v", "11", Addenda11.TypeCode) + } + if Addenda11.OriginatorName != "BEK Solutions" { + t.Errorf("expected %v got %v", "BEK Solutions", Addenda11.OriginatorName) + } + if Addenda11.OriginatorStreetAddress != "15 West Place Street" { + t.Errorf("expected: %v got: %v", "15 West Place Street", Addenda11.OriginatorStreetAddress) + } + if Addenda11.EntryDetailSequenceNumber != 0000001 { + t.Errorf("expected: %v got: %v", 0000001, Addenda11.EntryDetailSequenceNumber) + } +} + +// TestAddenda11Parse tests parsing Addenda11 record +func TestAddenda11Parse(t *testing.T) { + testAddenda11Parse(t) +} + +// BenchmarkAddenda11Parse benchmarks parsing Addenda11 record +func BenchmarkAddenda11Parse(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda11Parse(b) + } +} + +// testAddenda11ValidTypeCode validates Addenda11 TypeCode +func testAddenda11ValidTypeCode(t testing.TB) { + addenda11 := mockAddenda11() + addenda11.TypeCode = "65" + err := addenda11.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda11ValidTypeCode tests validating Addenda11 TypeCode +func TestAddenda11ValidTypeCode(t *testing.T) { + testAddenda11ValidTypeCode(t) +} + +// BenchmarkAddenda11ValidTypeCode benchmarks validating Addenda11 TypeCode +func BenchmarkAddenda11ValidTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda11ValidTypeCode(b) + } +} + +// testAddenda11TypeCode11 TypeCode is 11 if TypeCode is a valid TypeCode +func testAddenda11TypeCode11(t testing.TB) { + addenda11 := mockAddenda11() + addenda11.TypeCode = "05" + err := addenda11.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda11TypeCode11 tests TypeCode is 11 if TypeCode is a valid TypeCode +func TestAddenda11TypeCode11(t *testing.T) { + testAddenda11TypeCode11(t) +} + +// BenchmarkAddenda11TypeCode11 benchmarks TypeCode is 11 if TypeCode is a valid TypeCode +func BenchmarkAddenda11TypeCode11(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda11TypeCode11(b) + } +} + +// testOriginatorNameAlphaNumeric validates OriginatorName is alphanumeric +func testOriginatorNameAlphaNumeric(t testing.TB) { + addenda11 := mockAddenda11() + addenda11.OriginatorName = "BEK S®lutions" + err := addenda11.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestOriginatorNameAlphaNumeric tests validating OriginatorName is alphanumeric +func TestOriginatorNameAlphaNumeric(t *testing.T) { + testOriginatorNameAlphaNumeric(t) +} + +// BenchmarkOriginatorNameAlphaNumeric benchmarks validating OriginatorName is alphanumeric +func BenchmarkOriginatorNameAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testOriginatorNameAlphaNumeric(b) + } +} + +// testOriginatorStreetAddressAlphaNumeric validates OriginatorStreetAddress is alphanumeric +func testOriginatorStreetAddressAlphaNumeric(t testing.TB) { + addenda11 := mockAddenda11() + addenda11.OriginatorStreetAddress = "15 W®st" + err := addenda11.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestOriginatorStreetAddressAlphaNumeric tests validating OriginatorStreetAddress is alphanumeric +func TestOriginatorStreetAddressAlphaNumeric(t *testing.T) { + testOriginatorStreetAddressAlphaNumeric(t) +} + +// BenchmarkOriginatorStreetAddressAlphaNumeric benchmarks validating OriginatorStreetAddress is alphanumeric +func BenchmarkOriginatorStreetAddressAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testOriginatorStreetAddressAlphaNumeric(b) + } +} + +// testAddenda11FieldInclusionTypeCode validates TypeCode fieldInclusion +func testAddenda11FieldInclusionTypeCode(t testing.TB) { + addenda11 := mockAddenda11() + addenda11.TypeCode = "" + err := addenda11.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda11FieldInclusionTypeCode tests validating TypeCode fieldInclusion +func TestAddenda11FieldInclusionTypeCode(t *testing.T) { + testAddenda11FieldInclusionTypeCode(t) +} + +// BenchmarkAddenda11FieldInclusionTypeCode benchmarks validating TypeCode fieldInclusion +func BenchmarkAddenda11FieldInclusionTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda11FieldInclusionTypeCode(b) + } +} + +// testAddenda11FieldInclusionOriginatorName validates OriginatorName fieldInclusion +func testAddenda11FieldInclusionOriginatorName(t testing.TB) { + addenda11 := mockAddenda11() + addenda11.OriginatorName = "" + err := addenda11.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda11FieldInclusionOriginatorName tests validating OriginatorName fieldInclusion +func TestAddenda11FieldInclusionOriginatorName(t *testing.T) { + testAddenda11FieldInclusionOriginatorName(t) +} + +// BenchmarkAddenda11FieldInclusionOriginatorName benchmarks validating OriginatorName fieldInclusion +func BenchmarkAddenda11FieldInclusionOriginatorName(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda11FieldInclusionOriginatorName(b) + } +} + +// testAddenda11FieldInclusionOriginatorStreetAddress validates OriginatorStreetAddress fieldInclusion +func testAddenda11FieldInclusionOriginatorStreetAddress(t testing.TB) { + addenda11 := mockAddenda11() + addenda11.OriginatorStreetAddress = "" + err := addenda11.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda11FieldInclusionOriginatorStreetAddress tests validating OriginatorStreetAddress fieldInclusion +func TestAddenda11FieldInclusionOriginatorStreetAddress(t *testing.T) { + testAddenda11FieldInclusionOriginatorStreetAddress(t) +} + +// BenchmarkAddenda11FieldInclusionOriginatorStreetAddress benchmarks validating OriginatorStreetAddress fieldInclusion +func BenchmarkAddenda11FieldInclusionOriginatorStreetAddress(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda11FieldInclusionOriginatorStreetAddress(b) + } +} + +// testAddenda11FieldInclusionEntryDetailSequenceNumber validates EntryDetailSequenceNumber fieldInclusion +func testAddenda11FieldInclusionEntryDetailSequenceNumber(t testing.TB) { + addenda11 := mockAddenda11() + addenda11.EntryDetailSequenceNumber = 0 + err := addenda11.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda11FieldInclusionEntryDetailSequenceNumber tests validating +// EntryDetailSequenceNumber fieldInclusion +func TestAddenda11FieldInclusionEntryDetailSequenceNumber(t *testing.T) { + testAddenda11FieldInclusionEntryDetailSequenceNumber(t) +} + +// BenchmarkAddenda11FieldInclusionEntryDetailSequenceNumber benchmarks validating +// EntryDetailSequenceNumber fieldInclusion +func BenchmarkAddenda11FieldInclusionEntryDetailSequenceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda11FieldInclusionEntryDetailSequenceNumber(b) + } +} + +// TestAddenda11String validates that a known parsed Addenda11 record can be return to a string of the same value +func testAddenda11String(t testing.TB) { + addenda11 := NewAddenda11() + var line = "711BEK Solutions 15 West Place Street 0000001" + addenda11.Parse(line) + + if addenda11.String() != line { + t.Errorf("Strings do not match") + } + if addenda11.TypeCode != "11" { + t.Errorf("TypeCode Expected 11 got: %v", addenda11.TypeCode) + } +} + +// TestAddenda11String tests validating that a known parsed Addenda11 record can be return to a string of the same value +func TestAddenda11String(t *testing.T) { + testAddenda11String(t) +} + +// BenchmarkAddenda11String benchmarks validating that a known parsed Addenda11 record can be return to a string of the same value +func BenchmarkAddenda11String(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda11String(b) + } +} + +// TestAddenda11RuneCountInString validates RuneCountInString +func TestAddenda11RuneCountInString(t *testing.T) { + addenda11 := NewAddenda11() + var line = "711BEK Solutions 15 West Place Street" + addenda11.Parse(line) + + if addenda11.OriginatorName != "" { + t.Error("Parsed with an invalid RuneCountInString not equal to 94") + } +} diff --git a/addenda12.go b/addenda12.go new file mode 100644 index 000000000..63ff2f47b --- /dev/null +++ b/addenda12.go @@ -0,0 +1,151 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "unicode/utf8" +) + +// Addenda12 is an addenda which provides business transaction information for Addenda Type +// Code 12 in a machine readable format. It is usually formatted according to ANSI, ASC, X12 Standard. +// +// # Addenda12 is mandatory for IAT entries +// +// The Addenda12 record identifies key information related to the Originator of +// the entry. +type Addenda12 struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // TypeCode Addenda12 types code '12' + TypeCode string `json:"typeCode"` + // Originator City & State / Province + // Data elements City and State / Province should be separated with an asterisk (*) as a delimiter + // and the field should end with a backslash (\). + // For example: San Francisco*CA\ + OriginatorCityStateProvince string `json:"originatorCityStateProvince"` + // Originator Country & Postal Code + // Data elements must be separated by an asterisk (*) and must end with a backslash (\) + // For example: US*10036\ + OriginatorCountryPostalCode string `json:"originatorCountryPostalCode"` + // EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + // Detail or Corporate Entry Detail Record's trace number This number is + // the same as the last seven digits of the trace number of the related + // Entry Detail Record or Corporate Entry Detail Record. + EntryDetailSequenceNumber int `json:"entryDetailSequenceNumber"` + // validator is composed for data validation + validator + // converters is composed for ACH to GoLang Converters + converters +} + +// NewAddenda12 returns a new Addenda12 with default values for none exported fields +func NewAddenda12() *Addenda12 { + addenda12 := new(Addenda12) + addenda12.TypeCode = "12" + return addenda12 +} + +// Parse takes the input record string and parses the Addenda12 values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. +func (addenda12 *Addenda12) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + // 1-1 Always 7 + // 2-3 Always 12 + addenda12.TypeCode = record[1:3] + // 4-38 + addenda12.OriginatorCityStateProvince = strings.TrimSpace(record[3:38]) + // 39-73 + addenda12.OriginatorCountryPostalCode = strings.TrimSpace(record[38:73]) + // 74-87 reserved - Leave blank + // 88-94 Contains the last seven digits of the number entered in the Trace Number field in the corresponding Entry Detail Record + addenda12.EntryDetailSequenceNumber = addenda12.parseNumField(record[87:94]) +} + +// String writes the Addenda12 struct to a 94 character string. +func (addenda12 *Addenda12) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(entryAddendaPos) + buf.WriteString(addenda12.TypeCode) + buf.WriteString(addenda12.OriginatorCityStateProvinceField()) + // ToDo Validator for backslash + buf.WriteString(addenda12.OriginatorCountryPostalCodeField()) + buf.WriteString(" ") + buf.WriteString(addenda12.EntryDetailSequenceNumberField()) + return buf.String() +} + +// Validate performs NACHA format rule checks on the record and returns an error if not Validated +// The first error encountered is returned and stops that parsing. +func (addenda12 *Addenda12) Validate() error { + if err := addenda12.fieldInclusion(); err != nil { + return err + } + if err := addenda12.isTypeCode(addenda12.TypeCode); err != nil { + return fieldError("TypeCode", err, addenda12.TypeCode) + } + // Type Code must be 12 + if addenda12.TypeCode != "12" { + return fieldError("TypeCode", ErrAddendaTypeCode, addenda12.TypeCode) + } + if err := addenda12.isAlphanumeric(addenda12.OriginatorCityStateProvince); err != nil { + return fieldError("OriginatorCityStateProvince", err, addenda12.OriginatorCityStateProvince) + } + if err := addenda12.isAlphanumeric(addenda12.OriginatorCountryPostalCode); err != nil { + return fieldError("OriginatorCountryPostalCode", err, addenda12.OriginatorCountryPostalCode) + } + return nil +} + +// fieldInclusion validate mandatory fields are not default values. If fields are +// invalid the ACH transfer will be returned. +func (addenda12 *Addenda12) fieldInclusion() error { + if addenda12.TypeCode == "" { + return fieldError("TypeCode", ErrConstructor, addenda12.TypeCode) + } + if addenda12.OriginatorCityStateProvince == "" { + return fieldError("OriginatorCityStateProvince", ErrConstructor, addenda12.OriginatorCityStateProvince) + } + if addenda12.OriginatorCountryPostalCode == "" { + return fieldError("OriginatorCountryPostalCode", ErrConstructor, addenda12.OriginatorCountryPostalCode) + } + if addenda12.EntryDetailSequenceNumber == 0 { + return fieldError("EntryDetailSequenceNumber", ErrConstructor, addenda12.EntryDetailSequenceNumberField()) + } + return nil +} + +// OriginatorCityStateProvinceField gets the OriginatorCityStateProvinceField left padded +func (addenda12 *Addenda12) OriginatorCityStateProvinceField() string { + return addenda12.alphaField(addenda12.OriginatorCityStateProvince, 35) +} + +// OriginatorCountryPostalCodeField gets the OriginatorCountryPostalCode field left padded +func (addenda12 *Addenda12) OriginatorCountryPostalCodeField() string { + return addenda12.alphaField(addenda12.OriginatorCountryPostalCode, 35) +} + +// EntryDetailSequenceNumberField returns a zero padded EntryDetailSequenceNumber string +func (addenda12 *Addenda12) EntryDetailSequenceNumberField() string { + return addenda12.numericField(addenda12.EntryDetailSequenceNumber, 7) +} diff --git a/addenda12_test.go b/addenda12_test.go new file mode 100644 index 000000000..4f99dcbe6 --- /dev/null +++ b/addenda12_test.go @@ -0,0 +1,304 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockAddenda12 creates a mock Addenda12 record +func mockAddenda12() *Addenda12 { + addenda12 := NewAddenda12() + addenda12.OriginatorCityStateProvince = "JacobsTown*PA\\" + addenda12.OriginatorCountryPostalCode = "US*19305\\" + addenda12.EntryDetailSequenceNumber = 00000001 + return addenda12 +} + +// TestMockAddenda12 validates mockAddenda12 +func TestMockAddenda12(t *testing.T) { + addenda12 := mockAddenda12() + if err := addenda12.Validate(); err != nil { + t.Error("mockAddenda12 does not validate and will break other tests") + } +} + +// testAddenda12Parse parses Addenda12 record +func testAddenda12Parse(t testing.TB) { + Addenda12 := NewAddenda12() + line := "712" + "JacobsTown*PA\\ " + "US*19305\\ " + "0000001" + Addenda12.Parse(line) + // walk the Addenda12 struct + if Addenda12.TypeCode != "12" { + t.Errorf("expected %v got %v", "12", Addenda12.TypeCode) + } + if Addenda12.OriginatorCityStateProvince != "JacobsTown*PA\\" { + t.Errorf("expected %v got %v", "JacobsTown*PA\\", Addenda12.OriginatorCityStateProvince) + } + if Addenda12.OriginatorCountryPostalCode != "US*19305\\" { + t.Errorf("expected: %v got: %v", "US*19305\\", Addenda12.OriginatorCountryPostalCode) + } + if Addenda12.EntryDetailSequenceNumber != 0000001 { + t.Errorf("expected: %v got: %v", 0000001, Addenda12.EntryDetailSequenceNumber) + } +} + +// TestAddenda12Parse tests parsing Addenda12 record +func TestAddenda12Parse(t *testing.T) { + testAddenda12Parse(t) +} + +// BenchmarkAddenda12Parse benchmarks parsing Addenda12 record +func BenchmarkAddenda12Parse(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda12Parse(b) + } +} + +// testAddenda12ValidTypeCode validates Addenda12 TypeCode +func testAddenda12ValidTypeCode(t testing.TB) { + addenda12 := mockAddenda12() + addenda12.TypeCode = "65" + err := addenda12.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda12ValidTypeCode tests validating Addenda12 TypeCode +func TestAddenda12ValidTypeCode(t *testing.T) { + testAddenda12ValidTypeCode(t) +} + +// BenchmarkAddenda12ValidTypeCode benchmarks validating Addenda12 TypeCode +func BenchmarkAddenda12ValidTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda12ValidTypeCode(b) + } +} + +// testAddenda12TypeCode12 TypeCode is 12 if TypeCode is a valid TypeCode +func testAddenda12TypeCode12(t testing.TB) { + addenda12 := mockAddenda12() + addenda12.TypeCode = "05" + err := addenda12.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda12TypeCode12 tests TypeCode is 12 if TypeCode is a valid TypeCode +func TestAddenda12TypeCode12(t *testing.T) { + testAddenda12TypeCode12(t) +} + +// BenchmarkAddenda12TypeCode12 benchmarks TypeCode is 12 if TypeCode is a valid TypeCode +func BenchmarkAddenda12TypeCode12(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda12TypeCode12(b) + } +} + +// testOriginatorCityStateProvinceAlphaNumeric validates OriginatorCityStateProvince is alphanumeric +func testOriginatorCityStateProvinceAlphaNumeric(t testing.TB) { + addenda12 := mockAddenda12() + addenda12.OriginatorCityStateProvince = "Jacobs®Town*PA" + err := addenda12.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestOriginatorCityStateProvinceAlphaNumeric tests validating OriginatorCityStateProvince is alphanumeric +func TestOriginatorCityStateProvinceAlphaNumeric(t *testing.T) { + testOriginatorCityStateProvinceAlphaNumeric(t) +} + +// BenchmarkOriginatorCityStateProvinceAlphaNumeric benchmarks validating OriginatorCityStateProvince is alphanumeric +func BenchmarkOriginatorCityStateProvinceAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testOriginatorCityStateProvinceAlphaNumeric(b) + } +} + +// testOriginatorCountryPostalCodeAlphaNumeric validates OriginatorCountryPostalCode is alphanumeric +func testOriginatorCountryPostalCodeAlphaNumeric(t testing.TB) { + addenda12 := mockAddenda12() + addenda12.OriginatorCountryPostalCode = "US19®305" + err := addenda12.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestOriginatorCountryPostalCodeAlphaNumeric tests validating OriginatorCountryPostalCode is alphanumeric +func TestOriginatorCountryPostalCodeAlphaNumeric(t *testing.T) { + testOriginatorCountryPostalCodeAlphaNumeric(t) +} + +// BenchmarkOriginatorCountryPostalCodeAlphaNumeric benchmarks validating OriginatorCountryPostalCode is alphanumeric +func BenchmarkOriginatorCountryPostalCodeAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testOriginatorCountryPostalCodeAlphaNumeric(b) + } +} + +// testAddenda12FieldInclusionTypeCode validates TypeCode fieldInclusion +func testAddenda12FieldInclusionTypeCode(t testing.TB) { + addenda12 := mockAddenda12() + addenda12.TypeCode = "" + err := addenda12.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda12FieldInclusionTypeCode tests validating TypeCode fieldInclusion +func TestAddenda12FieldInclusionTypeCode(t *testing.T) { + testAddenda12FieldInclusionTypeCode(t) +} + +// BenchmarkAddenda12FieldInclusionTypeCode benchmarks validating TypeCode fieldInclusion +func BenchmarkAddenda12FieldInclusionTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda12FieldInclusionTypeCode(b) + } +} + +// testAddenda12FieldInclusionOriginatorCityStateProvince validates OriginatorCityStateProvince fieldInclusion +func testAddenda12FieldInclusionOriginatorCityStateProvince(t testing.TB) { + addenda12 := mockAddenda12() + addenda12.OriginatorCityStateProvince = "" + err := addenda12.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda12FieldInclusionOriginatorCityStateProvince tests validating OriginatorCityStateProvince fieldInclusion +func TestAddenda12FieldInclusionOriginatorCityStateProvince(t *testing.T) { + testAddenda12FieldInclusionOriginatorCityStateProvince(t) +} + +// BenchmarkAddenda12FieldInclusionOriginatorCityStateProvince benchmarks validating OriginatorCityStateProvince fieldInclusion +func BenchmarkAddenda12FieldInclusionOriginatorCityStateProvince(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda12FieldInclusionOriginatorCityStateProvince(b) + } +} + +// testAddenda12FieldInclusionOriginatorCountryPostalCode validates OriginatorCountryPostalCode fieldInclusion +func testAddenda12FieldInclusionOriginatorCountryPostalCode(t testing.TB) { + addenda12 := mockAddenda12() + addenda12.OriginatorCountryPostalCode = "" + err := addenda12.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda12FieldInclusionOriginatorCountryPostalCode tests validating OriginatorCountryPostalCode fieldInclusion +func TestAddenda12FieldInclusionOriginatorCountryPostalCode(t *testing.T) { + testAddenda12FieldInclusionOriginatorCountryPostalCode(t) +} + +// BenchmarkAddenda12FieldInclusionOriginatorCountryPostalCode benchmarks validating OriginatorCountryPostalCode fieldInclusion +func BenchmarkAddenda12FieldInclusionOriginatorCountryPostalCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda12FieldInclusionOriginatorCountryPostalCode(b) + } +} + +// testAddenda12FieldInclusionEntryDetailSequenceNumber validates EntryDetailSequenceNumber fieldInclusion +func testAddenda12FieldInclusionEntryDetailSequenceNumber(t testing.TB) { + addenda12 := mockAddenda12() + addenda12.EntryDetailSequenceNumber = 0 + err := addenda12.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda12FieldInclusionEntryDetailSequenceNumber tests validating +// EntryDetailSequenceNumber fieldInclusion +func TestAddenda12FieldInclusionEntryDetailSequenceNumber(t *testing.T) { + testAddenda12FieldInclusionEntryDetailSequenceNumber(t) +} + +// BenchmarkAddenda12FieldInclusionEntryDetailSequenceNumber benchmarks validating +// EntryDetailSequenceNumber fieldInclusion +func BenchmarkAddenda12FieldInclusionEntryDetailSequenceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda12FieldInclusionEntryDetailSequenceNumber(b) + } +} + +// TestAddenda12String validates that a known parsed Addenda12 record can be return to a string of the same value +func testAddenda12String(t testing.TB) { + addenda12 := NewAddenda12() + // Backslash logic + var line = "712" + + "JacobsTown*PA\\ " + + "US*19305\\ " + + " " + + "0000001" + + addenda12.Parse(line) + + if addenda12.String() != line { + t.Errorf("Strings do not match") + } + if addenda12.TypeCode != "12" { + t.Errorf("TypeCode Expected 12 got: %v", addenda12.TypeCode) + } +} + +// TestAddenda12String tests validating that a known parsed Addenda12 record can be return to a string of the same value +func TestAddenda12String(t *testing.T) { + testAddenda12String(t) +} + +// BenchmarkAddenda12String benchmarks validating that a known parsed Addenda12 record can be return to a string of the same value +func BenchmarkAddenda12String(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda12String(b) + } +} + +// TestAddenda12RuneCountInString validates RuneCountInString +func TestAddenda12RuneCountInString(t *testing.T) { + addenda12 := NewAddenda12() + var line = "712" + "JacobsTown*PA\\ " + "US*19305\\ " + addenda12.Parse(line) + + if addenda12.OriginatorCountryPostalCode != "" { + t.Error("Parsed with an invalid RuneCountInString not equal to 94") + } +} diff --git a/addenda13.go b/addenda13.go new file mode 100644 index 000000000..bbd53cb61 --- /dev/null +++ b/addenda13.go @@ -0,0 +1,195 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "unicode/utf8" +) + +// Addenda13 is an addenda which provides business transaction information for Addenda Type +// Code 13 in a machine readable format. It is usually formatted according to ANSI, ASC, X13 Standard. +// +// # Addenda13 is mandatory for IAT entries +// +// The Addenda13 contains information related to the financial institution originating the entry. +// For inbound IAT entries, the Fourth Addenda Record must contain information to identify the +// foreign financial institution that is providing the funding and payment instruction for the IAT entry. +type Addenda13 struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // TypeCode Addenda13 types code '13' + TypeCode string `json:"typeCode"` + // Originating DFI Name + // For Outbound IAT Entries, this field must contain the name of the U.S. ODFI. + // For Inbound IATs: Name of the foreign bank providing funding for the payment transaction + ODFIName string `json:"ODFIName"` + // Originating DFI Identification Number Qualifier + // For Inbound IATs: The 2-digit code that identifies the numbering scheme used in the + // Foreign DFI Identification Number field: + // 01 = National Clearing System + // 02 = BIC Code + // 03 = IBAN Code + ODFIIDNumberQualifier string `json:"ODFIIDNumberQualifier"` + // Originating DFI Identification + // This field contains the routing number that identifies the U.S. ODFI initiating the entry. + // For Inbound IATs: This field contains the bank ID number of the Foreign Bank providing funding + // for the payment transaction. + ODFIIdentification string `json:"ODFIIdentification"` + // Originating DFI Branch Country Code + // USb” = United States + //(“b” indicates a blank space) + // For Inbound IATs: This 3 position field contains a 2-character code as approved by the + // International Organization for Standardization (ISO) used to identify the country in which + // the branch of the bank that originated the entry is located. Values for other countries can + // be found on the International Organization for Standardization website: www.iso.org. + ODFIBranchCountryCode string `json:"ODFIBranchCountryCode"` + // EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + // Detail or Corporate Entry Detail Record's trace number This number is + // the same as the last seven digits of the trace number of the related + // Entry Detail Record or Corporate Entry Detail Record. + EntryDetailSequenceNumber int `json:"entryDetailSequenceNumber"` + // validator is composed for data validation + validator + // converters is composed for ACH to GoLang Converters + converters +} + +// NewAddenda13 returns a new Addenda13 with default values for none exported fields +func NewAddenda13() *Addenda13 { + addenda13 := new(Addenda13) + addenda13.TypeCode = "13" + return addenda13 +} + +// Parse takes the input record string and parses the Addenda13 values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. +func (addenda13 *Addenda13) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + // 1-1 Always 7 + // 2-3 Always 13 + addenda13.TypeCode = record[1:3] + // 4-38 ODFIName + addenda13.ODFIName = strings.TrimSpace(record[3:38]) + // 39-40 ODFIIDNumberQualifier + addenda13.ODFIIDNumberQualifier = record[38:40] + // 41-74 ODFIIdentification + addenda13.ODFIIdentification = addenda13.parseStringField(record[40:74]) + // 75-77 + addenda13.ODFIBranchCountryCode = strings.TrimSpace(record[74:77]) + // 78-87 reserved - Leave blank + // 88-94 Contains the last seven digits of the number entered in the Trace Number field in the corresponding Entry Detail Record + addenda13.EntryDetailSequenceNumber = addenda13.parseNumField(record[87:94]) +} + +// String writes the Addenda13 struct to a 94 character string. +func (addenda13 *Addenda13) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(entryAddendaPos) + buf.WriteString(addenda13.TypeCode) + buf.WriteString(addenda13.ODFINameField()) + buf.WriteString(addenda13.ODFIIDNumberQualifierField()) + buf.WriteString(addenda13.ODFIIdentificationField()) + buf.WriteString(addenda13.ODFIBranchCountryCodeField()) + buf.WriteString(" ") + buf.WriteString(addenda13.EntryDetailSequenceNumberField()) + return buf.String() +} + +// Validate performs NACHA format rule checks on the record and returns an error if not Validated +// The first error encountered is returned and stops that parsing. +func (addenda13 *Addenda13) Validate() error { + if err := addenda13.fieldInclusion(); err != nil { + return err + } + if err := addenda13.isTypeCode(addenda13.TypeCode); err != nil { + return fieldError("TypeCode", err, addenda13.TypeCode) + } + // Type Code must be 13 + if addenda13.TypeCode != "13" { + return fieldError("TypeCode", ErrAddendaTypeCode, addenda13.TypeCode) + } + if err := addenda13.isAlphanumeric(addenda13.ODFIName); err != nil { + return fieldError("ODFIName", err, addenda13.ODFIName) + } + // Valid ODFI Identification Number Qualifier + if err := addenda13.isIDNumberQualifier(addenda13.ODFIIDNumberQualifier); err != nil { + return fieldError("ODFIIDNumberQualifier", err, addenda13.ODFIIDNumberQualifier) + } + if err := addenda13.isAlphanumeric(addenda13.ODFIIdentification); err != nil { + return fieldError("ODFIIdentification", err, addenda13.ODFIIdentification) + } + if err := addenda13.isAlphanumeric(addenda13.ODFIBranchCountryCode); err != nil { + return fieldError("ODFIBranchCountryCode", err, addenda13.ODFIBranchCountryCode) + } + return nil +} + +// fieldInclusion validate mandatory fields are not default values. If fields are +// invalid the ACH transfer will be returned. +func (addenda13 *Addenda13) fieldInclusion() error { + if addenda13.TypeCode == "" { + return fieldError("TypeCode", ErrConstructor, addenda13.TypeCode) + } + if addenda13.ODFIName == "" { + return fieldError("ODFIName", ErrConstructor, addenda13.ODFIName) + } + if addenda13.ODFIIDNumberQualifier == "" { + return fieldError("ODFIIDNumberQualifier", ErrConstructor, addenda13.ODFIIDNumberQualifier) + } + if addenda13.ODFIIdentification == "" { + return fieldError("ODFIIdentification", ErrConstructor, addenda13.ODFIIdentification) + } + if addenda13.ODFIBranchCountryCode == "" { + return fieldError("ODFIBranchCountryCode", ErrConstructor, addenda13.ODFIBranchCountryCode) + } + if addenda13.EntryDetailSequenceNumber == 0 { + return fieldError("EntryDetailSequenceNumber", ErrConstructor, addenda13.EntryDetailSequenceNumberField()) + } + return nil +} + +// ODFINameField gets the ODFIName field left padded +func (addenda13 *Addenda13) ODFINameField() string { + return addenda13.alphaField(addenda13.ODFIName, 35) +} + +// ODFIIDNumberQualifierField gets the ODFIIDNumberQualifier field left padded +func (addenda13 *Addenda13) ODFIIDNumberQualifierField() string { + return addenda13.alphaField(addenda13.ODFIIDNumberQualifier, 2) +} + +// ODFIIdentificationField gets the ODFIIdentificationCode field left padded +func (addenda13 *Addenda13) ODFIIdentificationField() string { + return addenda13.alphaField(addenda13.ODFIIdentification, 34) +} + +// ODFIBranchCountryCodeField gets the ODFIBranchCountryCode field left padded +func (addenda13 *Addenda13) ODFIBranchCountryCodeField() string { + return addenda13.alphaField(addenda13.ODFIBranchCountryCode, 3) +} + +// EntryDetailSequenceNumberField returns a zero padded EntryDetailSequenceNumber string +func (addenda13 *Addenda13) EntryDetailSequenceNumberField() string { + return addenda13.numericField(addenda13.EntryDetailSequenceNumber, 7) +} diff --git a/addenda13_test.go b/addenda13_test.go new file mode 100644 index 000000000..d5c0d0b4e --- /dev/null +++ b/addenda13_test.go @@ -0,0 +1,398 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockAddenda13 creates a mock Addenda13 record +func mockAddenda13() *Addenda13 { + addenda13 := NewAddenda13() + addenda13.ODFIName = "Wells Fargo" + addenda13.ODFIIDNumberQualifier = "01" + addenda13.ODFIIdentification = "121042882" + addenda13.ODFIBranchCountryCode = "US" + addenda13.EntryDetailSequenceNumber = 00000001 + return addenda13 +} + +// testAddenda13Parse parses Addenda13 record +func testAddenda13Parse(t testing.TB) { + Addenda13 := NewAddenda13() + line := "713Wells Fargo 01121042882 US 0000001" + Addenda13.Parse(line) + // walk the Addenda13 struct + if Addenda13.TypeCode != "13" { + t.Errorf("expected %v got %v", "13", Addenda13.TypeCode) + } + if Addenda13.ODFIName != "Wells Fargo" { + t.Errorf("expected %v got %v", "Wells Fargo", Addenda13.ODFIName) + } + if Addenda13.ODFIIDNumberQualifier != "01" { + t.Errorf("expected: %v got: %v", "01", Addenda13.ODFIIDNumberQualifier) + } + if Addenda13.ODFIIdentification != "121042882" { + t.Errorf("expected: %v got: %v", "121042882", Addenda13.ODFIIdentification) + } + if Addenda13.ODFIBranchCountryCode != "US" { + t.Errorf("expected: %s got: %s", "US", Addenda13.ODFIBranchCountryCode) + } + if Addenda13.EntryDetailSequenceNumber != 0000001 { + t.Errorf("expected: %v got: %v", 0000001, Addenda13.EntryDetailSequenceNumber) + } +} + +// TestAddenda13Parse tests parsing Addenda13 record +func TestAddenda13Parse(t *testing.T) { + testAddenda13Parse(t) +} + +// BenchmarkAddenda13Parse benchmarks parsing Addenda13 record +func BenchmarkAddenda13Parse(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda13Parse(b) + } +} + +// TestMockAddenda13 validates mockAddenda13 +func TestMockAddenda13(t *testing.T) { + addenda13 := mockAddenda13() + if err := addenda13.Validate(); err != nil { + t.Error("mockAddenda13 does not validate and will break other tests") + } +} + +// testAddenda13ValidTypeCode validates Addenda13 TypeCode +func testAddenda13ValidTypeCode(t testing.TB) { + addenda13 := mockAddenda13() + addenda13.TypeCode = "65" + err := addenda13.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda13ValidTypeCode tests validating Addenda13 TypeCode +func TestAddenda13ValidTypeCode(t *testing.T) { + testAddenda13ValidTypeCode(t) +} + +// BenchmarkAddenda13ValidTypeCode benchmarks validating Addenda13 TypeCode +func BenchmarkAddenda13ValidTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda13ValidTypeCode(b) + } +} + +// testAddenda13TypeCode13 TypeCode is 13 if TypeCode is a valid TypeCode +func testAddenda13TypeCode13(t testing.TB) { + addenda13 := mockAddenda13() + addenda13.TypeCode = "05" + err := addenda13.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda13TypeCode13 tests TypeCode is 13 if TypeCode is a valid TypeCode +func TestAddenda13TypeCode13(t *testing.T) { + testAddenda13TypeCode13(t) +} + +// BenchmarkAddenda13TypeCode13 benchmarks TypeCode is 13 if TypeCode is a valid TypeCode +func BenchmarkAddenda13TypeCode13(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda13TypeCode13(b) + } +} + +// testODFINameAlphaNumeric validates ODFIName is alphanumeric +func testODFINameAlphaNumeric(t testing.TB) { + addenda13 := mockAddenda13() + addenda13.ODFIName = "Wells®Fargo" + err := addenda13.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestODFINameAlphaNumeric tests validating ODFIName is alphanumeric +func TestODFINameAlphaNumeric(t *testing.T) { + testODFINameAlphaNumeric(t) +} + +// BenchmarkODFINameAlphaNumeric benchmarks validating ODFIName is alphanumeric +func BenchmarkODFINameAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testODFINameAlphaNumeric(b) + } +} + +// testODFIIDNumberQualifierValid validates ODFIIDNumberQualifier is valid +func testODFIIDNumberQualifierValid(t testing.TB) { + addenda13 := mockAddenda13() + addenda13.ODFIIDNumberQualifier = "®1" + err := addenda13.Validate() + if !base.Match(err, ErrIDNumberQualifier) { + t.Errorf("%T: %s", err, err) + } +} + +// TestODFIIDNumberQualifierValid tests validating ODFIIDNumberQualifier is valid +func TestODFIIDNumberQualifierValid(t *testing.T) { + testODFIIDNumberQualifierValid(t) +} + +// BenchmarkODFIIDNumberQualifierValid benchmarks validating ODFIIDNumberQualifier is valid +func BenchmarkODFIIDNumberQualifierValid(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testODFIIDNumberQualifierValid(b) + } +} + +// testODFIIdentificationAlphaNumeric validates ODFIIdentification is alphanumeric +func testODFIIdentificationAlphaNumeric(t testing.TB) { + addenda13 := mockAddenda13() + addenda13.ODFIIdentification = "®121042882" + err := addenda13.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestODFIIdentificationAlphaNumeric tests validating ODFIIdentification is alphanumeric +func TestODFIIdentificationAlphaNumeric(t *testing.T) { + testODFIIdentificationAlphaNumeric(t) +} + +// BenchmarkODFIIdentificationAlphaNumeric benchmarks validating ODFIIdentification is alphanumeric +func BenchmarkODFIIdentificationAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testODFIIdentificationAlphaNumeric(b) + } +} + +// testODFIBranchCountryCodeAlphaNumeric validates ODFIBranchCountryCode is alphanumeric +func testODFIBranchCountryCodeAlphaNumeric(t testing.TB) { + addenda13 := mockAddenda13() + addenda13.ODFIBranchCountryCode = "U®" + err := addenda13.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestODFIBranchCountryCodeAlphaNumeric tests validating ODFIBranchCountryCode is alphanumeric +func TestODFIBranchCountryCodeAlphaNumeric(t *testing.T) { + testODFIBranchCountryCodeAlphaNumeric(t) +} + +// BenchmarkODFIBranchCountryCodeAlphaNumeric benchmarks validating ODFIBranchCountryCode is alphanumeric +func BenchmarkODFIBranchCountryCodeAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testODFIBranchCountryCodeAlphaNumeric(b) + } +} + +// testAddenda13FieldInclusionTypeCode validates TypeCode fieldInclusion +func testAddenda13FieldInclusionTypeCode(t testing.TB) { + addenda13 := mockAddenda13() + addenda13.TypeCode = "" + err := addenda13.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda13FieldInclusionTypeCode tests validating TypeCode fieldInclusion +func TestAddenda13FieldInclusionTypeCode(t *testing.T) { + testAddenda13FieldInclusionTypeCode(t) +} + +// BenchmarkAddenda13FieldInclusionTypeCode benchmarks validating TypeCode fieldInclusion +func BenchmarkAddenda13FieldInclusionTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda13FieldInclusionTypeCode(b) + } +} + +// testAddenda13FieldInclusionODFIName validates ODFIName fieldInclusion +func testAddenda13FieldInclusionODFIName(t testing.TB) { + addenda13 := mockAddenda13() + addenda13.ODFIName = "" + err := addenda13.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda13FieldInclusionODFIName tests validating ODFIName fieldInclusion +func TestAddenda13FieldInclusionODFIName(t *testing.T) { + testAddenda13FieldInclusionODFIName(t) +} + +// BenchmarkAddenda13FieldInclusionODFIName benchmarks validating ODFIName fieldInclusion +func BenchmarkAddenda13FieldInclusionODFIName(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda13FieldInclusionODFIName(b) + } +} + +// testAddenda13FieldInclusionODFIIDNumberQualifier validates ODFIIDNumberQualifier fieldInclusion +func testAddenda13FieldInclusionODFIIDNumberQualifier(t testing.TB) { + addenda13 := mockAddenda13() + addenda13.ODFIIDNumberQualifier = "" + err := addenda13.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda13FieldInclusionODFIIDNumberQualifier tests validating ODFIIDNumberQualifier fieldInclusion +func TestAddenda13FieldInclusionODFIIDNumberQualifier(t *testing.T) { + testAddenda13FieldInclusionODFIIDNumberQualifier(t) +} + +// BenchmarkAddenda13FieldInclusionODFIIDNumberQualifier benchmarks validating ODFIIDNumberQualifier fieldInclusion +func BenchmarkAddenda13FieldInclusionODFIIDNumberQualifier(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda13FieldInclusionODFIIDNumberQualifier(b) + } +} + +// testAddenda13FieldInclusionODFIIdentification validates ODFIIdentification fieldInclusion +func testAddenda13FieldInclusionODFIIdentification(t testing.TB) { + addenda13 := mockAddenda13() + addenda13.ODFIIdentification = "" + err := addenda13.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda13FieldInclusionODFIIdentification tests validating ODFIIdentification fieldInclusion +func TestAddenda13FieldInclusionODFIIdentification(t *testing.T) { + testAddenda13FieldInclusionODFIIdentification(t) +} + +// BenchmarkAddenda13FieldInclusionODFIIdentification benchmarks validating ODFIIdentification fieldInclusion +func BenchmarkAddenda13FieldInclusionODFIIdentification(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda13FieldInclusionODFIIdentification(b) + } +} + +// testAddenda13FieldInclusionODFIBranchCountryCode validates ODFIBranchCountryCode fieldInclusion +func testAddenda13FieldInclusionODFIBranchCountryCode(t testing.TB) { + addenda13 := mockAddenda13() + addenda13.ODFIBranchCountryCode = "" + err := addenda13.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda13FieldInclusionODFIBranchCountryCode tests validating ODFIBranchCountryCode fieldInclusion +func TestAddenda13FieldInclusionODFIBranchCountryCode(t *testing.T) { + testAddenda13FieldInclusionODFIBranchCountryCode(t) +} + +// BenchmarkAddenda13FieldInclusionODFIBranchCountryCode benchmarks validating ODFIBranchCountryCode fieldInclusion +func BenchmarkAddenda13FieldInclusionODFIBranchCountryCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda13FieldInclusionODFIBranchCountryCode(b) + } +} + +// testAddenda13FieldInclusionEntryDetailSequenceNumber validates EntryDetailSequenceNumber fieldInclusion +func testAddenda13FieldInclusionEntryDetailSequenceNumber(t testing.TB) { + addenda13 := mockAddenda13() + addenda13.EntryDetailSequenceNumber = 0 + err := addenda13.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda13FieldInclusionEntryDetailSequenceNumber tests validating +// EntryDetailSequenceNumber fieldInclusion +func TestAddenda13FieldInclusionEntryDetailSequenceNumber(t *testing.T) { + testAddenda13FieldInclusionEntryDetailSequenceNumber(t) +} + +// BenchmarkAddenda13FieldInclusionEntryDetailSequenceNumber benchmarks validating +// EntryDetailSequenceNumber fieldInclusion +func BenchmarkAddenda13FieldInclusionEntryDetailSequenceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda13FieldInclusionEntryDetailSequenceNumber(b) + } +} + +// TestAddenda13String validates that a known parsed Addenda13 record can be return to a string of the same value +func testAddenda13String(t testing.TB) { + addenda13 := NewAddenda13() + var line = "713Wells Fargo 121042882 US 0000001" + addenda13.Parse(line) + + if addenda13.String() != line { + t.Errorf("Strings do not match") + } + if addenda13.TypeCode != "13" { + t.Errorf("TypeCode Expected 13 got: %v", addenda13.TypeCode) + } +} + +// TestAddenda13String tests validating that a known parsed Addenda13 record can be return to a string of the same value +func TestAddenda13String(t *testing.T) { + testAddenda13String(t) +} + +// BenchmarkAddenda13String benchmarks validating that a known parsed Addenda13 record can be return to a string of the same value +func BenchmarkAddenda13String(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda13String(b) + } +} + +// TestAddenda13RuneCountInString validates RuneCountInString +func TestAddenda13RuneCountInString(t *testing.T) { + addenda13 := NewAddenda13() + var line = "713Wells Fargo 121042882 US" + addenda13.Parse(line) + + if addenda13.ODFIBranchCountryCode != "" { + t.Error("Parsed with an invalid RuneCountInString not equal to 94") + } +} diff --git a/addenda14.go b/addenda14.go new file mode 100644 index 000000000..740604e61 --- /dev/null +++ b/addenda14.go @@ -0,0 +1,191 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "unicode/utf8" +) + +// Addenda14 is an addenda which provides business transaction information for Addenda Type +// Code 14 in a machine readable format. It is usually formatted according to ANSI, ASC, X14 Standard. +// +// # Addenda14 is mandatory for IAT entries +// +// The Addenda14 identifies the Receiving financial institution holding the Receiver's account. +type Addenda14 struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // TypeCode Addenda14 types code '14' + TypeCode string `json:"typeCode"` + // Receiving DFI Name + // Name of the Receiver's bank + RDFIName string `json:"RDFIName"` + // Receiving DFI Identification Number Qualifier + // The 2-digit code that identifies the numbering scheme used in the + // Receiving DFI Identification Number field: + // 01 = National Clearing System + // 02 = BIC Code + // 03 = IBAN Code + RDFIIDNumberQualifier string `json:"RDFIIDNumberQualifier"` + // Receiving DFI Identification + // This field contains the bank identification number of the DFI at which the + // Receiver maintains his account. + RDFIIdentification string `json:"RDFIIdentification"` + // Receiving DFI Branch Country Code + // USb” = United States + //(“b” indicates a blank space) + // This 3 position field contains a 2-character code as approved by the International + // Organization for Standardization (ISO) used to identify the country in which the + // branch of the bank that receives the entry is located. Values for other countries can + // be found on the International Organization for Standardization website: www.iso.org + RDFIBranchCountryCode string `json:"RDFIBranchCountryCode"` + // EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + // Detail or Corporate Entry Detail Record's trace number This number is + // the same as the last seven digits of the trace number of the related + // Entry Detail Record or Corporate Entry Detail Record. + EntryDetailSequenceNumber int `json:"entryDetailSequenceNumber"` + // validator is composed for data validation + validator + // converters is composed for ACH to GoLang Converters + converters +} + +// NewAddenda14 returns a new Addenda14 with default values for none exported fields +func NewAddenda14() *Addenda14 { + addenda14 := new(Addenda14) + addenda14.TypeCode = "14" + return addenda14 +} + +// Parse takes the input record string and parses the Addenda14 values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. +func (addenda14 *Addenda14) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + // 1-1 Always 7 + // 2-3 Always 14 + addenda14.TypeCode = record[1:3] + // 4-38 RDFIName + addenda14.RDFIName = strings.TrimSpace(record[3:38]) + // 39-40 RDFIIDNumberQualifier + addenda14.RDFIIDNumberQualifier = record[38:40] + // 41-74 RDFIIdentification + addenda14.RDFIIdentification = addenda14.parseStringField(record[40:74]) + // 75-77 + addenda14.RDFIBranchCountryCode = strings.TrimSpace(record[74:77]) + // 78-87 reserved - Leave blank + // 88-94 Contains the last seven digits of the number entered in the Trace Number field in the corresponding Entry Detail Record + addenda14.EntryDetailSequenceNumber = addenda14.parseNumField(record[87:94]) +} + +// String writes the Addenda14 struct to a 94 character string. +func (addenda14 *Addenda14) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(entryAddendaPos) + buf.WriteString(addenda14.TypeCode) + buf.WriteString(addenda14.RDFINameField()) + buf.WriteString(addenda14.RDFIIDNumberQualifierField()) + buf.WriteString(addenda14.RDFIIdentificationField()) + buf.WriteString(addenda14.RDFIBranchCountryCodeField()) + buf.WriteString(" ") + buf.WriteString(addenda14.EntryDetailSequenceNumberField()) + return buf.String() +} + +// Validate performs NACHA format rule checks on the record and returns an error if not Validated +// The first error encountered is returned and stops that parsing. +func (addenda14 *Addenda14) Validate() error { + if err := addenda14.fieldInclusion(); err != nil { + return err + } + if err := addenda14.isTypeCode(addenda14.TypeCode); err != nil { + return fieldError("TypeCode", err, addenda14.TypeCode) + } + // Type Code must be 14 + if addenda14.TypeCode != "14" { + return fieldError("TypeCode", ErrAddendaTypeCode, addenda14.TypeCode) + } + if err := addenda14.isAlphanumeric(addenda14.RDFIName); err != nil { + return fieldError("RDFIName", err, addenda14.RDFIName) + } + // Valid RDFI Identification Number Qualifier + if err := addenda14.isIDNumberQualifier(addenda14.RDFIIDNumberQualifier); err != nil { + return fieldError("RDFIIDNumberQualifier", ErrIDNumberQualifier, addenda14.RDFIIDNumberQualifier) + } + if err := addenda14.isAlphanumeric(addenda14.RDFIIdentification); err != nil { + return fieldError("RDFIIdentification", err, addenda14.RDFIIdentification) + } + if err := addenda14.isAlphanumeric(addenda14.RDFIBranchCountryCode); err != nil { + return fieldError("RDFIBranchCountryCode", err, addenda14.RDFIBranchCountryCode) + } + return nil +} + +// fieldInclusion validate mandatory fields are not default values. If fields are +// invalid the ACH transfer will be returned. +func (addenda14 *Addenda14) fieldInclusion() error { + if addenda14.TypeCode == "" { + return fieldError("TypeCode", ErrConstructor, addenda14.TypeCode) + } + if addenda14.RDFIName == "" { + return fieldError("RDFIName", ErrConstructor, addenda14.RDFIName) + } + if addenda14.RDFIIDNumberQualifier == "" { + return fieldError("RDFIIDNumberQualifier", ErrConstructor, addenda14.RDFIIDNumberQualifier) + } + if addenda14.RDFIIdentification == "" { + return fieldError("RDFIIdentification", ErrConstructor, addenda14.RDFIIdentification) + } + if addenda14.RDFIBranchCountryCode == "" { + return fieldError("RDFIBranchCountryCode", ErrConstructor, addenda14.RDFIBranchCountryCode) + } + if addenda14.EntryDetailSequenceNumber == 0 { + return fieldError("EntryDetailSequenceNumber", ErrConstructor, addenda14.EntryDetailSequenceNumberField()) + } + return nil +} + +// RDFINameField gets the RDFIName field left padded +func (addenda14 *Addenda14) RDFINameField() string { + return addenda14.alphaField(addenda14.RDFIName, 35) +} + +// RDFIIDNumberQualifierField gets the RDFIIDNumberQualifier field left padded +func (addenda14 *Addenda14) RDFIIDNumberQualifierField() string { + return addenda14.alphaField(addenda14.RDFIIDNumberQualifier, 2) +} + +// RDFIIdentificationField gets the RDFIIdentificationCode field left padded +func (addenda14 *Addenda14) RDFIIdentificationField() string { + return addenda14.alphaField(addenda14.RDFIIdentification, 34) +} + +// RDFIBranchCountryCodeField gets the RDFIBranchCountryCode field left padded +func (addenda14 *Addenda14) RDFIBranchCountryCodeField() string { + return addenda14.alphaField(addenda14.RDFIBranchCountryCode, 3) +} + +// EntryDetailSequenceNumberField returns a zero padded EntryDetailSequenceNumber string +func (addenda14 *Addenda14) EntryDetailSequenceNumberField() string { + return addenda14.numericField(addenda14.EntryDetailSequenceNumber, 7) +} diff --git a/addenda14_test.go b/addenda14_test.go new file mode 100644 index 000000000..73521d169 --- /dev/null +++ b/addenda14_test.go @@ -0,0 +1,399 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockAddenda14 creates a mock Addenda14 record +func mockAddenda14() *Addenda14 { + addenda14 := NewAddenda14() + addenda14.RDFIName = "Citadel Bank" + addenda14.RDFIIDNumberQualifier = "01" + addenda14.RDFIIdentification = "231380104" + addenda14.RDFIBranchCountryCode = "US" + addenda14.EntryDetailSequenceNumber = 00000001 + return addenda14 +} + +// TestMockAddenda14 validates mockAddenda14 +func TestMockAddenda14(t *testing.T) { + addenda14 := mockAddenda14() + if err := addenda14.Validate(); err != nil { + t.Error("mockAddenda14 does not validate and will break other tests") + } +} + +// testAddenda14Parse parses Addenda14 record +func testAddenda14Parse(t testing.TB) { + Addenda14 := NewAddenda14() + line := "714Citadel Bank 01231380104 US 0000001" + Addenda14.Parse(line) + // walk the Addenda14 struct + if Addenda14.TypeCode != "14" { + t.Errorf("expected %v got %v", "14", Addenda14.TypeCode) + } + if Addenda14.RDFIName != "Citadel Bank" { + t.Errorf("expected %v got %v", "Citadel Bank", Addenda14.RDFIName) + } + if Addenda14.RDFIIDNumberQualifier != "01" { + t.Errorf("expected: %v got: %v", "01", Addenda14.RDFIIDNumberQualifier) + } + if Addenda14.RDFIIdentification != "231380104" { + t.Errorf("expected: %v got: %v", "928383-23938", Addenda14.RDFIIdentification) + } + if Addenda14.RDFIBranchCountryCode != "US" { + t.Errorf("expected: %s got: %s", "US", Addenda14.RDFIBranchCountryCode) + } + if Addenda14.EntryDetailSequenceNumber != 0000001 { + t.Errorf("expected: %v got: %v", 0000001, Addenda14.EntryDetailSequenceNumber) + } +} + +// TestAddenda14Parse tests parsing Addenda14 record +func TestAddenda14Parse(t *testing.T) { + testAddenda14Parse(t) +} + +// BenchmarkAddenda14Parse benchmarks parsing Addenda14 record +func BenchmarkAddenda14Parse(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda14Parse(b) + } +} + +// testAddenda14ValidTypeCode validates Addenda14 TypeCode +func testAddenda14ValidTypeCode(t testing.TB) { + addenda14 := mockAddenda14() + addenda14.TypeCode = "65" + err := addenda14.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda14ValidTypeCode tests validating Addenda14 TypeCode +func TestAddenda14ValidTypeCode(t *testing.T) { + testAddenda14ValidTypeCode(t) +} + +// BenchmarkAddenda14ValidTypeCode benchmarks validating Addenda14 TypeCode +func BenchmarkAddenda14ValidTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda14ValidTypeCode(b) + } +} + +// testAddenda14TypeCode14 TypeCode is 14 if TypeCode is a valid TypeCode +func testAddenda14TypeCode14(t testing.TB) { + addenda14 := mockAddenda14() + addenda14.TypeCode = "05" + err := addenda14.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda14TypeCode14 tests TypeCode is 14 if TypeCode is a valid TypeCode +func TestAddenda14TypeCode14(t *testing.T) { + testAddenda14TypeCode14(t) +} + +// BenchmarkAddenda14TypeCode14 benchmarks TypeCode is 14 if TypeCode is a valid TypeCode +func BenchmarkAddenda14TypeCode14(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda14TypeCode14(b) + } +} + +// testRDFINameAlphaNumeric validates RDFIName is alphanumeric +func testRDFINameAlphaNumeric(t testing.TB) { + addenda14 := mockAddenda14() + addenda14.RDFIName = "Wells®Fargo" + err := addenda14.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestRDFINameAlphaNumeric tests validating RDFIName is alphanumeric +func TestRDFINameAlphaNumeric(t *testing.T) { + testRDFINameAlphaNumeric(t) +} + +// BenchmarkRDFINameAlphaNumeric benchmarks validating RDFIName is alphanumeric +func BenchmarkRDFINameAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testRDFINameAlphaNumeric(b) + } +} + +// testRDFIIDNumberQualifierValid validates RDFIIDNumberQualifier is valid +func testRDFIIDNumberQualifierValid(t testing.TB) { + addenda14 := mockAddenda14() + addenda14.RDFIIDNumberQualifier = "®1" + err := addenda14.Validate() + if !base.Match(err, ErrIDNumberQualifier) { + t.Errorf("%T: %s", err, err) + } +} + +// TestRDFIIDNumberQualifierValid tests validating RDFIIDNumberQualifier is valid +func TestRDFIIDNumberQualifierValid(t *testing.T) { + testRDFIIDNumberQualifierValid(t) +} + +// BenchmarkRDFIIDNumberQualifierValid benchmarks validating RDFIIDNumberQualifier is valid +func BenchmarkRDFIIDNumberQualifierValid(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testRDFIIDNumberQualifierValid(b) + } +} + +// testRDFIIdentificationAlphaNumeric validates RDFIIdentification is alphanumeric +func testRDFIIdentificationAlphaNumeric(t testing.TB) { + addenda14 := mockAddenda14() + addenda14.RDFIIdentification = "®121042882" + err := addenda14.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestRDFIIdentificationAlphaNumeric tests validating RDFIIdentification is alphanumeric +func TestRDFIIdentificationAlphaNumeric(t *testing.T) { + testRDFIIdentificationAlphaNumeric(t) +} + +// BenchmarkRDFIIdentificationAlphaNumeric benchmarks validating RDFIIdentification is alphanumeric +func BenchmarkRDFIIdentificationAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testRDFIIdentificationAlphaNumeric(b) + } +} + +// testRDFIBranchCountryCodeAlphaNumeric validates RDFIBranchCountryCode is alphanumeric +func testRDFIBranchCountryCodeAlphaNumeric(t testing.TB) { + addenda14 := mockAddenda14() + addenda14.RDFIBranchCountryCode = "U®" + err := addenda14.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestRDFIBranchCountryCodeAlphaNumeric tests validating RDFIBranchCountryCode is alphanumeric +func TestRDFIBranchCountryCodeAlphaNumeric(t *testing.T) { + testRDFIBranchCountryCodeAlphaNumeric(t) +} + +// BenchmarkRDFIBranchCountryCodeAlphaNumeric benchmarks validating RDFIBranchCountryCode is alphanumeric +func BenchmarkRDFIBranchCountryCodeAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testRDFIBranchCountryCodeAlphaNumeric(b) + } +} + +// testAddenda14FieldInclusionTypeCode validates TypeCode fieldInclusion +func testAddenda14FieldInclusionTypeCode(t testing.TB) { + addenda14 := mockAddenda14() + addenda14.TypeCode = "" + err := addenda14.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda14FieldInclusionTypeCode tests validating TypeCode fieldInclusion +func TestAddenda14FieldInclusionTypeCode(t *testing.T) { + testAddenda14FieldInclusionTypeCode(t) +} + +// BenchmarkAddenda14FieldInclusionTypeCode benchmarks validating TypeCode fieldInclusion +func BenchmarkAddenda14FieldInclusionTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda14FieldInclusionTypeCode(b) + } +} + +// testAddenda14FieldInclusionRDFIName validates RDFIName fieldInclusion +func testAddenda14FieldInclusionRDFIName(t testing.TB) { + addenda14 := mockAddenda14() + addenda14.RDFIName = "" + err := addenda14.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda14FieldInclusionRDFIName tests validating RDFIName fieldInclusion +func TestAddenda14FieldInclusionRDFIName(t *testing.T) { + testAddenda14FieldInclusionRDFIName(t) +} + +// BenchmarkAddenda14FieldInclusionRDFIName benchmarks validating RDFIName fieldInclusion +func BenchmarkAddenda14FieldInclusionRDFIName(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda14FieldInclusionRDFIName(b) + } +} + +// testAddenda14FieldInclusionRDFIIDNumberQualifier validates RDFIIDNumberQualifier fieldInclusion +func testAddenda14FieldInclusionRDFIIDNumberQualifier(t testing.TB) { + addenda14 := mockAddenda14() + addenda14.RDFIIDNumberQualifier = "" + err := addenda14.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda14FieldInclusionRDFIIdNumberQualifier tests validating RDFIIdNumberQualifier fieldInclusion +func TestAddenda14FieldInclusionRDFIIdNumberQualifier(t *testing.T) { + testAddenda14FieldInclusionRDFIIDNumberQualifier(t) +} + +// BenchmarkAddenda14FieldInclusionRDFIIdNumberQualifier benchmarks validating RDFIIdNumberQualifier fieldInclusion +func BenchmarkAddenda14FieldInclusionRDFIIdNumberQualifier(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda14FieldInclusionRDFIIDNumberQualifier(b) + } +} + +// testAddenda14FieldInclusionRDFIIdentification validates RDFIIdentification fieldInclusion +func testAddenda14FieldInclusionRDFIIdentification(t testing.TB) { + addenda14 := mockAddenda14() + addenda14.RDFIIdentification = "" + err := addenda14.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda14FieldInclusionRDFIIdentification tests validating RDFIIdentification fieldInclusion +func TestAddenda14FieldInclusionRDFIIdentification(t *testing.T) { + testAddenda14FieldInclusionRDFIIdentification(t) +} + +// BenchmarkAddenda14FieldInclusionRDFIIdentification benchmarks validating RDFIIdentification fieldInclusion +func BenchmarkAddenda14FieldInclusionRDFIIdentification(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda14FieldInclusionRDFIIdentification(b) + } +} + +// testAddenda14FieldInclusionRDFIBranchCountryCode validates RDFIBranchCountryCode fieldInclusion +func testAddenda14FieldInclusionRDFIBranchCountryCode(t testing.TB) { + addenda14 := mockAddenda14() + addenda14.RDFIBranchCountryCode = "" + err := addenda14.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda14FieldInclusionRDFIBranchCountryCode tests validating RDFIBranchCountryCode fieldInclusion +func TestAddenda14FieldInclusionRDFIBranchCountryCode(t *testing.T) { + testAddenda14FieldInclusionRDFIBranchCountryCode(t) +} + +// BenchmarkAddenda14FieldInclusionRDFIBranchCountryCode benchmarks validating RDFIBranchCountryCode fieldInclusion +func BenchmarkAddenda14FieldInclusionRDFIBranchCountryCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda14FieldInclusionRDFIBranchCountryCode(b) + } +} + +// testAddenda14FieldInclusionEntryDetailSequenceNumber validates EntryDetailSequenceNumber fieldInclusion +func testAddenda14FieldInclusionEntryDetailSequenceNumber(t testing.TB) { + addenda14 := mockAddenda14() + addenda14.EntryDetailSequenceNumber = 0 + err := addenda14.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda14FieldInclusionEntryDetailSequenceNumber tests validating +// EntryDetailSequenceNumber fieldInclusion +func TestAddenda14FieldInclusionEntryDetailSequenceNumber(t *testing.T) { + testAddenda14FieldInclusionEntryDetailSequenceNumber(t) +} + +// BenchmarkAddenda14FieldInclusionEntryDetailSequenceNumber benchmarks validating +// EntryDetailSequenceNumber fieldInclusion +func BenchmarkAddenda14FieldInclusionEntryDetailSequenceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda14FieldInclusionEntryDetailSequenceNumber(b) + } +} + +// TestAddenda14String validates that a known parsed Addenda14 record can be return to a string of the same value +func testAddenda14String(t testing.TB) { + addenda14 := NewAddenda14() + var line = "714Citadel Bank 231380104 US 0000001" + + addenda14.Parse(line) + + if addenda14.String() != line { + t.Errorf("Strings do not match") + } + if addenda14.TypeCode != "14" { + t.Errorf("TypeCode Expected 14 got: %v", addenda14.TypeCode) + } +} + +// TestAddenda14String tests validating that a known parsed Addenda14 record can be return to a string of the same value +func TestAddenda14String(t *testing.T) { + testAddenda14String(t) +} + +// BenchmarkAddenda14String benchmarks validating that a known parsed Addenda14 record can be return to a string of the same value +func BenchmarkAddenda14String(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda14String(b) + } +} + +// TestAddenda14RuneCountInString validates RuneCountInString +func TestAddenda14RuneCountInString(t *testing.T) { + addenda14 := NewAddenda14() + var line = "714Citadel Bank 231380104 US" + addenda14.Parse(line) + + if addenda14.RDFIBranchCountryCode != "" { + t.Error("Parsed with an invalid RuneCountInString not equal to 94") + } +} diff --git a/addenda15.go b/addenda15.go new file mode 100644 index 000000000..4e6caad4a --- /dev/null +++ b/addenda15.go @@ -0,0 +1,143 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "unicode/utf8" +) + +// Addenda15 is an addenda which provides business transaction information for Addenda Type +// Code 15 in a machine readable format. It is usually formatted according to ANSI, ASC, X12 Standard. +// +// # Addenda15 is mandatory for IAT entries +// +// The Addenda15 record identifies key information related to the Receiver. +type Addenda15 struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // TypeCode Addenda15 types code '15' + TypeCode string `json:"typeCode"` + // Receiver Identification Number contains the accounting number by which the Originator is known to + // the Receiver for descriptive purposes. NACHA Rules recommend but do not require the RDFI to print + // the contents of this field on the receiver's statement. + ReceiverIDNumber string `json:"receiverIDNumber,omitempty"` + // Receiver Street Address contains the Receiver's physical address + ReceiverStreetAddress string `json:"receiverStreetAddress"` + // EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + // Detail or Corporate Entry Detail Record's trace number This number is + // the same as the last seven digits of the trace number of the related + // Entry Detail Record or Corporate Entry Detail Record. + EntryDetailSequenceNumber int `json:"entryDetailSequenceNumber"` + // validator is composed for data validation + validator + // converters is composed for ACH to GoLang Converters + converters +} + +// NewAddenda15 returns a new Addenda15 with default values for none exported fields +func NewAddenda15() *Addenda15 { + addenda15 := new(Addenda15) + addenda15.TypeCode = "15" + return addenda15 +} + +// Parse takes the input record string and parses the Addenda15 values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. +func (addenda15 *Addenda15) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + // 1-1 Always 7 + // 2-3 Always 15 + addenda15.TypeCode = record[1:3] + // 4-18 + addenda15.ReceiverIDNumber = addenda15.parseStringField(record[3:18]) + // 19-53 + addenda15.ReceiverStreetAddress = strings.TrimSpace(record[18:53]) + // 54-87 reserved - Leave blank + // 88-94 Contains the last seven digits of the number entered in the Trace Number field in the corresponding Entry Detail Record + addenda15.EntryDetailSequenceNumber = addenda15.parseNumField(record[87:94]) +} + +// String writes the Addenda15 struct to a 94 character string. +func (addenda15 *Addenda15) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(entryAddendaPos) + buf.WriteString(addenda15.TypeCode) + buf.WriteString(addenda15.ReceiverIDNumberField()) + buf.WriteString(addenda15.ReceiverStreetAddressField()) + buf.WriteString(" ") + buf.WriteString(addenda15.EntryDetailSequenceNumberField()) + return buf.String() +} + +// Validate performs NACHA format rule checks on the record and returns an error if not Validated +// The first error encountered is returned and stops that parsing. +func (addenda15 *Addenda15) Validate() error { + if err := addenda15.fieldInclusion(); err != nil { + return err + } + if err := addenda15.isTypeCode(addenda15.TypeCode); err != nil { + return fieldError("TypeCode", err, addenda15.TypeCode) + } + // Type Code must be 15 + if addenda15.TypeCode != "15" { + return fieldError("TypeCode", ErrAddendaTypeCode, addenda15.TypeCode) + } + if err := addenda15.isAlphanumeric(addenda15.ReceiverIDNumber); err != nil { + return fieldError("ReceiverIDNumber", err, addenda15.ReceiverIDNumber) + } + if err := addenda15.isAlphanumeric(addenda15.ReceiverStreetAddress); err != nil { + return fieldError("ReceiverStreetAddress", err, addenda15.ReceiverStreetAddress) + } + return nil +} + +// fieldInclusion validate mandatory fields are not default values. If fields are +// invalid the ACH transfer will be returned. +func (addenda15 *Addenda15) fieldInclusion() error { + if addenda15.TypeCode == "" { + return fieldError("TypeCode", ErrConstructor, addenda15.TypeCode) + } + if addenda15.ReceiverStreetAddress == "" { + return fieldError("ReceiverStreetAddress", ErrConstructor, addenda15.ReceiverStreetAddress) + } + if addenda15.EntryDetailSequenceNumber == 0 { + return fieldError("EntryDetailSequenceNumber", ErrConstructor, addenda15.EntryDetailSequenceNumberField()) + } + return nil +} + +// ReceiverIDNumberField gets the ReceiverIDNumber field left padded +func (addenda15 *Addenda15) ReceiverIDNumberField() string { + return addenda15.alphaField(addenda15.ReceiverIDNumber, 15) +} + +// ReceiverStreetAddressField gets the ReceiverStreetAddressField field left padded +func (addenda15 *Addenda15) ReceiverStreetAddressField() string { + return addenda15.alphaField(addenda15.ReceiverStreetAddress, 35) +} + +// EntryDetailSequenceNumberField returns a zero padded EntryDetailSequenceNumber string +func (addenda15 *Addenda15) EntryDetailSequenceNumberField() string { + return addenda15.numericField(addenda15.EntryDetailSequenceNumber, 7) +} diff --git a/addenda15_test.go b/addenda15_test.go new file mode 100644 index 000000000..24cab9c48 --- /dev/null +++ b/addenda15_test.go @@ -0,0 +1,275 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockAddenda15 creates a mock Addenda15 record +func mockAddenda15() *Addenda15 { + addenda15 := NewAddenda15() + addenda15.ReceiverIDNumber = "987465493213987" + addenda15.ReceiverStreetAddress = "2121 Front Street" + addenda15.EntryDetailSequenceNumber = 00000001 + return addenda15 +} + +// TestMockAddenda15 validates mockAddenda15 +func TestMockAddenda15(t *testing.T) { + addenda15 := mockAddenda15() + if err := addenda15.Validate(); err != nil { + t.Error("mockAddenda15 does not validate and will break other tests") + } +} + +// testAddenda15Parse parses Addenda15 record +func testAddenda15Parse(t testing.TB) { + Addenda15 := NewAddenda15() + line := "7159874654932139872121 Front Street 0000001" + Addenda15.Parse(line) + // walk the Addenda15 struct + if Addenda15.TypeCode != "15" { + t.Errorf("expected %v got %v", "15", Addenda15.TypeCode) + } + if Addenda15.ReceiverIDNumber != "987465493213987" { + t.Errorf("expected %v got %v", "987465493213987", Addenda15.ReceiverIDNumber) + } + if Addenda15.ReceiverStreetAddress != "2121 Front Street" { + t.Errorf("expected: %v got: %v", "2121 Front Street", Addenda15.ReceiverStreetAddress) + } + if Addenda15.EntryDetailSequenceNumber != 0000001 { + t.Errorf("expected: %v got: %v", 0000001, Addenda15.EntryDetailSequenceNumber) + } +} + +// TestAddenda15Parse tests parsing Addenda15 record +func TestAddenda15Parse(t *testing.T) { + testAddenda15Parse(t) +} + +// BenchmarkAddenda15Parse benchmarks parsing Addenda15 record +func BenchmarkAddenda15Parse(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda15Parse(b) + } +} + +// testAddenda15ValidTypeCode validates Addenda15 TypeCode +func testAddenda15ValidTypeCode(t testing.TB) { + addenda15 := mockAddenda15() + addenda15.TypeCode = "65" + err := addenda15.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda15ValidTypeCode tests validating Addenda15 TypeCode +func TestAddenda15ValidTypeCode(t *testing.T) { + testAddenda15ValidTypeCode(t) +} + +// BenchmarkAddenda15ValidTypeCode benchmarks validating Addenda15 TypeCode +func BenchmarkAddenda15ValidTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda15ValidTypeCode(b) + } +} + +// testAddenda15TypeCode15 TypeCode is 15 if TypeCode is a valid TypeCode +func testAddenda15TypeCode15(t testing.TB) { + addenda15 := mockAddenda15() + addenda15.TypeCode = "05" + err := addenda15.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda15TypeCode15 tests TypeCode is 15 if TypeCode is a valid TypeCode +func TestAddenda15TypeCode15(t *testing.T) { + testAddenda15TypeCode15(t) +} + +// BenchmarkAddenda15TypeCode15 benchmarks TypeCode is 15 if TypeCode is a valid TypeCode +func BenchmarkAddenda15TypeCode15(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda15TypeCode15(b) + } +} + +// testReceiverIDNumberAlphaNumeric validates ReceiverIDNumber is alphanumeric +func testReceiverIDNumberAlphaNumeric(t testing.TB) { + addenda15 := mockAddenda15() + addenda15.ReceiverIDNumber = "9874654932®1398" + err := addenda15.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestReceiverIDNumberAlphaNumeric tests validating ReceiverIDNumber is alphanumeric +func TestReceiverIDNumberAlphaNumeric(t *testing.T) { + testReceiverIDNumberAlphaNumeric(t) +} + +// BenchmarkReceiverIDNumberAlphaNumeric benchmarks validating ReceiverIDNumber is alphanumeric +func BenchmarkReceiverIDNumberAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testReceiverIDNumberAlphaNumeric(b) + } +} + +// testReceiverStreetAddressAlphaNumeric validates ReceiverStreetAddress is alphanumeric +func testReceiverStreetAddressAlphaNumeric(t testing.TB) { + addenda15 := mockAddenda15() + addenda15.ReceiverStreetAddress = "2121 Fr®nt Street" + err := addenda15.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestReceiverStreetAddressAlphaNumeric tests validating ReceiverStreetAddress is alphanumeric +func TestReceiverStreetAddressAlphaNumeric(t *testing.T) { + testReceiverStreetAddressAlphaNumeric(t) +} + +// BenchmarkReceiverStreetAddressAlphaNumeric benchmarks validating ReceiverStreetAddress is alphanumeric +func BenchmarkReceiverStreetAddressAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testReceiverStreetAddressAlphaNumeric(b) + } +} + +// testAddenda15FieldInclusionTypeCode validates TypeCode fieldInclusion +func testAddenda15FieldInclusionTypeCode(t testing.TB) { + addenda15 := mockAddenda15() + addenda15.TypeCode = "" + err := addenda15.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda15FieldInclusionTypeCode tests validating TypeCode fieldInclusion +func TestAddenda15FieldInclusionTypeCode(t *testing.T) { + testAddenda15FieldInclusionTypeCode(t) +} + +// BenchmarkAddenda15FieldInclusionTypeCode benchmarks validating TypeCode fieldInclusion +func BenchmarkAddenda15FieldInclusionTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda15FieldInclusionTypeCode(b) + } +} + +// testAddenda15FieldInclusionReceiverStreetAddress validates ReceiverStreetAddress fieldInclusion +func testAddenda15FieldInclusionReceiverStreetAddress(t testing.TB) { + addenda15 := mockAddenda15() + addenda15.ReceiverStreetAddress = "" + err := addenda15.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda15FieldInclusionReceiverStreetAddress tests validating ReceiverStreetAddress fieldInclusion +func TestAddenda15FieldInclusionReceiverStreetAddress(t *testing.T) { + testAddenda15FieldInclusionReceiverStreetAddress(t) +} + +// BenchmarkAddenda15FieldInclusionReceiverStreetAddress benchmarks validating ReceiverStreetAddress fieldInclusion +func BenchmarkAddenda15FieldInclusionReceiverStreetAddress(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda15FieldInclusionReceiverStreetAddress(b) + } +} + +// testAddenda15FieldInclusionEntryDetailSequenceNumber validates EntryDetailSequenceNumber fieldInclusion +func testAddenda15FieldInclusionEntryDetailSequenceNumber(t testing.TB) { + addenda15 := mockAddenda15() + addenda15.EntryDetailSequenceNumber = 0 + err := addenda15.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda15FieldInclusionEntryDetailSequenceNumber tests validating +// EntryDetailSequenceNumber fieldInclusion +func TestAddenda15FieldInclusionEntryDetailSequenceNumber(t *testing.T) { + testAddenda15FieldInclusionEntryDetailSequenceNumber(t) +} + +// BenchmarkAddenda15FieldInclusionEntryDetailSequenceNumber benchmarks validating +// EntryDetailSequenceNumber fieldInclusion +func BenchmarkAddenda15FieldInclusionEntryDetailSequenceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda15FieldInclusionEntryDetailSequenceNumber(b) + } +} + +// TestAddenda15String validates that a known parsed Addenda15 record can be return to a string of the same value +func testAddenda15String(t testing.TB) { + addenda15 := NewAddenda15() + var line = "7159874654932139872121 Front Street 0000001" + addenda15.Parse(line) + + if addenda15.String() != line { + t.Errorf("Strings do not match") + } + if addenda15.TypeCode != "15" { + t.Errorf("TypeCode Expected 15 got: %v", addenda15.TypeCode) + } +} + +// TestAddenda15String tests validating that a known parsed Addenda15 record can be return to a string of the same value +func TestAddenda15String(t *testing.T) { + testAddenda15String(t) +} + +// BenchmarkAddenda15String benchmarks validating that a known parsed Addenda15 record can be return to a string of the same value +func BenchmarkAddenda15String(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda15String(b) + } +} + +// TestAddenda15RuneCountInString validates RuneCountInString +func TestAddenda15RuneCountInString(t *testing.T) { + addenda15 := NewAddenda15() + var line = "7159874654932139872121 Front Street" + addenda15.Parse(line) + + if addenda15.ReceiverIDNumber != "" { + t.Error("Parsed with an invalid RuneCountInString not equal to 94") + } +} diff --git a/addenda16.go b/addenda16.go new file mode 100644 index 000000000..75b3c1a9b --- /dev/null +++ b/addenda16.go @@ -0,0 +1,149 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "unicode/utf8" +) + +// Addenda16 is an addenda which provides business transaction information for Addenda Type +// Code 16 in a machine readable format. It is usually formatted according to ANSI, ASC, X16 Standard. +// +// # Addenda16 is mandatory for IAT entries +// +// The Addenda16 record identifies key information related to the Receiver. +type Addenda16 struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // TypeCode Addenda16 types code '16' + TypeCode string `json:"typeCode"` + // Receiver City & State / Province + // Data elements City and State / Province should be separated with an asterisk (*) as a delimiter + // and the field should end with a backslash (\). + // For example: San Francisco*CA\ + ReceiverCityStateProvince string `json:"receiverCityStateProvince"` + // Receiver Country & Postal Code + // Data elements must be separated by an asterisk (*) and must end with a backslash (\) + // For example: US*10036\ + ReceiverCountryPostalCode string `json:"receiverCountryPostalCode"` + // EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + // Detail or Corporate Entry Detail Record's trace number This number is + // the same as the last seven digits of the trace number of the related + // Entry Detail Record or Corporate Entry Detail Record. + EntryDetailSequenceNumber int `json:"entryDetailSequenceNumber"` + // validator is composed for data validation + validator + // converters is composed for ACH to GoLang Converters + converters +} + +// NewAddenda16 returns a new Addenda16 with default values for none exported fields +func NewAddenda16() *Addenda16 { + addenda16 := new(Addenda16) + addenda16.TypeCode = "16" + return addenda16 +} + +// Parse takes the input record string and parses the Addenda16 values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. +func (addenda16 *Addenda16) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + // 1-1 Always 7 + // 2-3 Always 16 + addenda16.TypeCode = record[1:3] + // 4-38 ReceiverCityStateProvince + addenda16.ReceiverCityStateProvince = strings.TrimSpace(record[3:38]) + // 39-73 ReceiverCountryPostalCode + addenda16.ReceiverCountryPostalCode = strings.TrimSpace(record[38:73]) + // 74-87 reserved - Leave blank + // 88-94 Contains the last seven digits of the number entered in the Trace Number field in the corresponding Entry Detail Record + addenda16.EntryDetailSequenceNumber = addenda16.parseNumField(record[87:94]) +} + +// String writes the Addenda16 struct to a 94 character string. +func (addenda16 *Addenda16) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(entryAddendaPos) + buf.WriteString(addenda16.TypeCode) + buf.WriteString(addenda16.ReceiverCityStateProvinceField()) + buf.WriteString(addenda16.ReceiverCountryPostalCodeField()) + buf.WriteString(" ") + buf.WriteString(addenda16.EntryDetailSequenceNumberField()) + return buf.String() +} + +// Validate performs NACHA format rule checks on the record and returns an error if not Validated +// The first error encountered is returned and stops that parsing. +func (addenda16 *Addenda16) Validate() error { + if err := addenda16.fieldInclusion(); err != nil { + return err + } + if err := addenda16.isTypeCode(addenda16.TypeCode); err != nil { + return fieldError("TypeCode", err, addenda16.TypeCode) + } + // Type Code must be 16 + if addenda16.TypeCode != "16" { + return fieldError("TypeCode", ErrAddendaTypeCode, addenda16.TypeCode) + } + if err := addenda16.isAlphanumeric(addenda16.ReceiverCityStateProvince); err != nil { + return fieldError("ReceiverCityStateProvince", err, addenda16.ReceiverCityStateProvince) + } + if err := addenda16.isAlphanumeric(addenda16.ReceiverCountryPostalCode); err != nil { + return fieldError("ReceiverCountryPostalCode", err, addenda16.ReceiverCountryPostalCode) + } + return nil +} + +// fieldInclusion validate mandatory fields are not default values. If fields are +// invalid the ACH transfer will be returned. +func (addenda16 *Addenda16) fieldInclusion() error { + if addenda16.TypeCode == "" { + return fieldError("TypeCode", ErrConstructor, addenda16.TypeCode) + } + if addenda16.ReceiverCityStateProvince == "" { + return fieldError("ReceiverCityStateProvince", ErrConstructor, addenda16.ReceiverCityStateProvince) + } + if addenda16.ReceiverCountryPostalCode == "" { + return fieldError("ReceiverCountryPostalCode", ErrConstructor, addenda16.ReceiverCountryPostalCode) + } + if addenda16.EntryDetailSequenceNumber == 0 { + return fieldError("EntryDetailSequenceNumber", ErrConstructor, addenda16.EntryDetailSequenceNumberField()) + } + return nil +} + +// ReceiverCityStateProvinceField gets the ReceiverCityStateProvinceField left padded +func (addenda16 *Addenda16) ReceiverCityStateProvinceField() string { + return addenda16.alphaField(addenda16.ReceiverCityStateProvince, 35) +} + +// ReceiverCountryPostalCodeField gets the ReceiverCountryPostalCode field left padded +func (addenda16 *Addenda16) ReceiverCountryPostalCodeField() string { + return addenda16.alphaField(addenda16.ReceiverCountryPostalCode, 35) +} + +// EntryDetailSequenceNumberField returns a zero padded EntryDetailSequenceNumber string +func (addenda16 *Addenda16) EntryDetailSequenceNumberField() string { + return addenda16.numericField(addenda16.EntryDetailSequenceNumber, 7) +} diff --git a/addenda16_test.go b/addenda16_test.go new file mode 100644 index 000000000..946c69b67 --- /dev/null +++ b/addenda16_test.go @@ -0,0 +1,304 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockAddenda16 creates a mock Addenda16 record +func mockAddenda16() *Addenda16 { + addenda16 := NewAddenda16() + addenda16.ReceiverCityStateProvince = "LetterTown*AB\\" + addenda16.ReceiverCountryPostalCode = "CA*80014\\" + addenda16.EntryDetailSequenceNumber = 00000001 + return addenda16 +} + +// TestMockAddenda16 validates mockAddenda16 +func TestMockAddenda16(t *testing.T) { + addenda16 := mockAddenda16() + if err := addenda16.Validate(); err != nil { + t.Error("mockAddenda16 does not validate and will break other tests") + } +} + +// testAddenda16Parse parses Addenda16 record +func testAddenda16Parse(t testing.TB) { + Addenda16 := NewAddenda16() + line := "716LetterTown*AB\\ CA*80014\\ 0000001" + Addenda16.Parse(line) + // walk the Addenda16 struct + if Addenda16.TypeCode != "16" { + t.Errorf("expected %v got %v", "16", Addenda16.TypeCode) + } + if Addenda16.ReceiverCityStateProvince != "LetterTown*AB\\" { + t.Errorf("expected %v got %v", "LetterTown*AB\\", Addenda16.ReceiverCityStateProvince) + } + if Addenda16.ReceiverCountryPostalCode != "CA*80014\\" { + t.Errorf("expected: %v got: %v", "CA*80014\\", Addenda16.ReceiverCountryPostalCode) + } + if Addenda16.EntryDetailSequenceNumber != 0000001 { + t.Errorf("expected: %v got: %v", 0000001, Addenda16.EntryDetailSequenceNumber) + } +} + +// TestAddenda16Parse tests parsing Addenda16 record +func TestAddenda16Parse(t *testing.T) { + testAddenda16Parse(t) +} + +// BenchmarkAddenda16Parse benchmarks parsing Addenda16 record +func BenchmarkAddenda16Parse(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda16Parse(b) + } +} + +// testAddenda16ValidTypeCode validates Addenda16 TypeCode +func testAddenda16ValidTypeCode(t testing.TB) { + addenda16 := mockAddenda16() + addenda16.TypeCode = "65" + err := addenda16.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda16ValidTypeCode tests validating Addenda16 TypeCode +func TestAddenda16ValidTypeCode(t *testing.T) { + testAddenda16ValidTypeCode(t) +} + +// BenchmarkAddenda16ValidTypeCode benchmarks validating Addenda16 TypeCode +func BenchmarkAddenda16ValidTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda16ValidTypeCode(b) + } +} + +// testAddenda16TypeCode16 TypeCode is 16 if TypeCode is a valid TypeCode +func testAddenda16TypeCode16(t testing.TB) { + addenda16 := mockAddenda16() + addenda16.TypeCode = "05" + err := addenda16.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda16TypeCode16 tests TypeCode is 16 if TypeCode is a valid TypeCode +func TestAddenda16TypeCode16(t *testing.T) { + testAddenda16TypeCode16(t) +} + +// BenchmarkAddenda16TypeCode16 benchmarks TypeCode is 16 if TypeCode is a valid TypeCode +func BenchmarkAddenda16TypeCode16(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda16TypeCode16(b) + } +} + +// testReceiverCityStateProvinceAlphaNumeric validates ReceiverCityStateProvince is alphanumeric +func testReceiverCityStateProvinceAlphaNumeric(t testing.TB) { + addenda16 := mockAddenda16() + addenda16.ReceiverCityStateProvince = "Jacobs®Town*PA" + err := addenda16.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestReceiverCityStateProvinceAlphaNumeric tests validating ReceiverCityStateProvince is alphanumeric +func TestReceiverCityStateProvinceAlphaNumeric(t *testing.T) { + testReceiverCityStateProvinceAlphaNumeric(t) +} + +// BenchmarkReceiverCityStateProvinceAlphaNumeric benchmarks validating ReceiverCityStateProvince is alphanumeric +func BenchmarkReceiverCityStateProvinceAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testReceiverCityStateProvinceAlphaNumeric(b) + } +} + +// testReceiverCountryPostalCodeAlphaNumeric validates ReceiverCountryPostalCode is alphanumeric +func testReceiverCountryPostalCodeAlphaNumeric(t testing.TB) { + addenda16 := mockAddenda16() + addenda16.ReceiverCountryPostalCode = "US19®305" + err := addenda16.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestReceiverCountryPostalCodeAlphaNumeric tests validating ReceiverCountryPostalCode is alphanumeric +func TestReceiverCountryPostalCodeAlphaNumeric(t *testing.T) { + testReceiverCountryPostalCodeAlphaNumeric(t) +} + +// BenchmarkReceiverCountryPostalCodeAlphaNumeric benchmarks validating ReceiverCountryPostalCode is alphanumeric +func BenchmarkReceiverCountryPostalCodeAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testReceiverCountryPostalCodeAlphaNumeric(b) + } +} + +// testAddenda16FieldInclusionTypeCode validates TypeCode fieldInclusion +func testAddenda16FieldInclusionTypeCode(t testing.TB) { + addenda16 := mockAddenda16() + addenda16.TypeCode = "" + err := addenda16.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda16FieldInclusionTypeCode tests validating TypeCode fieldInclusion +func TestAddenda16FieldInclusionTypeCode(t *testing.T) { + testAddenda16FieldInclusionTypeCode(t) +} + +// BenchmarkAddenda16FieldInclusionTypeCode benchmarks validating TypeCode fieldInclusion +func BenchmarkAddenda16FieldInclusionTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda16FieldInclusionTypeCode(b) + } +} + +// testAddenda16FieldInclusionReceiverCityStateProvince validates ReceiverCityStateProvince fieldInclusion +func testAddenda16FieldInclusionReceiverCityStateProvince(t testing.TB) { + addenda16 := mockAddenda16() + addenda16.ReceiverCityStateProvince = "" + err := addenda16.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda16FieldInclusionReceiverCityStateProvince tests validating ReceiverCityStateProvince fieldInclusion +func TestAddenda16FieldInclusionReceiverCityStateProvince(t *testing.T) { + testAddenda16FieldInclusionReceiverCityStateProvince(t) +} + +// BenchmarkAddenda16FieldInclusionReceiverCityStateProvince benchmarks validating ReceiverCityStateProvince fieldInclusion +func BenchmarkAddenda16FieldInclusionReceiverCityStateProvince(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda16FieldInclusionReceiverCityStateProvince(b) + } +} + +// testAddenda16FieldInclusionReceiverCountryPostalCode validates ReceiverCountryPostalCode fieldInclusion +func testAddenda16FieldInclusionReceiverCountryPostalCode(t testing.TB) { + addenda16 := mockAddenda16() + addenda16.ReceiverCountryPostalCode = "" + err := addenda16.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda16FieldInclusionReceiverCountryPostalCode tests validating ReceiverCountryPostalCode fieldInclusion +func TestAddenda16FieldInclusionReceiverCountryPostalCode(t *testing.T) { + testAddenda16FieldInclusionReceiverCountryPostalCode(t) +} + +// BenchmarkAddenda16FieldInclusionReceiverCountryPostalCode benchmarks validating ReceiverCountryPostalCode fieldInclusion +func BenchmarkAddenda16FieldInclusionReceiverCountryPostalCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda16FieldInclusionReceiverCountryPostalCode(b) + } +} + +// testAddenda16FieldInclusionEntryDetailSequenceNumber validates EntryDetailSequenceNumber fieldInclusion +func testAddenda16FieldInclusionEntryDetailSequenceNumber(t testing.TB) { + addenda16 := mockAddenda16() + addenda16.EntryDetailSequenceNumber = 0 + err := addenda16.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda16FieldInclusionEntryDetailSequenceNumber tests validating +// EntryDetailSequenceNumber fieldInclusion +func TestAddenda16FieldInclusionEntryDetailSequenceNumber(t *testing.T) { + testAddenda16FieldInclusionEntryDetailSequenceNumber(t) +} + +// BenchmarkAddenda16FieldInclusionEntryDetailSequenceNumber benchmarks validating +// EntryDetailSequenceNumber fieldInclusion +func BenchmarkAddenda16FieldInclusionEntryDetailSequenceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda16FieldInclusionEntryDetailSequenceNumber(b) + } +} + +// TestAddenda16String validates that a known parsed Addenda16 record can be return to a string of the same value +func testAddenda16String(t testing.TB) { + addenda16 := NewAddenda16() + // Backslash logic + var line = "716" + + "LetterTown*AB\\ " + + "CA*80014\\ " + + " " + + "0000001" + + addenda16.Parse(line) + + if addenda16.String() != line { + t.Errorf("Strings do not match") + } + if addenda16.TypeCode != "16" { + t.Errorf("TypeCode Expected 16 got: %v", addenda16.TypeCode) + } +} + +// TestAddenda16String tests validating that a known parsed Addenda16 record can be return to a string of the same value +func TestAddenda16String(t *testing.T) { + testAddenda16String(t) +} + +// BenchmarkAddenda16String benchmarks validating that a known parsed Addenda16 record can be return to a string of the same value +func BenchmarkAddenda16String(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda16String(b) + } +} + +// TestAddenda16RuneCountInString validates RuneCountInString +func TestAddenda16RuneCountInString(t *testing.T) { + addenda16 := NewAddenda16() + var line = "716" + addenda16.Parse(line) + + if addenda16.ReceiverCityStateProvince != "" { + t.Error("Parsed with an invalid RuneCountInString not equal to 94") + } +} diff --git a/addenda17.go b/addenda17.go new file mode 100644 index 000000000..795484e71 --- /dev/null +++ b/addenda17.go @@ -0,0 +1,141 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "unicode/utf8" +) + +// Addenda17 is an addenda which provides business transaction information for Addenda Type +// Code 17 in a machine readable format. It is usually formatted according to ANSI, ASC, X12 Standard. +// +// # Addenda17 is optional for IAT entries +// +// The Addenda17 record identifies payment-related data. A maximum of two of these Addenda Records +// may be included with each IAT entry. +type Addenda17 struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // TypeCode Addenda17 types code '17' + TypeCode string `json:"typeCode"` + // PaymentRelatedInformation + PaymentRelatedInformation string `json:"paymentRelatedInformation"` + // SequenceNumber is consecutively assigned to each Addenda17 Record following + // an Entry Detail Record. The first addenda17 sequence number must always + // be a "1". + SequenceNumber int `json:"sequenceNumber"` + // EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + // Detail or Corporate Entry Detail Record's trace number This number is + // the same as the last seven digits of the trace number of the related + // Entry Detail Record or Corporate Entry Detail Record. + EntryDetailSequenceNumber int `json:"entryDetailSequenceNumber"` + // validator is composed for data validation + validator + // converters is composed for ACH to GoLang Converters + converters +} + +// NewAddenda17 returns a new Addenda17 with default values for none exported fields +func NewAddenda17() *Addenda17 { + addenda17 := new(Addenda17) + addenda17.TypeCode = "17" + return addenda17 +} + +// Parse takes the input record string and parses the Addenda17 values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. +func (addenda17 *Addenda17) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + // 1-1 Always 7 + // 2-3 Always 17 + addenda17.TypeCode = record[1:3] + // 4-83 Based on the information entered (04-83) 80 alphanumeric + addenda17.PaymentRelatedInformation = strings.TrimSpace(record[3:83]) + // 84-87 SequenceNumber is consecutively assigned to each Addenda17 Record following + // an Entry Detail Record + addenda17.SequenceNumber = addenda17.parseNumField(record[83:87]) + // 88-94 Contains the last seven digits of the number entered in the Trace Number field in the corresponding Entry Detail Record + addenda17.EntryDetailSequenceNumber = addenda17.parseNumField(record[87:94]) +} + +// String writes the Addenda17 struct to a 94 character string. +func (addenda17 *Addenda17) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(entryAddendaPos) + buf.WriteString(addenda17.TypeCode) + buf.WriteString(addenda17.PaymentRelatedInformationField()) + buf.WriteString(addenda17.SequenceNumberField()) + buf.WriteString(addenda17.EntryDetailSequenceNumberField()) + return buf.String() +} + +// Validate performs NACHA format rule checks on the record and returns an error if not Validated +// The first error encountered is returned and stops that parsing. +func (addenda17 *Addenda17) Validate() error { + if err := addenda17.fieldInclusion(); err != nil { + return err + } + if err := addenda17.isTypeCode(addenda17.TypeCode); err != nil { + return fieldError("TypeCode", err, addenda17.TypeCode) + } + // Type Code must be 17 + if addenda17.TypeCode != "17" { + return fieldError("TypeCode", ErrAddendaTypeCode, addenda17.TypeCode) + } + if err := addenda17.isAlphanumeric(addenda17.PaymentRelatedInformation); err != nil { + return fieldError("PaymentRelatedInformation", err, addenda17.PaymentRelatedInformation) + } + + return nil +} + +// fieldInclusion validate mandatory fields are not default values. If fields are +// invalid the ACH transfer will be returned. +func (addenda17 *Addenda17) fieldInclusion() error { + if addenda17.TypeCode == "" { + return fieldError("TypeCode", ErrConstructor, addenda17.TypeCode) + } + if addenda17.SequenceNumber == 0 { + return fieldError("SequenceNumber", ErrConstructor, addenda17.SequenceNumberField()) + } + if addenda17.EntryDetailSequenceNumber == 0 { + return fieldError("EntryDetailSequenceNumber", ErrConstructor, addenda17.EntryDetailSequenceNumberField()) + } + return nil +} + +// PaymentRelatedInformationField returns a zero padded PaymentRelatedInformation string +func (addenda17 *Addenda17) PaymentRelatedInformationField() string { + return addenda17.alphaField(addenda17.PaymentRelatedInformation, 80) +} + +// SequenceNumberField returns a zero padded SequenceNumber string +func (addenda17 *Addenda17) SequenceNumberField() string { + return addenda17.numericField(addenda17.SequenceNumber, 4) +} + +// EntryDetailSequenceNumberField returns a zero padded EntryDetailSequenceNumber string +func (addenda17 *Addenda17) EntryDetailSequenceNumberField() string { + return addenda17.numericField(addenda17.EntryDetailSequenceNumber, 7) +} diff --git a/addenda17_test.go b/addenda17_test.go new file mode 100644 index 000000000..682d7e0fd --- /dev/null +++ b/addenda17_test.go @@ -0,0 +1,218 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockAddenda17 creates a mock Addenda17 record +func mockAddenda17() *Addenda17 { + addenda17 := NewAddenda17() + addenda17.PaymentRelatedInformation = "This is an international payment" + addenda17.SequenceNumber = 1 + addenda17.EntryDetailSequenceNumber = 0000001 + return addenda17 +} + +func mockAddenda17B() *Addenda17 { + addenda17 := NewAddenda17() + addenda17.PaymentRelatedInformation = "Transfer of money from one country to another" + addenda17.SequenceNumber = 2 + addenda17.EntryDetailSequenceNumber = 0000001 + + return addenda17 +} + +// testAddenda17Parse parses Addenda17 record +func testAddenda17Parse(t testing.TB) { + Addenda17 := NewAddenda17() + line := "717This is an international payment 00010000001" + Addenda17.Parse(line) + // walk the Addenda17 struct + if Addenda17.TypeCode != "17" { + t.Errorf("expected %v got %v", "17", Addenda17.TypeCode) + } + if Addenda17.PaymentRelatedInformation != "This is an international payment" { + t.Errorf("expected %v got %v", "This is an international payment", Addenda17.PaymentRelatedInformation) + } + if Addenda17.SequenceNumber != 1 { + t.Errorf("expected: %v got: %v", 1, Addenda17.SequenceNumber) + } + if Addenda17.EntryDetailSequenceNumber != 0000001 { + t.Errorf("expected: %v got: %v", 0000001, Addenda17.EntryDetailSequenceNumber) + } +} + +// TestAddenda17Parse tests parsing Addenda17 record +func TestAddenda17Parse(t *testing.T) { + testAddenda17Parse(t) +} + +// BenchmarkAddenda17Parse benchmarks parsing Addenda17 record +func BenchmarkAddenda17Parse(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda17Parse(b) + } +} + +// TestMockAddenda17 validates mockAddenda17 +func TestMockAddenda17(t *testing.T) { + addenda17 := mockAddenda17() + if err := addenda17.Validate(); err != nil { + t.Error("mockAddenda17 does not validate and will break other tests") + } + if addenda17.EntryDetailSequenceNumber != 0000001 { + t.Error("EntryDetailSequenceNumber dependent default value has changed") + } +} + +// testAddenda17String validates that a known parsed file can be return to a string of the same value +func testAddenda17String(t testing.TB) { + addenda17 := NewAddenda17() + var line = "717IAT DIEGO MAY 00010000001" + addenda17.Parse(line) + + if addenda17.String() != line { + t.Errorf("Strings do not match") + } +} + +// TestAddenda17 String tests validating that a known parsed file can be return to a string of the same value +func TestAddenda17String(t *testing.T) { + testAddenda17String(t) +} + +// BenchmarkAddenda17 String benchmarks validating that a known parsed file can be return to a string of the same value +func BenchmarkAddenda17String(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda17String(b) + } +} + +func TestAddenda17FieldInclusionTypeCode(t *testing.T) { + addenda17 := mockAddenda17() + addenda17.TypeCode = "" + err := addenda17.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +func TestAddenda17FieldInclusion(t *testing.T) { + addenda17 := mockAddenda17() + addenda17.EntryDetailSequenceNumber = 0 + err := addenda17.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +func TestAddenda17FieldInclusionSequenceNumber(t *testing.T) { + addenda17 := mockAddenda17() + addenda17.SequenceNumber = 0 + err := addenda17.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// testAddenda17PaymentRelatedInformationAlphaNumeric validates PaymentRelatedInformation is alphanumeric +func testAddenda17PaymentRelatedInformationAlphaNumeric(t testing.TB) { + addenda17 := mockAddenda17() + addenda17.PaymentRelatedInformation = "®©" + err := addenda17.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda17PaymentRelatedInformationAlphaNumeric tests validating PaymentRelatedInformation is alphanumeric +func TestAddenda17PaymentRelatedInformationAlphaNumeric(t *testing.T) { + testAddenda17PaymentRelatedInformationAlphaNumeric(t) + +} + +// BenchmarkAddenda17PaymentRelatedInformationAlphaNumeric benchmarks PaymentRelatedInformation is alphanumeric +func BenchmarkAddenda17PaymentRelatedInformationAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda17PaymentRelatedInformationAlphaNumeric(b) + } +} + +// testAddenda17ValidTypeCode validates Addenda17 TypeCode +func testAddenda17ValidTypeCode(t testing.TB) { + addenda17 := mockAddenda17() + addenda17.TypeCode = "65" + err := addenda17.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda17ValidTypeCode tests validating Addenda17 TypeCode +func TestAddenda17ValidTypeCode(t *testing.T) { + testAddenda17ValidTypeCode(t) +} + +// BenchmarkAddenda17ValidTypeCode benchmarks validating Addenda17 TypeCode +func BenchmarkAddenda17ValidTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda17ValidTypeCode(b) + } +} + +// testAddenda17TypeCode17 TypeCode is 17 if TypeCode is a valid TypeCode +func testAddenda17TypeCode17(t testing.TB) { + addenda17 := mockAddenda17() + addenda17.TypeCode = "05" + err := addenda17.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda17TypeCode17 tests TypeCode is 17 if TypeCode is a valid TypeCode +func TestAddenda17TypeCode17(t *testing.T) { + testAddenda17TypeCode17(t) +} + +// BenchmarkAddenda17TypeCode17 benchmarks TypeCode is 17 if TypeCode is a valid TypeCode +func BenchmarkAddenda17TypeCode17(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda17TypeCode17(b) + } +} + +// TestAddenda17RuneCountInString validates RuneCountInString +func TestAddenda17RuneCountInString(t *testing.T) { + addenda17 := NewAddenda17() + var line = "717IAT DIEGO MAY" + addenda17.Parse(line) + + if addenda17.PaymentRelatedInformation != "" { + t.Error("Parsed with an invalid RuneCountInString not equal to 94") + } +} diff --git a/addenda18.go b/addenda18.go new file mode 100644 index 000000000..6bbad9f59 --- /dev/null +++ b/addenda18.go @@ -0,0 +1,206 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "unicode/utf8" +) + +// Addenda18 is an addenda which provides business transaction information for Addenda Type +// Code 18 in a machine readable format. It is usually formatted according to ANSI, ASC, X12 Standard. +// +// # Addenda18 is optional for IAT entries +// +// The Addenda18 record identifies information on each Foreign Correspondent Bank involved in the +// processing of the IAT entry. If no Foreign Correspondent Bank is involved,t he record should not be +// included. A maximum of five of these Addenda Records may be included with each IAT entry. +type Addenda18 struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // TypeCode Addenda18 types code '18' + TypeCode string `json:"typeCode"` + // ForeignCorrespondentBankName contains the name of the Foreign Correspondent Bank + ForeignCorrespondentBankName string `json:"foreignCorrespondentBankName"` + // Foreign Correspondent Bank Identification Number Qualifier contains a 2-digit code that + // identifies the numbering scheme used in the Foreign Correspondent Bank Identification Number + // field. Code values for this field are: + // “01” = National Clearing System + // “02” = BIC Code + // “03” = IBAN Code + ForeignCorrespondentBankIDNumberQualifier string `json:"foreignCorrespondentBankIDNumberQualifier"` + // Foreign Correspondent Bank Identification Number contains the bank ID number of the Foreign + // Correspondent Bank + ForeignCorrespondentBankIDNumber string `json:"foreignCorrespondentBankIDNumber"` + // Foreign Correspondent Bank Branch Country Code contains the two-character code, as approved by + // the International Organization for Standardization (ISO), to identify the country in which the + // branch of the Foreign Correspondent Bank is located. Values can be found on the International + // Organization for Standardization website: www.iso.org + ForeignCorrespondentBankBranchCountryCode string `json:"foreignCorrespondentBankBranchCountryCode"` + // SequenceNumber is consecutively assigned to each Addenda18 Record following + // an Entry Detail Record. The first addenda18 sequence number must always + // be a "1". + SequenceNumber int `json:"sequenceNumber"` + // EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + // Detail or Corporate Entry Detail Record's trace number This number is + // the same as the last seven digits of the trace number of the related + // Entry Detail Record or Corporate Entry Detail Record. + EntryDetailSequenceNumber int `json:"entryDetailSequenceNumber"` + // validator is composed for data validation + validator + // converters is composed for ACH to GoLang Converters + converters +} + +// NewAddenda18 returns a new Addenda18 with default values for none exported fields +func NewAddenda18() *Addenda18 { + addenda18 := new(Addenda18) + addenda18.TypeCode = "18" + return addenda18 +} + +// Parse takes the input record string and parses the Addenda18 values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. +func (addenda18 *Addenda18) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + // 1-1 Always 7 + // 2-3 Always 18 + addenda18.TypeCode = record[1:3] + // 4-83 Based on the information entered (04-38) 35 alphanumeric + addenda18.ForeignCorrespondentBankName = strings.TrimSpace(record[3:38]) + // 39-40 Based on the information entered (39-40) 2 alphanumeric + // “01” = National Clearing System + // “02” = BIC Code + // “03” = IBAN Code + addenda18.ForeignCorrespondentBankIDNumberQualifier = record[38:40] + // 41-74 Based on the information entered (41-74) 34 alphanumeric + addenda18.ForeignCorrespondentBankIDNumber = strings.TrimSpace(record[40:74]) + // 75-77 Based on the information entered (75-77) 3 alphanumeric + addenda18.ForeignCorrespondentBankBranchCountryCode = strings.TrimSpace(record[74:77]) + // 78-83 - Blank space + // 84-87 SequenceNumber is consecutively assigned to each Addenda18 Record following + // an Entry Detail Record + addenda18.SequenceNumber = addenda18.parseNumField(record[83:87]) + // 88-94 Contains the last seven digits of the number entered in the Trace Number field in the corresponding Entry Detail Record + addenda18.EntryDetailSequenceNumber = addenda18.parseNumField(record[87:94]) +} + +// String writes the Addenda18 struct to a 94 character string. +func (addenda18 *Addenda18) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(entryAddendaPos) + buf.WriteString(addenda18.TypeCode) + buf.WriteString(addenda18.ForeignCorrespondentBankNameField()) + buf.WriteString(addenda18.ForeignCorrespondentBankIDNumberQualifierField()) + buf.WriteString(addenda18.ForeignCorrespondentBankIDNumberField()) + buf.WriteString(addenda18.ForeignCorrespondentBankBranchCountryCodeField()) + buf.WriteString(" ") + buf.WriteString(addenda18.SequenceNumberField()) + buf.WriteString(addenda18.EntryDetailSequenceNumberField()) + return buf.String() +} + +// Validate performs NACHA format rule checks on the record and returns an error if not Validated +// The first error encountered is returned and stops that parsing. +func (addenda18 *Addenda18) Validate() error { + if err := addenda18.fieldInclusion(); err != nil { + return err + } + if err := addenda18.isTypeCode(addenda18.TypeCode); err != nil { + return fieldError("TypeCode", err, addenda18.TypeCode) + } + // Type Code must be 18 + if addenda18.TypeCode != "18" { + return fieldError("TypeCode", ErrAddendaTypeCode, addenda18.TypeCode) + } + if err := addenda18.isAlphanumeric(addenda18.ForeignCorrespondentBankName); err != nil { + return fieldError("ForeignCorrespondentBankName", err, addenda18.ForeignCorrespondentBankName) + } + if err := addenda18.isAlphanumeric(addenda18.ForeignCorrespondentBankIDNumberQualifier); err != nil { + return fieldError("ForeignCorrespondentBankIDNumberQualifier", err, addenda18.ForeignCorrespondentBankIDNumberQualifier) + } + if err := addenda18.isAlphanumeric(addenda18.ForeignCorrespondentBankIDNumber); err != nil { + return fieldError("ForeignCorrespondentBankIDNumber", err, addenda18.ForeignCorrespondentBankIDNumber) + } + if err := addenda18.isAlphanumeric(addenda18.ForeignCorrespondentBankBranchCountryCode); err != nil { + return fieldError("ForeignCorrespondentBankBranchCountryCode", err, addenda18.ForeignCorrespondentBankBranchCountryCode) + } + return nil +} + +// fieldInclusion validate mandatory fields are not default values. If fields are +// invalid the ACH transfer will be returned. +func (addenda18 *Addenda18) fieldInclusion() error { + if addenda18.TypeCode == "" { + return fieldError("TypeCode", ErrConstructor, addenda18.TypeCode) + } + if addenda18.ForeignCorrespondentBankName == "" { + return fieldError("ForeignCorrespondentBankName", ErrConstructor, addenda18.ForeignCorrespondentBankName) + } + if addenda18.ForeignCorrespondentBankIDNumberQualifier == "" { + return fieldError("ForeignCorrespondentBankIDNumberQualifier", ErrConstructor, addenda18.ForeignCorrespondentBankIDNumberQualifier) + } + if addenda18.ForeignCorrespondentBankIDNumber == "" { + return fieldError("ForeignCorrespondentBankIDNumber", ErrConstructor, addenda18.ForeignCorrespondentBankIDNumber) + } + if addenda18.ForeignCorrespondentBankBranchCountryCode == "" { + return fieldError("ForeignCorrespondentBankBranchCountryCode", ErrConstructor, addenda18.ForeignCorrespondentBankBranchCountryCode) + } + if addenda18.SequenceNumber == 0 { + return fieldError("SequenceNumber", ErrConstructor, addenda18.SequenceNumberField()) + } + if addenda18.EntryDetailSequenceNumber == 0 { + return fieldError("EntryDetailSequenceNumber", ErrConstructor, addenda18.EntryDetailSequenceNumberField()) + } + return nil +} + +// ForeignCorrespondentBankNameField returns a zero padded ForeignCorrespondentBankName string +func (addenda18 *Addenda18) ForeignCorrespondentBankNameField() string { + return addenda18.alphaField(addenda18.ForeignCorrespondentBankName, 35) +} + +// ForeignCorrespondentBankIDNumberQualifierField returns a zero padded ForeignCorrespondentBankIDNumberQualifier string +func (addenda18 *Addenda18) ForeignCorrespondentBankIDNumberQualifierField() string { + return addenda18.alphaField(addenda18.ForeignCorrespondentBankIDNumberQualifier, 2) +} + +// ForeignCorrespondentBankIDNumberField returns a zero padded ForeignCorrespondentBankIDNumber string +func (addenda18 *Addenda18) ForeignCorrespondentBankIDNumberField() string { + return addenda18.alphaField(addenda18.ForeignCorrespondentBankIDNumber, 34) +} + +// ForeignCorrespondentBankBranchCountryCodeField returns a zero padded ForeignCorrespondentBankBranchCountryCode string +func (addenda18 *Addenda18) ForeignCorrespondentBankBranchCountryCodeField() string { + return addenda18.alphaField(addenda18.ForeignCorrespondentBankBranchCountryCode, 3) +} + +// SequenceNumberField returns a zero padded SequenceNumber string +func (addenda18 *Addenda18) SequenceNumberField() string { + return addenda18.numericField(addenda18.SequenceNumber, 4) +} + +// EntryDetailSequenceNumberField returns a zero padded EntryDetailSequenceNumber string +func (addenda18 *Addenda18) EntryDetailSequenceNumberField() string { + return addenda18.numericField(addenda18.EntryDetailSequenceNumber, 7) +} diff --git a/addenda18_test.go b/addenda18_test.go new file mode 100644 index 000000000..17906cbe1 --- /dev/null +++ b/addenda18_test.go @@ -0,0 +1,390 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockAddenda18 creates a mock Addenda18 record +func mockAddenda18() *Addenda18 { + addenda18 := NewAddenda18() + addenda18.ForeignCorrespondentBankName = "Bank of Germany" + addenda18.ForeignCorrespondentBankIDNumberQualifier = "01" + addenda18.ForeignCorrespondentBankIDNumber = "987987987654654" + addenda18.ForeignCorrespondentBankBranchCountryCode = "DE" + addenda18.SequenceNumber = 1 + addenda18.EntryDetailSequenceNumber = 0000001 + return addenda18 +} + +func mockAddenda18B() *Addenda18 { + addenda18 := NewAddenda18() + addenda18.ForeignCorrespondentBankName = "Bank of Spain" + addenda18.ForeignCorrespondentBankIDNumberQualifier = "01" + addenda18.ForeignCorrespondentBankIDNumber = "987987987123123" + addenda18.ForeignCorrespondentBankBranchCountryCode = "ES" + addenda18.SequenceNumber = 2 + addenda18.EntryDetailSequenceNumber = 0000001 + return addenda18 +} + +func mockAddenda18C() *Addenda18 { + addenda18 := NewAddenda18() + addenda18.ForeignCorrespondentBankName = "Bank of France" + addenda18.ForeignCorrespondentBankIDNumberQualifier = "01" + addenda18.ForeignCorrespondentBankIDNumber = "456456456987987" + addenda18.ForeignCorrespondentBankBranchCountryCode = "FR" + addenda18.SequenceNumber = 3 + addenda18.EntryDetailSequenceNumber = 0000001 + return addenda18 +} + +func mockAddenda18D() *Addenda18 { + addenda18 := NewAddenda18() + addenda18.ForeignCorrespondentBankName = "Bank of Turkey" + addenda18.ForeignCorrespondentBankIDNumberQualifier = "01" + addenda18.ForeignCorrespondentBankIDNumber = "12312345678910" + addenda18.ForeignCorrespondentBankBranchCountryCode = "TR" + addenda18.SequenceNumber = 4 + addenda18.EntryDetailSequenceNumber = 0000001 + return addenda18 +} + +func mockAddenda18E() *Addenda18 { + addenda18 := NewAddenda18() + addenda18.ForeignCorrespondentBankName = "Bank of United Kingdom" + addenda18.ForeignCorrespondentBankIDNumberQualifier = "01" + addenda18.ForeignCorrespondentBankIDNumber = "1234567890123456789012345678901234" + addenda18.ForeignCorrespondentBankBranchCountryCode = "GB" + addenda18.SequenceNumber = 5 + addenda18.EntryDetailSequenceNumber = 0000001 + return addenda18 +} + +func mockAddenda18F() *Addenda18 { + addenda18 := NewAddenda18() + addenda18.ForeignCorrespondentBankName = "Bank of Antarctica" + addenda18.ForeignCorrespondentBankIDNumberQualifier = "01" + addenda18.ForeignCorrespondentBankIDNumber = "123456789012345678901" + addenda18.ForeignCorrespondentBankBranchCountryCode = "AQ" + addenda18.SequenceNumber = 6 + addenda18.EntryDetailSequenceNumber = 0000001 + return addenda18 +} + +// TestMockAddenda18 validates mockAddenda18 +func TestMockAddenda18(t *testing.T) { + addenda18 := mockAddenda18() + if err := addenda18.Validate(); err != nil { + t.Error("mockAddenda18 does not validate and will break other tests") + } + if addenda18.ForeignCorrespondentBankName != "Bank of Germany" { + t.Error("ForeignCorrespondentBankName dependent default value has changed") + } + if addenda18.ForeignCorrespondentBankIDNumberQualifier != "01" { + t.Error("ForeignCorrespondentBankIDNumberQualifier dependent default value has changed") + } + if addenda18.ForeignCorrespondentBankIDNumber != "987987987654654" { + t.Error("ForeignCorrespondentBankIDNumber dependent default value has changed") + } + if addenda18.ForeignCorrespondentBankBranchCountryCode != "DE" { + t.Error("ForeignCorrespondentBankBranchCountryCode dependent default value has changed") + } + if addenda18.EntryDetailSequenceNumber != 0000001 { + t.Error("EntryDetailSequenceNumber dependent default value has changed") + } +} + +// testAddenda18Parse parses Addenda18 record +func testAddenda18Parse(t testing.TB) { + Addenda18 := NewAddenda18() + line := "718Bank of Germany 01987987987654654 DE 00010000001" + Addenda18.Parse(line) + // walk the Addenda18 struct + if Addenda18.TypeCode != "18" { + t.Errorf("expected %v got %v", "18", Addenda18.TypeCode) + } + if Addenda18.ForeignCorrespondentBankName != "Bank of Germany" { + t.Errorf("expected %v got %v", "Bank of Germany", Addenda18.ForeignCorrespondentBankName) + } + if Addenda18.ForeignCorrespondentBankIDNumberQualifier != "01" { + t.Errorf("expected: %v got: %v", "01", Addenda18.ForeignCorrespondentBankIDNumberQualifier) + } + if Addenda18.ForeignCorrespondentBankIDNumber != "987987987654654" { + t.Errorf("expected: %v got: %v", "987987987654654", Addenda18.ForeignCorrespondentBankIDNumber) + } + if Addenda18.ForeignCorrespondentBankBranchCountryCode != "DE" { + t.Errorf("expected: %s got: %s", "DE", Addenda18.ForeignCorrespondentBankBranchCountryCode) + } + if Addenda18.EntryDetailSequenceNumber != 0000001 { + t.Errorf("expected: %v got: %v", 0000001, Addenda18.EntryDetailSequenceNumber) + } +} + +// TestAddenda18Parse tests parsing Addenda18 record +func TestAddenda18Parse(t *testing.T) { + testAddenda18Parse(t) +} + +// BenchmarkAddenda18Parse benchmarks parsing Addenda18 record +func BenchmarkAddenda18Parse(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda18Parse(b) + } +} + +// testAddenda18String validates that a known parsed file can be return to a string of the same value +func testAddenda18String(t testing.TB) { + addenda18 := NewAddenda18() + var line = "718Bank of United Kingdom 011234567890123456789012345678901234GB 00010000001" + addenda18.Parse(line) + + if addenda18.String() != line { + t.Errorf("Strings do not match") + } +} + +// TestAddenda18 String tests validating that a known parsed file can be return to a string of the same value +func TestAddenda18String(t *testing.T) { + testAddenda18String(t) +} + +// BenchmarkAddenda18 String benchmarks validating that a known parsed file can be return to a string of the same value +func BenchmarkAddenda18String(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda18String(b) + } +} + +func TestAddenda18FieldInclusionTypeCode(t *testing.T) { + addenda18 := mockAddenda18() + addenda18.TypeCode = "" + err := addenda18.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +func TestAddenda18FieldInclusion(t *testing.T) { + addenda18 := mockAddenda18() + addenda18.EntryDetailSequenceNumber = 0 + err := addenda18.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +func TestAddenda18FieldInclusionSequenceNumber(t *testing.T) { + addenda18 := mockAddenda18() + addenda18.SequenceNumber = 0 + err := addenda18.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +func TestAddenda18FieldInclusionFCBankName(t *testing.T) { + addenda18 := mockAddenda18() + addenda18.ForeignCorrespondentBankName = "" + err := addenda18.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +func TestAddenda18FieldInclusionFCBankIDNumberQualifier(t *testing.T) { + addenda18 := mockAddenda18() + addenda18.ForeignCorrespondentBankIDNumberQualifier = "" + err := addenda18.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +func TestAddenda18FieldInclusionFCBankIDNumber(t *testing.T) { + addenda18 := mockAddenda18() + addenda18.ForeignCorrespondentBankIDNumber = "" + err := addenda18.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +func TestAddenda18FieldInclusionFCBankBranchCountryCode(t *testing.T) { + addenda18 := mockAddenda18() + addenda18.ForeignCorrespondentBankBranchCountryCode = "" + err := addenda18.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// testAddenda18ForeignCorrespondentBankNameAlphaNumeric validates ForeignCorrespondentBankName is alphanumeric +func testAddenda18ForeignCorrespondentBankNameAlphaNumeric(t testing.TB) { + addenda18 := mockAddenda18() + addenda18.ForeignCorrespondentBankName = "®©" + err := addenda18.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda18ForeignCorrespondentBankNameAlphaNumeric tests validating ForeignCorrespondentBankName is alphanumeric +func TestAddenda18ForeignCorrespondentBankNameAlphaNumeric(t *testing.T) { + testAddenda18ForeignCorrespondentBankNameAlphaNumeric(t) + +} + +// BenchmarkAddenda18ForeignCorrespondentBankNameAlphaNumeric benchmarks ForeignCorrespondentBankName is alphanumeric +func BenchmarkAddenda18ForeignCorrespondentBankNameAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda18ForeignCorrespondentBankNameAlphaNumeric(b) + } +} + +// testAddenda18ForeignCorrespondentBankIDQualifierAlphaNumeric validates ForeignCorrespondentBankIDNumberQualifier is alphanumeric +func testAddenda18ForeignCorrespondentBankIDQualifierAlphaNumeric(t testing.TB) { + addenda18 := mockAddenda18() + addenda18.ForeignCorrespondentBankIDNumberQualifier = "®©" + err := addenda18.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda18ForeignCorrespondentBankIDQualifierAlphaNumeric tests validating ForeignCorrespondentBankIDNumberQualifier is alphanumeric +func TestAddenda18ForeignCorrespondentBankIDQualifierAlphaNumeric(t *testing.T) { + testAddenda18ForeignCorrespondentBankIDQualifierAlphaNumeric(t) +} + +// BenchmarkAddenda18ForeignCorrespondentBankIDQualifierAlphaNumeric benchmarks ForeignCorrespondentBankIDNumberQualifier is alphanumeric +func BenchmarkAddenda18ForeignCorrespondentBankIDQualifierAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda18ForeignCorrespondentBankIDQualifierAlphaNumeric(b) + } +} + +// testAddenda18ForeignCorrespondentBankBranchCountryCodeAlphaNumeric validates ForeignCorrespondentBankBranchCountryCode is alphanumeric +func testAddenda18ForeignCorrespondentBankBranchCountryCodeAlphaNumeric(t testing.TB) { + addenda18 := mockAddenda18() + addenda18.ForeignCorrespondentBankBranchCountryCode = "®©" + err := addenda18.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda18ForeignCorrespondentBankBranchCountryCodeNumeric tests validating ForeignCorrespondentBankBranchCountryCode is alphanumeric +func TestAddenda18ForeignCorrespondentBankBranchCountryCodeAlphaNumeric(t *testing.T) { + testAddenda18ForeignCorrespondentBankBranchCountryCodeAlphaNumeric(t) +} + +// BenchmarkAddenda18ForeignCorrespondentBankBranchCountryCodeAlphaNumeric benchmarks ForeignCorrespondentBankBranchCountryCode is alphanumeric +func BenchmarkAddenda18ForeignCorrespondentBankBranchCountryCodeAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda18ForeignCorrespondentBankBranchCountryCodeAlphaNumeric(b) + } +} + +// testAddenda18ForeignCorrespondentBankIDNumberAlphaNumeric validates ForeignCorrespondentBankIDNumber is alphanumeric +func testAddenda18ForeignCorrespondentBankIDNumberAlphaNumeric(t testing.TB) { + addenda18 := mockAddenda18() + addenda18.ForeignCorrespondentBankIDNumber = "®©" + err := addenda18.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda18ForeignCorrespondentBankIDNumberAlphaNumeric tests validating ForeignCorrespondentBankIDNumber is alphanumeric +func TestAddenda18ForeignCorrespondentBankIDNumberAlphaNumeric(t *testing.T) { + testAddenda18ForeignCorrespondentBankIDNumberAlphaNumeric(t) +} + +// BenchmarkAddenda18ForeignCorrespondentBankIDNumberAlphaNumeric benchmarks ForeignCorrespondentBankIDNumber is alphanumeric +func BenchmarkAddendaForeignCorrespondentBankIDNumberAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda18ForeignCorrespondentBankIDNumberAlphaNumeric(b) + } +} + +// testAddenda18ValidTypeCode validates Addenda18 TypeCode +func testAddenda18ValidTypeCode(t testing.TB) { + addenda18 := mockAddenda18() + addenda18.TypeCode = "65" + err := addenda18.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda18ValidTypeCode tests validating Addenda18 TypeCode +func TestAddenda18ValidTypeCode(t *testing.T) { + testAddenda18ValidTypeCode(t) +} + +// BenchmarkAddenda18ValidTypeCode benchmarks validating Addenda18 TypeCode +func BenchmarkAddenda18ValidTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda18ValidTypeCode(b) + } +} + +// testAddenda18TypeCode18 TypeCode is 18 if TypeCode is a valid TypeCode +func testAddenda18TypeCode18(t testing.TB) { + addenda18 := mockAddenda18() + addenda18.TypeCode = "05" + err := addenda18.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda18TypeCode18 tests TypeCode is 18 if TypeCode is a valid TypeCode +func TestAddenda18TypeCode18(t *testing.T) { + testAddenda18TypeCode18(t) +} + +// BenchmarkAddenda18TypeCode18 benchmarks TypeCode is 18 if TypeCode is a valid TypeCode +func BenchmarkAddenda18TypeCode18(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda18TypeCode18(b) + } +} + +// TestAddenda18RuneCountInString validates RuneCountInString +func TestAddenda18RuneCountInString(t *testing.T) { + addenda18 := NewAddenda18() + var line = "718Bank of United Kingdom " + addenda18.Parse(line) + + if addenda18.ForeignCorrespondentBankBranchCountryCode != "" { + t.Error("Parsed with an invalid RuneCountInString not equal to 94") + } +} diff --git a/addenda98.go b/addenda98.go new file mode 100644 index 000000000..f7d309c7d --- /dev/null +++ b/addenda98.go @@ -0,0 +1,336 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "fmt" + "strconv" + "strings" + "unicode/utf8" +) + +// Addenda98 is a Addendumer addenda record format for Notification OF Change(98) +// The field contents for Notification of Change Entries must match the field contents of the original Entries +type Addenda98 struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // TypeCode Addenda types code '98' + TypeCode string `json:"typeCode"` + // ChangeCode field contains a standard code used by an ACH Operator or RDFI to describe the reason for a change Entry. + // Must exist in changeCodeDict + ChangeCode string `json:"changeCode"` + // OriginalTrace This field contains the Trace Number as originally included on the forward Entry or Prenotification. + // The RDFI must include the Original Entry Trace Number in the Addenda Record of an Entry being returned to an ODFI, + // in the Addenda Record of an 98, within an Acknowledgment Entry, or with an RDFI request for a copy of an authorization. + OriginalTrace string `json:"originalTrace"` + // OriginalDFI field contains the Receiving DFI Identification (addenda.RDFIIdentification) as originally included on the forward Entry or Prenotification that the RDFI is returning or correcting. + OriginalDFI string `json:"originalDFI"` + // CorrectedData + CorrectedData string `json:"correctedData"` + // TraceNumber matches the Entry Detail Trace Number of the entry being returned. + // + // Use TraceNumberField for a properly formatted string representation. + TraceNumber string `json:"traceNumber,omitempty"` + + // validator is composed for data validation + validator + // converters is composed for ACH to GoLang Converters + converters +} + +var ( + changeCodeDict = map[string]*ChangeCode{} +) + +func init() { + // populate the changeCode map with lookup values + changeCodeDict = makeChangeCodeDict() +} + +// ChangeCode holds a change Code, Reason/Title, and Description +// table of return codes exists in Part 4.2 of the NACHA corporate rules and guidelines +type ChangeCode struct { + Code string `json:"code"` + Reason string `json:"reason"` + Description string `json:"description"` +} + +// NewAddenda98 returns an reference to an instantiated Addenda98 with default values +func NewAddenda98() *Addenda98 { + addenda98 := &Addenda98{ + TypeCode: "98", + } + return addenda98 +} + +// Parse takes the input record string and parses the Addenda98 values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. +func (addenda98 *Addenda98) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + // 1-1 Always 7 + // 2-3 Always "98" + addenda98.TypeCode = record[1:3] + // 4-6 + addenda98.ChangeCode = record[3:6] + // 7-21 + addenda98.OriginalTrace = strings.TrimSpace(record[6:21]) + // 28-35 + addenda98.OriginalDFI = addenda98.parseStringField(record[27:35]) + // 36-64 + addenda98.CorrectedData = strings.TrimSpace(record[35:64]) + // 80-94 + addenda98.TraceNumber = strings.TrimSpace(record[79:94]) +} + +// String writes the Addenda98 struct to a 94 character string +func (addenda98 *Addenda98) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(entryAddendaPos) + buf.WriteString(addenda98.TypeCode) + buf.WriteString(addenda98.ChangeCode) + buf.WriteString(addenda98.OriginalTraceField()) + buf.WriteString(" ") // 6 char reserved field + buf.WriteString(addenda98.OriginalDFIField()) + buf.WriteString(addenda98.CorrectedDataField()) + buf.WriteString(" ") // 15 char reserved field + buf.WriteString(addenda98.TraceNumberField()) + return buf.String() +} + +// Validate verifies NACHA rules for Addenda98 +func (addenda98 *Addenda98) Validate() error { + if addenda98.TypeCode == "" { + return fieldError("TypeCode", ErrConstructor, addenda98.TypeCode) + } + // Type Code must be 98 + if addenda98.TypeCode != "98" { + return fieldError("TypeCode", ErrAddendaTypeCode, addenda98.TypeCode) + } + + // Addenda98 requires a valid ChangeCode + _, ok := changeCodeDict[addenda98.ChangeCode] + if !ok { + return fieldError("ChangeCode", ErrAddenda98ChangeCode, addenda98.ChangeCode) + } + + // Addenda98 Record must contain the corrected information corresponding to the Change Code used + if addenda98.CorrectedData == "" { + return fieldError("CorrectedData", ErrAddenda98CorrectedData, addenda98.CorrectedData) + } + + return nil +} + +// OriginalTraceField returns a zero padded OriginalTrace string +func (addenda98 *Addenda98) OriginalTraceField() string { + return addenda98.stringField(addenda98.OriginalTrace, 15) +} + +// OriginalDFIField returns a zero padded OriginalDFI string +func (addenda98 *Addenda98) OriginalDFIField() string { + return addenda98.stringField(addenda98.OriginalDFI, 8) +} + +// CorrectedDataField returns a space padded CorrectedData string +func (addenda98 *Addenda98) CorrectedDataField() string { + return addenda98.alphaField(addenda98.CorrectedData, 29) +} + +// TraceNumberField returns a zero padded traceNumber string +func (addenda98 *Addenda98) TraceNumberField() string { + return addenda98.stringField(addenda98.TraceNumber, 15) +} + +func (addenda98 *Addenda98) ChangeCodeField() *ChangeCode { + code, ok := changeCodeDict[addenda98.ChangeCode] + if ok { + return code + } + return nil +} + +// LookupChangeCode will return a struct representing the reason and description for +// the provided NACHA change code. +func LookupChangeCode(code string) *ChangeCode { + if code, exists := changeCodeDict[strings.ToUpper(code)]; exists { + return code + } + return nil +} + +func makeChangeCodeDict() map[string]*ChangeCode { + dict := make(map[string]*ChangeCode) + + codes := []ChangeCode{ + {"C01", "Incorrect bank account number", "Bank account number incorrect or formatted incorrectly"}, + {"C02", "Incorrect transit/routing number", "Once valid transit/routing number must be changed"}, + {"C03", "Incorrect transit/routing number and bank account number", "Once valid transit/routing number must be changed and causes a change to bank account number structure"}, + {"C04", "Bank account name change", "Customer has changed name or ODFI submitted name incorrectly"}, + {"C05", "Incorrect payment code", "Entry posted to demand account should contain savings payment codes or vice versa"}, + {"C06", "Incorrect bank account number and transit code", "Bank account number must be changed and payment code should indicate posting to another account type (demand/savings)"}, + {"C07", "Incorrect transit/routing number, bank account number and payment code", "Changes required in three fields indicated"}, + {"C09", "Incorrect individual ID number", "Individual's ID number is incorrect"}, + {"C10", "Incorrect company name", "Company name is no longer valid and should be changed."}, + {"C11", "Incorrect company identification", "Company ID is no longer valid and should be changed"}, + {"C12", "Incorrect company name and company ID", "Both the company name and company id are no longer valid and must be changed"}, + // Change codes used when refusing a Notification of Change + {"C61", "Misrouted Notification of Change", ""}, + {"C62", "Incorrect Trace Number", ""}, + {"C63", "Incorrect Company Identification Number", ""}, + {"C64", "Incorrect Individual Identification Number or Identification Number", ""}, + {"C65", "Incorrectly Formatted Corrected Data", ""}, + {"C66", "Incorrect Discretionary Data", ""}, + {"C67", "Routing Number not from Original Entry Detail Record", ""}, + {"C68", "DFI Account Number not from Original Entry Detail Record", ""}, + {"C69", "Incorrect Transaction Code", ""}, + } + // populate the map + for i := range codes { + dict[codes[i].Code] = &codes[i] + } + return dict +} + +// CorrectedData is a struct returned from our helper method for parsing the NOC/COR +// corrected data from Addenda98 records. +// +// All fields are optional and a valid code may not have populated data in this struct. +type CorrectedData struct { + AccountNumber string + RoutingNumber string + Name string + TransactionCode int + Identification string +} + +// ParseCorrectedData returns a struct with some fields filled in depending on the Addenda98's +// Code and CorrectedData. Fields are trimmed when populated in this struct. +func (addenda98 *Addenda98) ParseCorrectedData() *CorrectedData { + if addenda98 == nil { + return nil + } + cc := addenda98.ChangeCodeField() + if cc == nil { + return nil + } + switch cc.Code { + case "C01": // Incorrect DFI Account Number + if v := first(17, addenda98.CorrectedData); v != "" { + return &CorrectedData{AccountNumber: v} + } + case "C02": // Incorrect Routing Number + if v := first(9, addenda98.CorrectedData); v != "" { + return &CorrectedData{RoutingNumber: v} + } + case "C03": // Incorrect Routing Number and Incorrect DFI Account Number + parts := strings.Fields(addenda98.CorrectedData) + if len(parts) == 2 { + return &CorrectedData{ + RoutingNumber: parts[0], + AccountNumber: parts[1], + } + } + case "C04": // Incorrect Individual Name + if v := first(22, addenda98.CorrectedData); v != "" { + return &CorrectedData{Name: v} + } + case "C05": // Incorrect Transaction Code + if n, err := strconv.Atoi(first(2, addenda98.CorrectedData)); err == nil { + return &CorrectedData{TransactionCode: n} + } + case "C06": // Incorrect DFI Account Number and Incorrect Transaction Code + parts := strings.Fields(addenda98.CorrectedData) + if len(parts) == 2 { + if n, err := strconv.Atoi(parts[1]); err == nil { + return &CorrectedData{ + AccountNumber: parts[0], + TransactionCode: n, + } + } + } + case "C07": // Incorrect Routing Number, Incorrect DFI Account Number, and Incorrect Tranaction Code + var cd CorrectedData + if n := len(addenda98.CorrectedData); n > 9 { + cd.RoutingNumber = addenda98.CorrectedData[:9] + } else { + return nil + } + parts := strings.Fields(addenda98.CorrectedData[9:]) + if len(parts) == 2 { + if n, err := strconv.Atoi(parts[1]); err == nil { + cd.AccountNumber = parts[0] + cd.TransactionCode = n + return &cd + } + } else { + return nil + } + case "C09": // Incorrect Individual Identification Number + if v := first(22, addenda98.CorrectedData); v != "" { + return &CorrectedData{Identification: v} + } + } + // The Code/Correction is either unsupported or wasn't parsed correctly + return nil +} + +func first(size int, data string) string { + if utf8.RuneCountInString(data) < size { + if data != "" { + return strings.TrimSpace(data) + } else { + return "" + } + } + return strings.TrimSpace(data[:size]) +} + +// ParseCorrectedData returns the string properlty formatted and justified for an +// Addenda98.CorrectedData field. The code must be an official NACHA change code. +func WriteCorrectionData(code string, data *CorrectedData) string { + pad := &converters{} + switch strings.ToUpper(code) { + case "C01": + return pad.alphaField(data.AccountNumber, 22) + case "C02": + return pad.alphaField(data.RoutingNumber, 22) + case "C03": + spaces := strings.Repeat(" ", 22-len(data.RoutingNumber)-len(data.AccountNumber)) + return fmt.Sprintf("%s%s%s", data.RoutingNumber, spaces, data.AccountNumber) + case "C04": + return pad.alphaField(data.Name, 22) + case "C05": + return pad.alphaField(strconv.Itoa(data.TransactionCode), 22) + case "C06": + txcode := strconv.Itoa(data.TransactionCode) + spaces := strings.Repeat(" ", 22-len(data.AccountNumber)-len(txcode)) + return fmt.Sprintf("%s%s%s", data.AccountNumber, spaces, txcode) + case "C07": + txcode := strconv.Itoa(data.TransactionCode) + spaces := strings.Repeat(" ", 22-9-len(data.AccountNumber)-len(txcode)) + return fmt.Sprintf("%s%s%s%s", data.RoutingNumber, data.AccountNumber, spaces, txcode) + case "C09": + return pad.alphaField(data.Identification, 22) + } + return pad.alphaField("", 22) +} diff --git a/addenda98_test.go b/addenda98_test.go new file mode 100644 index 000000000..2f13fb840 --- /dev/null +++ b/addenda98_test.go @@ -0,0 +1,437 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +func mockAddenda98() *Addenda98 { + addenda98 := NewAddenda98() + addenda98.ChangeCode = "C01" + addenda98.OriginalTrace = "12345" + addenda98.OriginalDFI = "9101298" + addenda98.CorrectedData = "1918171614" + addenda98.TraceNumber = "91012980000088" + + return addenda98 +} + +func TestAddenda98_LookupChangecode(t *testing.T) { + if code := LookupChangeCode(""); code != nil { + t.Error("expected nil ChangeCode") + } + if code := LookupChangeCode("C05"); code == nil { + t.Error("expected ChangeCode") + } else { + if code.Code != "C05" { + t.Errorf("code.Code=%s", code.Code) + } + if code.Reason != "Incorrect payment code" { + t.Errorf("code.Reason=%s", code.Reason) + } + } + if code := LookupChangeCode("C64"); code == nil { + t.Error("expected ChangeCode") + } + if code := LookupChangeCode("C99"); code != nil { + t.Errorf("expected nil: %#v", code) + } +} + +func testAddenda98Parse(t testing.TB) { + addenda98 := NewAddenda98() + line := "798C01099912340000015 091012981918171614 091012980000088" + addenda98.Parse(line) + // walk the Addenda98 struct + if addenda98.TypeCode != "98" { + t.Errorf("expected %v got %v", "98", addenda98.TypeCode) + } + if addenda98.ChangeCode != "C01" { + t.Errorf("expected %v got %v", "C01", addenda98.ChangeCode) + } + if addenda98.OriginalTrace != "099912340000015" { + t.Errorf("expected %v got %v", "099912340000015", addenda98.OriginalTrace) + } + if addenda98.OriginalDFI != "09101298" { + t.Errorf("expected %s got %s", "09101298", addenda98.OriginalDFI) + } + if addenda98.CorrectedData != "1918171614" { + t.Errorf("expected %v got %v", "1918171614", addenda98.CorrectedData) + } + if addenda98.TraceNumber != "091012980000088" { + t.Errorf("expected %v got %v", "091012980000088", addenda98.TraceNumber) + } +} + +func TestAddenda98Parse(t *testing.T) { + testAddenda98Parse(t) +} + +func BenchmarkAddenda98Parse(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda98Parse(b) + } +} + +func testAddenda98String(t testing.TB) { + addenda98 := NewAddenda98() + line := "798C01099912340000015 091012981918171614 091012980000088" + addenda98.Parse(line) + + if addenda98.String() != line { + t.Errorf("\n expected: %v\n got : %v", line, addenda98.String()) + } +} + +func TestAddenda98String(t *testing.T) { + testAddenda98String(t) +} + +func BenchmarkAddenda98String(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda98String(b) + } +} + +func testAddenda98ValidTypeCode(t testing.TB) { + addenda98 := mockAddenda98() + addenda98.TypeCode = "05" + err := addenda98.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +func TestAddenda98ValidTypeCode(t *testing.T) { + testAddenda98ValidTypeCode(t) +} + +func BenchmarkAddenda98ValidTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda98ValidTypeCode(b) + } +} + +func testAddenda98ValidCorrectedData(t testing.TB) { + addenda98 := mockAddenda98() + addenda98.CorrectedData = "" + err := addenda98.Validate() + if !base.Match(err, ErrAddenda98CorrectedData) { + t.Errorf("%T: %s", err, err) + } +} + +func TestAddenda98ValidCorrectedData(t *testing.T) { + testAddenda98ValidCorrectedData(t) +} + +func BenchmarkAddenda98ValidCorrectedData(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda98ValidCorrectedData(b) + } +} + +func testAddenda98ValidateTrue(t testing.TB) { + addenda98 := mockAddenda98() + addenda98.ChangeCode = "C11" + err := addenda98.Validate() + // no error expected + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +func TestAddenda98ValidateTrue(t *testing.T) { + testAddenda98ValidateTrue(t) +} + +func BenchmarkAddenda98ValidateTrue(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda98ValidateTrue(b) + } +} + +func testAddenda98ValidateChangeCodeFalse(t testing.TB) { + addenda98 := mockAddenda98() + addenda98.ChangeCode = "C55" + err := addenda98.Validate() + if !base.Match(err, ErrAddenda98ChangeCode) { + t.Errorf("%T: %s", err, err) + } +} + +func TestAddenda98ValidateChangeCodeFalse(t *testing.T) { + testAddenda98ValidateChangeCodeFalse(t) +} + +func BenchmarkAddenda98ValidateChangeCodeFalse(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda98ValidateChangeCodeFalse(b) + } +} + +func testAddenda98OriginalTraceField(t testing.TB) { + addenda98 := mockAddenda98() + exp := "000000000012345" + if addenda98.OriginalTraceField() != exp { + t.Errorf("expected %v received %v", exp, addenda98.OriginalTraceField()) + } +} + +func TestAddenda98OriginalTraceField(t *testing.T) { + testAddenda98OriginalTraceField(t) +} + +func BenchmarkAddenda98OriginalTraceField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda98OriginalTraceField(b) + } +} + +func testAddenda98OriginalDFIField(t testing.TB) { + addenda98 := mockAddenda98() + exp := "09101298" + if addenda98.OriginalDFIField() != exp { + t.Errorf("expected %v received %v", exp, addenda98.OriginalDFIField()) + } +} + +func TestAddenda98OriginalDFIField(t *testing.T) { + testAddenda98OriginalDFIField(t) +} + +func BenchmarkAddenda98OriginalDFIField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda98OriginalDFIField(b) + } +} + +func testAddenda98CorrectedDataField(t testing.TB) { + addenda98 := mockAddenda98() + exp := "1918171614 " // 29 char + if addenda98.CorrectedDataField() != exp { + t.Errorf("expected %v received %v", exp, addenda98.CorrectedDataField()) + } +} + +func TestAddenda98CorrectedDataField(t *testing.T) { + testAddenda98CorrectedDataField(t) +} + +func BenchmarkAddenda98CorrectedDataField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda98CorrectedDataField(b) + } +} + +func testAddenda98TraceNumberField(t testing.TB) { + addenda98 := mockAddenda98() + exp := "091012980000088" + if addenda98.TraceNumberField() != exp { + t.Errorf("expected %v received %v", exp, addenda98.TraceNumberField()) + } +} + +func TestAddenda98TraceNumberField(t *testing.T) { + testAddenda98TraceNumberField(t) +} + +func BenchmarkAddenda98TraceNumberField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda98TraceNumberField(b) + } +} + +func TestAddenda98__ChangeCodeField(t *testing.T) { + addenda98 := mockAddenda98() + if addenda98.ChangeCode != "C01" { + t.Errorf("addenda98.ChangeCode=%s", addenda98.ChangeCode) + } + if code := addenda98.ChangeCodeField(); code == nil { + t.Fatal("nil Addenda98.ChangeCodeField") + } else { + if code.Code != "C01" { + t.Errorf("code.Code=%s", code.Code) + } + if code.Reason != "Incorrect bank account number" { + t.Errorf("code.Reason=%s", code.Reason) + } + } + + // verify another change code + addenda98.ChangeCode = "C07" + if code := addenda98.ChangeCodeField(); code == nil { + t.Fatal("nil Addenda98.ChangeCodeField") + } else { + if code.Code != "C07" { + t.Errorf("code.Code=%s", code.Code) + } + } + + // invalid change code + addenda98.ChangeCode = "C99" + if code := addenda98.ChangeCodeField(); code != nil { + t.Errorf("unexpected change code: %v", code) + } +} + +// testAddenda98TypeCodeNil validates TypeCode is "" +func testAddenda98TypeCodeNil(t testing.TB) { + addenda98 := mockAddenda98() + addenda98.TypeCode = "" + err := addenda98.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda98TypeCodeES tests TypeCode is "" +func TestAddenda98TypeCodeNil(t *testing.T) { + testAddenda98TypeCodeNil(t) +} + +// BenchmarkAddenda98TypeCodeNil benchmarks TypeCode is "" +func BenchmarkAddenda98TypeCodeNil(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda98TypeCodeNil(b) + } +} + +// TestAddenda98RuneCountInString validates RuneCountInString +func TestAddenda98RuneCountInString(t *testing.T) { + addenda98 := NewAddenda98() + var line = "798" + addenda98.Parse(line) + + if addenda98.CorrectedData != "" { + t.Error("Parsed with an invalid RuneCountInString not equal to 94") + } +} + +func TestCorrectedData__first(t *testing.T) { + if v := first(2, ""); v != "" { + t.Errorf("got='%s'", v) + } + if v := first(2, " "); v != "" { + t.Errorf("got='%s'", v) + } + if v := first(3, "22"); v != "22" { + t.Errorf("got='%s'", v) + } + if v := first(17, " 123 "); v != "123" { + t.Errorf("got='%s'", v) + } + if v := first(17, "123456789 "); v != "123456789" { + t.Errorf("got='%s'", v) + } +} + +func TestCorrectedData__ParseCorrectedData(t *testing.T) { + run := func(code, data string) *CorrectedData { + add := NewAddenda98() + add.ChangeCode = code + add.CorrectedData = data + return add.ParseCorrectedData() + } + + if v := run("C01", "123456789 "); v.AccountNumber != "123456789" { + t.Errorf("%#v", v) + } + if v := run("C02", "987654320 "); v.RoutingNumber != "987654320" { + t.Errorf("%#v", v) + } + if v := run("C03", "987654320 123456"); v.AccountNumber != "123456" || v.RoutingNumber != "987654320" { + t.Errorf("%#v", v) + } + if v := run("C04", "Jane Doe"); v.Name != "Jane Doe" { + t.Errorf("%#v", v) + } + if v := run("C05", "22 other"); v.TransactionCode != 22 { + t.Errorf("%#v", v) + } + if v := run("C06", "123456789 22"); v.AccountNumber != "123456789" || v.TransactionCode != 22 { + t.Errorf("%#v", v) + } + if v := run("C07", "987654320 12345 22"); v.RoutingNumber != "987654320" || v.AccountNumber != "12345" || v.TransactionCode != 22 { + t.Errorf("%#v", v) + } + if v := run("C07", "9876543201242415 22"); v.RoutingNumber != "987654320" || v.AccountNumber != "1242415" || v.TransactionCode != 22 { + t.Errorf("%#v", v) + } + if v := run("C07", "1234"); v != nil { + t.Errorf("expected nil: %v", v) + } + if v := run("C07", "987654320 1234 1234 1234"); v != nil { + t.Errorf("expected nil: %v", v) + } + if v := run("C09", "21345678 "); v.Identification != "21345678" { + t.Errorf("%#v", v) + } + if v := run("C99", " "); v != nil { + t.Error("expected nil CorrectedData") + } +} + +func TestCorrectedData__WriteCorrectionData(t *testing.T) { + data := &CorrectedData{AccountNumber: "12345"} + if v := WriteCorrectionData("C01", data); v != "12345 " { + t.Errorf("C01 got %q (length=%d)", v, len(v)) + } + data = &CorrectedData{RoutingNumber: "987654320"} + if v := WriteCorrectionData("C02", data); v != "987654320 " { + t.Errorf("C02 got %q (length=%d)", v, len(v)) + } + data = &CorrectedData{AccountNumber: "123", RoutingNumber: "987654320"} + if v := WriteCorrectionData("C03", data); v != "987654320 123" { + t.Errorf("C03 got %q (length=%d)", v, len(v)) + } + data = &CorrectedData{Name: "Jane Doe"} + if v := WriteCorrectionData("C04", data); v != "Jane Doe " { + t.Errorf("C04 got %q (length=%d)", v, len(v)) + } + data = &CorrectedData{TransactionCode: 22} + if v := WriteCorrectionData("C05", data); v != "22 " { + t.Errorf("C05 got %q (length=%d)", v, len(v)) + } + data = &CorrectedData{AccountNumber: "5421", TransactionCode: 27} + if v := WriteCorrectionData("C06", data); v != "5421 27" { + t.Errorf("C06 got %q (length=%d)", v, len(v)) + } + data = &CorrectedData{RoutingNumber: "987654320", AccountNumber: "5421", TransactionCode: 32} + if v := WriteCorrectionData("C07", data); v != "9876543205421 32" { + t.Errorf("C07 got %q (length=%d)", v, len(v)) + } + data = &CorrectedData{Identification: "FooBar"} + if v := WriteCorrectionData("C09", data); v != "FooBar " { + t.Errorf("C09 got %q (length=%d)", v, len(v)) + } +} diff --git a/addenda99.go b/addenda99.go new file mode 100644 index 000000000..aeefca08a --- /dev/null +++ b/addenda99.go @@ -0,0 +1,317 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "unicode/utf8" +) + +// When a Return Entry is prepared, the original Company/Batch Header Record, the original Entry Detail Record, +// and the Company/Batch Control Record are copied for return to the Originator. +// +// The Return Entry is a new Entry. These Entries must be assigned new batch and trace numbers, new identification numbers for the returning institution, +// appropriate transaction codes, etc., as required per format specifications. +// +// See Appendix Four: Return Entries in the NACHA Corporate + +var ( + returnCodeDict = map[string]*ReturnCode{} +) + +func init() { + // populate the ReturnCode map with lookup values + returnCodeDict = makeReturnCodeDict() +} + +// Addenda99 utilized for Notification of Change Entry (COR) and Return types. +type Addenda99 struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // TypeCode Addenda types code '99' + TypeCode string `json:"typeCode"` + // ReturnCode field contains a standard code used by an ACH Operator or RDFI to describe the reason for returning an Entry. + // Must exist in returnCodeDict + ReturnCode string `json:"returnCode"` + // OriginalTrace This field contains the Trace Number as originally included on the forward Entry or Prenotification. + // The RDFI must include the Original Entry Trace Number in the Addenda Record of an Entry being returned to an ODFI, + // in the Addenda Record of an 98, within an Acknowledgment Entry, or with an RDFI request for a copy of an authorization. + OriginalTrace string `json:"originalTrace"` + // DateOfDeath The field date of death is to be supplied on Entries being returned for reason of death (return reason codes R14 and R15). Format: YYMMDD (Y=Year, M=Month, D=Day) + DateOfDeath string `json:"dateOfDeath"` + // OriginalDFI field contains the Receiving DFI Identification (addenda.RDFIIdentification) as originally included on the forward Entry or Prenotification that the RDFI is returning or correcting. + OriginalDFI string `json:"originalDFI"` + // AddendaInformation + AddendaInformation string `json:"addendaInformation,omitempty"` + // TraceNumber matches the Entry Detail Trace Number of the entry being returned. + // + // Use TraceNumberField for a properly formatted string representation. + TraceNumber string `json:"traceNumber,omitempty"` + + // validator is composed for data validation + validator + // converters is composed for ACH to GoLang Converters + converters + + validateOpts *ValidateOpts +} + +// ReturnCode holds a return Code, Reason/Title, and Description +// +// Table of return codes exists in Part 4.2 of the NACHA corporate rules and guidelines +type ReturnCode struct { + Code string `json:"code"` + Reason string `json:"reason"` + Description string `json:"description"` +} + +// NewAddenda99 returns a new Addenda99 with default values for none exported fields +func NewAddenda99() *Addenda99 { + Addenda99 := &Addenda99{ + TypeCode: "99", + } + return Addenda99 +} + +// Parse takes the input record string and parses the Addenda99 values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. +func (Addenda99 *Addenda99) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + // 2-3 Defines the specific explanation and format for the addenda information contained in the same record + Addenda99.TypeCode = record[1:3] + // 4-6 + Addenda99.ReturnCode = record[3:6] + // 7-21 + Addenda99.OriginalTrace = strings.TrimSpace(record[6:21]) + // 22-27, might be a date or blank + Addenda99.DateOfDeath = Addenda99.validateSimpleDate(record[21:27]) + // 28-35 + Addenda99.OriginalDFI = Addenda99.parseStringField(record[27:35]) + // 36-79 + Addenda99.AddendaInformation = strings.TrimSpace(record[35:79]) + // 80-94 + Addenda99.TraceNumber = strings.TrimSpace(record[79:94]) +} + +// String writes the Addenda99 struct to a 94 character string +func (Addenda99 *Addenda99) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(entryAddendaPos) + buf.WriteString(Addenda99.TypeCode) + buf.WriteString(Addenda99.ReturnCode) + buf.WriteString(Addenda99.OriginalTraceField()) + buf.WriteString(Addenda99.DateOfDeathField()) + buf.WriteString(Addenda99.OriginalDFIField()) + buf.WriteString(Addenda99.AddendaInformationField()) + buf.WriteString(Addenda99.TraceNumberField()) + return buf.String() +} + +// Validate verifies NACHA rules for Addenda99 +func (Addenda99 *Addenda99) Validate() error { + if Addenda99.TypeCode == "" { + return fieldError("TypeCode", ErrConstructor, Addenda99.TypeCode) + } + if Addenda99.TypeCode != "99" { + return fieldError("TypeCode", ErrAddendaTypeCode, Addenda99.TypeCode) + } + + if Addenda99.validateOpts == nil || !Addenda99.validateOpts.CustomReturnCodes { + _, ok := returnCodeDict[Addenda99.ReturnCode] + if !ok { + // Return Addenda requires a valid ReturnCode + return fieldError("ReturnCode", ErrAddenda99ReturnCode, Addenda99.ReturnCode) + } + } + + return nil +} + +// SetValidation stores ValidateOpts on the Batch which are to be used to override +// the default NACHA validation rules. +func (Addenda99 *Addenda99) SetValidation(opts *ValidateOpts) { + if Addenda99 == nil { + return + } + Addenda99.validateOpts = opts +} + +// OriginalTraceField returns a zero padded OriginalTrace string +func (Addenda99 *Addenda99) OriginalTraceField() string { + return Addenda99.stringField(Addenda99.OriginalTrace, 15) +} + +// DateOfDeathField returns a space padded DateOfDeath string +func (Addenda99 *Addenda99) DateOfDeathField() string { + // Return space padded 6 characters if it is a zero value of DateOfDeath + if Addenda99.DateOfDeath == "" { + return Addenda99.alphaField("", 6) + } + return Addenda99.formatSimpleDate(Addenda99.DateOfDeath) +} + +// OriginalDFIField returns a zero padded OriginalDFI string +func (Addenda99 *Addenda99) OriginalDFIField() string { + return Addenda99.stringField(Addenda99.OriginalDFI, 8) +} + +// AddendaInformationField returns a space padded AddendaInformation string +func (Addenda99 *Addenda99) AddendaInformationField() string { + return Addenda99.alphaField(Addenda99.AddendaInformation, 44) +} + +// IATPaymentAmount sets original forward entry payment amount characters 1-10 of underlying AddendaInformation +func (Addenda99 *Addenda99) IATPaymentAmount(s string) { + Addenda99.AddendaInformation = Addenda99.stringField(s, 10) +} + +// IATAddendaInformation sets Addenda Information for IAT return items, characters 10-44 of +// underlying AddendaInformation +func (Addenda99 *Addenda99) IATAddendaInformation(s string) { + Addenda99.AddendaInformation = Addenda99.AddendaInformation + Addenda99.alphaField(s, 34) +} + +// IATPaymentAmountField returns original forward entry payment amount int, characters 1-10 of +// underlying AddendaInformation +func (Addenda99 *Addenda99) IATPaymentAmountField() int { + return Addenda99.parseNumField(Addenda99.AddendaInformation[0:10]) +} + +// IATAddendaInformationField returns a space padded AddendaInformation string, characters 10-44 of +// underlying AddendaInformation +func (Addenda99 *Addenda99) IATAddendaInformationField() string { + return Addenda99.alphaField(Addenda99.AddendaInformation[9:44], 34) +} + +// TraceNumberField returns a zero padded TraceNumber string +func (Addenda99 *Addenda99) TraceNumberField() string { + return Addenda99.stringField(Addenda99.TraceNumber, 15) +} + +// ReturnCodeField gives the ReturnCode struct for the given Addenda99 record +func (Addenda99 *Addenda99) ReturnCodeField() *ReturnCode { + code, ok := returnCodeDict[Addenda99.ReturnCode] + if ok { + return code + } + return nil +} + +// LookupReturnCode will return a struct representing the reason and description for +// the provided NACHA return code. +func LookupReturnCode(code string) *ReturnCode { + if code, exists := returnCodeDict[strings.ToUpper(code)]; exists { + return code + } + return nil +} + +func makeReturnCodeDict() map[string]*ReturnCode { + dict := make(map[string]*ReturnCode) + + codes := []ReturnCode{ + // Return Reason Codes for RDFIs + {"R01", "Insufficient Funds", "Available balance is not sufficient to cover the dollar value of the debit entry"}, + {"R02", "Account Closed", "Previously active account has been closed by customer or RDFI"}, + // R03 may not be used to return ARC, BOC or POP entries solely because they do not contain an Individual Name. + {"R03", "No Account/Unable to Locate Account", "Account number structure is valid and passes editing process, but does not correspond to individual or is not an open account"}, + {"R04", "Invalid Account Number", "Account number structure not valid; entry may fail check digit validation or may contain an incorrect number of digits."}, + {"R05", "Improper Debit to Consumer Account", "A CCD, CTX, or CBR debit entry was transmitted to a Consumer Account of the Receiver and was not authorized by the Receiver"}, + {"R06", "Returned per ODFI's Request", "ODFI has requested RDFI to return the ACH entry (optional to RDFI - ODFI indemnifies RDFI)"}, + // R07 Prohibited use for ARC, BOC, POP and RCK. + {"R07", "Authorization Revoked by Customer", "Consumer, who previously authorized ACH payment, has revoked authorization from Originator (must be returned no later than 60 days from settlement date and customer must sign affidavit)"}, + {"R08", "Payment Stopped", "Receiver of a recurring debit transaction has stopped payment to a specific ACH debit. RDFI should verify the Receiver's intent when a request for stop payment is made to insure this is not intended to be a revocation of authorization"}, + {"R09", "Uncollected Funds", "Sufficient book or ledger balance exists to satisfy dollar value of the transaction, but the dollar value of transaction is in process of collection (i.e., uncollected checks) or cash reserve balance below dollar value of the debit entry."}, + {"R10", "Customer Advises Originator is Not Known to Receiver and/or Originator is Not Authorized by Receiver to Debit Receiver’s Account", "The receiver does not know the Originator’s identity and/or has not authorized the Originator to debit. Alternatively, for ARC and BOC entries, the signature on the source document is not authentic or authorized. For POP entries, the signature on the written authorization is not authentic or authorized."}, + {"R11", "Customer Advises Entry Not in Accordance with the Terms of the Authorization", "The Originator and Receiver have a relationship, and an authorization to debit exists, but there is an error or defect in the payment such that the entry does not conform to the terms of the authorization. The Originator may correct the error and submit a new entry within 60 days of the return entry's settlement date without the need for re-authorization by the Receiver."}, + {"R12", "Branch Sold to Another DFI", "Financial institution receives entry destined for an account at a branch that has been sold to another financial institution."}, + {"R13", "RDFI not qualified to participate", "Financial institution does not receive commercial ACH entries"}, + {"R14", "Representative payee deceased or unable to continue in that capacity", "The representative payee authorized to accept entries on behalf of a beneficiary is either deceased or unable to continue in that capacity"}, + {"R15", "Beneficiary or bank account holder", "(Other than representative payee) deceased* - (1) the beneficiary entitled to payments is deceased or (2) the bank account holder other than a representative payee is deceased"}, + {"R16", "Bank account frozen", "Funds in bank account are unavailable due to action by RDFI or legal order"}, + {"R17", "File Record Edit Criteria/Entry with Invalid Account Number Initiated Under Questionable Circumstances", "(1) Field(s) cannot be processed by RDFI; or (2) the Entry contains an invalid DFI Account Number (account closed/no account/unable to locate account/invalid account number) and is believed by the RDFI to have been initiated under questionable circumstances; or (3) either the RDFI or Receiver has identified a Reversing Entry as one that was improperly initiated by the Originator or ODFI."}, + {"R18", "Improper effective entry date", "Entries have been presented prior to the first available processing window for the effective date."}, + {"R19", "Amount field error", "Improper formatting of the amount field"}, + {"R20", "Non-payment bank account", "Entry destined for non-payment bank account defined by reg."}, + {"R21", "Invalid company ID number", "The company ID information not valid (normally CIE entries)"}, + {"R22", "Invalid individual ID number", "Individual id used by receiver is incorrect (CIE entries)"}, + {"R23", "Credit entry refused by receiver", "Receiver returned entry because minimum or exact amount not remitted, bank account is subject to litigation, or payment represents an overpayment, originator is not known to receiver or receiver has not authorized this credit entry to this bank account"}, + {"R24", "Duplicate entry", "RDFI has received a duplicate entry"}, + {"R25", "Addenda error", "Improper formatting of the addenda record information"}, + {"R26", "Mandatory field error", "Improper information in one of the mandatory fields"}, + {"R27", "Trace number error", "Original entry trace number is not valid for return entry; or addenda trace numbers do not correspond with entry detail record"}, + {"R28", "Transit routing number check digit error", "Check digit for the transit routing number is incorrect"}, + {"R29", "Corporate customer advises not authorized", "RDFI has bee notified by corporate receiver that debit entry of originator is not authorized"}, + {"R30", "RDFI not participant in check truncation program", "Financial institution not participating in automated check safekeeping application"}, + {"R31", "Permissible return entry (CCD and CTX only)", "RDFI has been notified by the ODFI that it agrees to accept a CCD or CTX return entry"}, + {"R32", "RDFI non-settlement", "RDFI is not able to settle the entry"}, + {"R33", "Return of XCK entry", "RDFI determines at its sole discretion to return an XCK entry; an XCK return entry may be initiated by midnight of the sixtieth day following the settlement date if the XCK entry"}, + {"R34", "Limited participation RDFI", "RDFI participation has been limited by a federal or state supervisor"}, + {"R35", "Return of improper debit entry", "ACH debit not permitted for use with the CIE standard entry class code (except for reversals)"}, + {"R36", "Return of improper credit entry", "ACH credit entries (with the exception of reversing entries) are not permitted for use with ARC, BOC, POP, RCK, TEL, and XCK."}, + {"R37", "Source Document Presented for Payment (Adjustment Entry)", "The source document to which an ARC, BOC or POP entry relates has been presented for payment. RDFI must obtain a Written Statement and return the entry within 60 days following Settlement Date"}, + {"R38", "Stop Payment on Source Document (Adjustment Entry)", "A stop payment has been placed on the source document to which the ARC or BOC entry relates. RDFI must return no later than 60 days following Settlement Date. No Written Statement is required as the original stop payment form covers the return"}, + {"R39", "Improper Source Document", "The RDFI has determined the source document used for the ARC, BOC or POP entry to its Receiver's account is improper."}, + // Return Codes to be used for ENR entries and are initiated by a Federal Government Agency + {"R40", "Return of ENR Entry by Federal Government Agency (ENR Only)", "This return reason code may only be used to return ENR entries and is at the federal Government Agency's Sole discretion"}, + {"R41", "Invalid Transaction Code (ENR only)", "Either the Transaction Code included in Field 3 of the Addenda Record does not conform to the ACH Record Format Specifications contained in Appendix Three (ACH Record Format Specifications) or it is not appropriate with regard to an Automated Enrollment Entry."}, + {"R42", "Routing Number/Check Digit Error (ENR Only)", "The Routing Number and the Check Digit included in Field 3 of the Addenda Record is either not a valid number or it does not conform to the Modulus 10 formula."}, + {"R43", "Invalid DFI Account Number (ENR Only)", "The Receiver's account number included in Field 3 of the Addenda Record must include at least one alphameric character."}, + {"R44", "Invalid Individual ID Number/Identification Number (ENR only)", "The Individual ID Number/Identification Number provided in Field 3 of the Addenda Record does not match a corresponding ID number in the Federal Government Agency's records."}, + {"R45", "Invalid Individual Name/Company Name (ENR only)", "The name of the consumer or company provided in Field 3 of the Addenda Record either does not match a corresponding name in the Federal Government Agency's records or fails to include at least one alphameric character."}, + {"R46", "Invalid Representative Payee Indicator (ENR Only)", "The Representative Payee Indicator Code included in Field 3 of the Addenda Record has been omitted or it is not consistent with the Federal Government Agency's records."}, + {"R47", "Duplicate Enrollment (ENR Only)", "The Entry is a duplicate of an Automated Enrollment Entry previously initiated by a DFI."}, + // Return Codes to be used for RCK entries only and are initiated by a RDFI + {"R50", "State Law Affecting RCK Acceptance", "RDFI is located in a state that has not adopted Revised Article 4 of the UCC or the RDFI is located in a state that requires all canceled checks to be returned within the periodic statement"}, + {"R51", "Item Related to RCK Entry is Ineligible or RCK Entry is Improper", "The item to which the RCK entry relates was not eligible, Originator did not provide notice of the RCK policy, signature on the item was not genuine, the item has been altered or amount of the entry was not accurately obtained from the item. RDFI must obtain a Written Statement and return the entry within 60 days following Settlement Date"}, + {"R52", "Stop Payment on Item (Adjustment Entry)", "A stop payment has been placed on the item to which the RCK entry relates. RDFI must return no later than 60 days following Settlement Date. No Written Statement is required as the original stop payment form covers the return."}, + {"R53", "Item and RCK Entry Presented for Payment (Adjustment Entry)", "Both the RCK entry and check have been presented for payment. RDFI must obtain a Written Statement and return the entry within 60 days following Settlement Date"}, + // Return Codes to be used by the ODFI for dishonored return entries + {"R61", "Misrouted Return", "The financial institution preparing the Return Entry (the RDFI of the original Entry) has placed the incorrect Routing Number in the Receiving DFI Identification field."}, + {"R62", "Return of Erroneous or Reversing Debt", "The Originator’s/ODFI’s use of the reversal process resulted in, or failed to correct, an unintended credit to the Receiver."}, + {"R67", "Duplicate Return", "The ODFI has received more than one Return for the same Entry."}, + {"R68", "Untimely Return", "The Return Entry has not been sent within the time frame established by these Rules."}, + {"R69", "Field Error(s)", "One or more of the field requirements are incorrect."}, + {"R70", "Permissible Return Entry Not Accepted/Return Not Requested by ODFI", "The ODFI has received a Return Entry identified by the RDFI as being returned with the permission of, or at the request of, the ODFI, but the ODFI has not agreed to accept the Entry or has not requested the return of the Entry."}, + // Return Codes to be used by the RDFI for contested dishonored return entries + {"R71", "Misrouted Dishonored Return", "The financial institution preparing the dishonored Return Entry (the ODFI of the original Entry) has placed the incorrect Routing Number in the Receiving DFI Identification field."}, + {"R72", "Untimely Dishonored Return", "The dishonored Return Entry has not been sent within the designated time frame."}, + {"R73", "Timely Original Return", "The RDFI is certifying that the original Return Entry was sent within the time frame designated in these Rules."}, + {"R74", "Corrected Return", "The RDFI is correcting a previous Return Entry that was dishonored using Return Reason Code R69 (Field Error(s)) because it contained incomplete or incorrect information."}, + {"R75", "Return Not a Duplicate", "The Return Entry was not a duplicate of an Entry previously returned by the RDFI."}, + {"R76", "No Errors Found", "The original Return Entry did not contain the errors indicated by the ODFI in the dishonored Return Entry."}, + {"R77", "Non-Acceptance of R62 Dishonored Return", "The RDFI returned the Erroneous Entry and the related Reversing Entry. Alternatively, the funds relating to the R62 dishonored Return are not recoverably from the Receiver."}, + //Return Codes to be used by Gateways for the return of international payments + {"R80", "IAT Entry Coding Error", "The IAT Entry is being returned due to one or more of the following conditions: Invalid DFI/Bank Branch Country Code, invalid DFI/Bank Identification Number Qualifier, invalid Foreign Exchange Indicator, invalid ISO Originating Currency Code, invalid ISO Destination Currency Code, invalid ISO Destination Country Code, invalid Transaction Type Code"}, + {"R81", "Non-Participant in IAT Program", "The IAT Entry is being returned because the Gateway does not have an agreement with either the ODFI or the Gateway's customer to transmit Outbound IAT Entries."}, + {"R82", "Invalid Foreign Receiving DFI Identification", "The reference used to identify the Foreign Receiving DFI of an Outbound IAT Entry is invalid."}, + {"R83", "Foreign Receiving DFI Unable to Settle", "The IAT Entry is being returned due to settlement problems in the foreign payment system."}, + {"R84", "Entry Not Processed by Gateway", "For Outbound IAT Entries, the Entry has not been processed and is being returned at the Gateway's discretion because either (1) the processing of such Entry may expose the Gateway to excessive risk, or (2) the foreign payment system does not support the functions needed to process the transaction."}, + {"R85", "Incorrectly Coded Outbound International Payment", "The RDFI/Gateway has identified the Entry as an Outbound international payment and is returning the Entry because it bears an SEC Code that lacks information required by the Gateway for OFAC compliance."}, + } + // populate the map + for i := range codes { + dict[codes[i].Code] = &codes[i] + } + return dict +} diff --git a/addenda99_contested_return.go b/addenda99_contested_return.go new file mode 100644 index 000000000..a4522f6d8 --- /dev/null +++ b/addenda99_contested_return.go @@ -0,0 +1,210 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "unicode/utf8" +) + +type Addenda99Contested struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + + // TypeCode Addenda types code '99' + TypeCode string `json:"typeCode"` + + // ContestedReturnCode is the return code explaining the contested dishonorment + ContestedReturnCode string `json:"contestedReturnCode"` + + // OriginalEntryTraceNumber is the trace number specifieid in the initial entry + OriginalEntryTraceNumber string `json:"originalEntryTraceNumber"` + + // DateOriginalEntryReturned is the original entry's date + DateOriginalEntryReturned string `json:"dateOriginalEntryReturned"` + + // OriginalReceivingDFIIdentification is the DFI Identification specifieid in the initial entry + OriginalReceivingDFIIdentification string `json:"originalReceivingDFIIdentification"` + + // OriginalSettlementDate is the initial date of settlement + OriginalSettlementDate string `json:"originalSettlementDate"` + + // ReturnTraceNumber is the original returns trace number + ReturnTraceNumber string `json:"returnTraceNumber"` + + // ReturnSettlementDate is the original return's settlement date + ReturnSettlementDate string `json:"returnSettlementDate"` + + // ReturnReasonCode is the original return's code + ReturnReasonCode string `json:"returnReasonCode"` + + // DishonoredReturnTraceNumber is the dishonorment's trace number + DishonoredReturnTraceNumber string `json:"dishonoredReturnTraceNumber"` + + // DishonoredReturnSettlementDate is the dishonorment's settlement date + DishonoredReturnSettlementDate string `json:"dishonoredReturnSettlementDate"` + + // DishonoredReturnReasonCode is the dishonorment's return code + DishonoredReturnReasonCode string `json:"dishonoredReturnReasonCode"` + + // TraceNumber is the trace number for contesting + TraceNumber string `json:"traceNumber"` + + // validator is composed for data validation + validator + // converters is composed for ACH to GoLang Converters + converters + + validateOpts *ValidateOpts +} + +// NewAddenda99Contested returns a new Addenda99Contested with default values for none exported fields +func NewAddenda99Contested() *Addenda99Contested { + Addenda99Contested := &Addenda99Contested{ + TypeCode: "99", + } + return Addenda99Contested +} + +func (Addenda99Contested *Addenda99Contested) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + Addenda99Contested.TypeCode = record[1:3] + Addenda99Contested.ContestedReturnCode = record[3:6] + Addenda99Contested.OriginalEntryTraceNumber = record[6:21] + Addenda99Contested.DateOriginalEntryReturned = record[21:27] + Addenda99Contested.OriginalReceivingDFIIdentification = record[27:35] + Addenda99Contested.OriginalSettlementDate = record[35:38] + Addenda99Contested.ReturnTraceNumber = record[38:53] + Addenda99Contested.ReturnSettlementDate = record[53:56] + Addenda99Contested.ReturnReasonCode = record[56:58] + Addenda99Contested.DishonoredReturnTraceNumber = record[58:73] + Addenda99Contested.DishonoredReturnSettlementDate = record[73:76] + Addenda99Contested.DishonoredReturnReasonCode = record[76:78] + Addenda99Contested.TraceNumber = record[79:94] +} + +func (Addenda99Contested *Addenda99Contested) String() string { + var buf strings.Builder + buf.Grow(94) + + buf.WriteString(entryAddendaPos) + buf.WriteString(Addenda99Contested.TypeCode) + buf.WriteString(Addenda99Contested.ContestedReturnCodeField()) + buf.WriteString(Addenda99Contested.OriginalEntryTraceNumberField()) + buf.WriteString(Addenda99Contested.DateOriginalEntryReturnedField()) + buf.WriteString(Addenda99Contested.OriginalReceivingDFIIdentificationField()) + buf.WriteString(Addenda99Contested.OriginalSettlementDateField()) + buf.WriteString(Addenda99Contested.ReturnTraceNumberField()) + buf.WriteString(Addenda99Contested.ReturnSettlementDateField()) + buf.WriteString(Addenda99Contested.ReturnReasonCodeField()) + buf.WriteString(Addenda99Contested.DishonoredReturnTraceNumberField()) + buf.WriteString(Addenda99Contested.DishonoredReturnSettlementDateField()) + buf.WriteString(Addenda99Contested.DishonoredReturnReasonCodeField()) + buf.WriteString(" ") + buf.WriteString(Addenda99Contested.TraceNumberField()) + + return buf.String() +} + +// SetValidation stores ValidateOpts on the Batch which are to be used to override +// the default NACHA validation rules. +func (Addenda99Contested *Addenda99Contested) SetValidation(opts *ValidateOpts) { + if Addenda99Contested == nil { + return + } + Addenda99Contested.validateOpts = opts +} + +// Validate verifies NACHA rules for Addenda99Contested +func (Addenda99Contested *Addenda99Contested) Validate() error { + if Addenda99Contested.TypeCode == "" { + return fieldError("TypeCode", ErrConstructor, Addenda99Contested.TypeCode) + } + if Addenda99Contested.TypeCode != "99" { + return fieldError("TypeCode", ErrAddendaTypeCode, Addenda99Contested.TypeCode) + } + + // Verify the ContestedReturnReasonCode matches expected values + if Addenda99Contested.validateOpts == nil || !Addenda99Contested.validateOpts.CustomReturnCodes { + // We can validate the Contested ReturnCode + if !IsContestedReturnCode(Addenda99Contested.ContestedReturnCode) { + return fieldError("ContestedReturnCode", ErrAddenda99ContestedReturnCode, Addenda99Contested.ContestedReturnCode) + } + } + + return nil +} + +func IsContestedReturnCode(code string) bool { + switch code { + case "R71", "R72", "R73", "R74", "R75", "R76": + return true + } + return false +} + +func (Addenda99Contested *Addenda99Contested) ContestedReturnCodeField() string { + return Addenda99Contested.stringField(Addenda99Contested.ContestedReturnCode, 3) +} + +func (Addenda99Contested *Addenda99Contested) OriginalEntryTraceNumberField() string { + return Addenda99Contested.stringField(Addenda99Contested.OriginalEntryTraceNumber, 15) +} + +func (Addenda99Contested *Addenda99Contested) DateOriginalEntryReturnedField() string { + return Addenda99Contested.stringField(Addenda99Contested.DateOriginalEntryReturned, 6) +} + +func (Addenda99Contested *Addenda99Contested) OriginalReceivingDFIIdentificationField() string { + return Addenda99Contested.stringField(Addenda99Contested.OriginalReceivingDFIIdentification, 8) +} + +func (Addenda99Contested *Addenda99Contested) OriginalSettlementDateField() string { + return Addenda99Contested.stringField(Addenda99Contested.OriginalSettlementDate, 3) +} + +func (Addenda99Contested *Addenda99Contested) ReturnTraceNumberField() string { + return Addenda99Contested.stringField(Addenda99Contested.ReturnTraceNumber, 15) +} + +func (Addenda99Contested *Addenda99Contested) ReturnSettlementDateField() string { + return Addenda99Contested.stringField(Addenda99Contested.ReturnSettlementDate, 3) +} + +func (Addenda99Contested *Addenda99Contested) ReturnReasonCodeField() string { + return Addenda99Contested.stringField(Addenda99Contested.ReturnReasonCode, 2) +} + +func (Addenda99Contested *Addenda99Contested) DishonoredReturnTraceNumberField() string { + return Addenda99Contested.stringField(Addenda99Contested.DishonoredReturnTraceNumber, 15) +} + +func (Addenda99Contested *Addenda99Contested) DishonoredReturnSettlementDateField() string { + return Addenda99Contested.stringField(Addenda99Contested.DishonoredReturnSettlementDate, 3) +} + +func (Addenda99Contested *Addenda99Contested) DishonoredReturnReasonCodeField() string { + return Addenda99Contested.stringField(Addenda99Contested.DishonoredReturnReasonCode, 2) +} + +func (Addenda99Contested *Addenda99Contested) TraceNumberField() string { + return Addenda99Contested.stringField(Addenda99Contested.TraceNumber, 15) +} diff --git a/addenda99_contested_return_test.go b/addenda99_contested_return_test.go new file mode 100644 index 000000000..f31ae7a45 --- /dev/null +++ b/addenda99_contested_return_test.go @@ -0,0 +1,137 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "bytes" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func mockAddenda99Contested() *Addenda99Contested { + addenda99 := NewAddenda99Contested() + addenda99.ContestedReturnCode = "R71" + addenda99.OriginalEntryTraceNumber = "059999990000301" + addenda99.DateOriginalEntryReturned = "167" + addenda99.OriginalReceivingDFIIdentification = "12391871" + addenda99.OriginalSettlementDate = "164" + addenda99.ReturnTraceNumber = "779999990000301" + addenda99.ReturnSettlementDate = "165" + addenda99.ReturnReasonCode = "01" + addenda99.DishonoredReturnTraceNumber = "889999990000301" + addenda99.DishonoredReturnSettlementDate = "166" + addenda99.DishonoredReturnReasonCode = "67" + addenda99.TraceNumber = "123918710000001" + return addenda99 +} + +func TestAddenda99Contested__Fields(t *testing.T) { + addenda99 := mockAddenda99Contested() + + // shorten some fields + addenda99.OriginalEntryTraceNumber = "0599999900301" + addenda99.ReturnTraceNumber = "1239187101" + addenda99.TraceNumber = "1239187100001" + + require.Equal(t, "R71", addenda99.ContestedReturnCodeField()) + require.Equal(t, "000599999900301", addenda99.OriginalEntryTraceNumberField()) + require.Equal(t, "000167", addenda99.DateOriginalEntryReturnedField()) + require.Equal(t, "12391871", addenda99.OriginalReceivingDFIIdentificationField()) + require.Equal(t, "164", addenda99.OriginalSettlementDateField()) + require.Equal(t, "000001239187101", addenda99.ReturnTraceNumberField()) + require.Equal(t, "165", addenda99.ReturnSettlementDateField()) + require.Equal(t, "01", addenda99.ReturnReasonCodeField()) + require.Equal(t, "889999990000301", addenda99.DishonoredReturnTraceNumberField()) + require.Equal(t, "166", addenda99.DishonoredReturnSettlementDateField()) + require.Equal(t, "67", addenda99.DishonoredReturnReasonCodeField()) + require.Equal(t, "001239187100001", addenda99.TraceNumberField()) +} + +func TestAddenda99Contested(t *testing.T) { + file := NewFile() + file.SetHeader(mockFileHeader()) + file.Control = mockFileControl() + + batch, err := NewBatch(mockBatchHeader()) + require.NoError(t, err) + + ed := mockEntryDetail() + ed.AddendaRecordIndicator = 1 + ed.Category = CategoryDishonoredReturnContested + + addenda99 := mockAddenda99() + ed.Addenda99 = addenda99 + ed.Addenda99Dishonored = mockAddenda99Dishonored() + ed.Addenda99Contested = mockAddenda99Contested() + require.Equal(t, 3, ed.addendaCount()) + + batch.AddEntry(ed) + require.NoError(t, batch.Create()) + require.Equal(t, 4, batch.GetControl().EntryAddendaCount) + + file.AddBatch(batch) + require.NoError(t, file.Create()) + + require.Equal(t, 1, len(file.Batches)) + require.Equal(t, 1, len(file.Batches[0].GetEntries())) + require.Equal(t, 4, file.Batches[0].GetControl().EntryAddendaCount) + require.Equal(t, 4, file.Control.EntryAddendaCount) + + require.NoError(t, file.Validate()) + + var buf bytes.Buffer + if err := NewWriter(&buf).Write(file); err != nil { + t.Fatal(err) + } + + path := filepath.Join("examples", "testdata", "contested-return.ach") + err = os.WriteFile(path, buf.Bytes(), 0600) + if err != nil { + t.Fatal(err) + } +} + +func TestAddenda99Contested__Read(t *testing.T) { + file, err := ReadFile(filepath.Join("test", "testdata", "contested_addenda.txt")) + require.NoError(t, err) + + require.Len(t, file.Batches, 1) + + entries := file.Batches[0].GetEntries() + require.Len(t, entries, 1) + + ed := entries[0] + require.NotNil(t, ed.Addenda99Contested) + + addenda := ed.Addenda99Contested + require.Equal(t, "R72", addenda.ContestedReturnCode) + require.Equal(t, "123456780000069", addenda.OriginalEntryTraceNumber) + require.Equal(t, " ", addenda.DateOriginalEntryReturned) + require.Equal(t, "75639218", addenda.OriginalReceivingDFIIdentification) + require.Equal(t, " ", addenda.OriginalSettlementDate) + require.Equal(t, "756392180000001", addenda.ReturnTraceNumber) + require.Equal(t, "067", addenda.ReturnSettlementDate) + require.Equal(t, "01", addenda.ReturnReasonCode) + require.Equal(t, "123456780000070", addenda.DishonoredReturnTraceNumber) + require.Equal(t, "218", addenda.DishonoredReturnSettlementDate) + require.Equal(t, "68", addenda.DishonoredReturnReasonCode) + require.Equal(t, "364275034310088", addenda.TraceNumber) +} diff --git a/addenda99_dishonored_return.go b/addenda99_dishonored_return.go new file mode 100644 index 000000000..5e7c093e2 --- /dev/null +++ b/addenda99_dishonored_return.go @@ -0,0 +1,175 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "unicode/utf8" +) + +type Addenda99Dishonored struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + + // TypeCode Addenda types code '99' + TypeCode string `json:"typeCode"` + + // DishonoredReturnReasonCode is the return code explaining the dishonorment + DishonoredReturnReasonCode string `json:"dishonoredReturnReasonCode"` + + // OriginalEntryTraceNumber is the trace number specifieid in the initial entry + OriginalEntryTraceNumber string `json:"originalEntryTraceNumber"` + + // OriginalReceivingDFIIdentification is the DFI Identification specifieid in the initial entry + OriginalReceivingDFIIdentification string `json:"originalReceivingDFIIdentification"` + + // ReturnTraceNumber is the TraceNumber used when issuing the return + ReturnTraceNumber string `json:"returnTraceNumber"` + + // ReturnSettlementDate is the date of return issuing + ReturnSettlementDate string `json:"returnSettlementDate"` + + // ReturnReasonCode is the initial return code + ReturnReasonCode string `json:"returnReasonCode"` + + // AddendaInformation is additional data + AddendaInformation string `json:"addendaInformation"` + + // TraceNumber is the trace number for dishonorment + TraceNumber string `json:"traceNumber"` + + // validator is composed for data validation + validator + // converters is composed for ACH to GoLang Converters + converters + + validateOpts *ValidateOpts +} + +// NewAddenda99Dishonored returns a new Addenda99Dishonored with default values for none exported fields +func NewAddenda99Dishonored() *Addenda99Dishonored { + Addenda99Dishonored := &Addenda99Dishonored{ + TypeCode: "99", + } + return Addenda99Dishonored +} + +func (Addenda99Dishonored *Addenda99Dishonored) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + Addenda99Dishonored.TypeCode = record[1:3] + Addenda99Dishonored.DishonoredReturnReasonCode = record[3:6] + Addenda99Dishonored.OriginalEntryTraceNumber = record[6:21] + Addenda99Dishonored.OriginalReceivingDFIIdentification = record[27:35] + Addenda99Dishonored.ReturnTraceNumber = record[38:53] + Addenda99Dishonored.ReturnSettlementDate = record[53:56] + Addenda99Dishonored.ReturnReasonCode = record[56:58] + Addenda99Dishonored.AddendaInformation = record[58:79] + Addenda99Dishonored.TraceNumber = record[79:94] +} + +func (Addenda99Dishonored *Addenda99Dishonored) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(entryAddendaPos) + buf.WriteString(Addenda99Dishonored.TypeCode) + buf.WriteString(Addenda99Dishonored.DishonoredReturnReasonCodeField()) + buf.WriteString(Addenda99Dishonored.OriginalEntryTraceNumberField()) + buf.WriteString(" ") + buf.WriteString(Addenda99Dishonored.OriginalReceivingDFIIdentificationField()) + buf.WriteString(" ") + buf.WriteString(Addenda99Dishonored.ReturnTraceNumberField()) + buf.WriteString(Addenda99Dishonored.ReturnSettlementDateField()) + buf.WriteString(Addenda99Dishonored.ReturnReasonCodeField()) + buf.WriteString(Addenda99Dishonored.AddendaInformationField()) + buf.WriteString(Addenda99Dishonored.TraceNumberField()) + return buf.String() +} + +// SetValidation stores ValidateOpts on the Batch which are to be used to override +// the default NACHA validation rules. +func (Addenda99Dishonored *Addenda99Dishonored) SetValidation(opts *ValidateOpts) { + if Addenda99Dishonored == nil { + return + } + Addenda99Dishonored.validateOpts = opts +} + +func IsDishonoredReturnCode(code string) bool { + switch code { + case "R61", "R67", "R68", "R69", "R70": + return true + } + return false +} + +// Validate verifies NACHA rules for Addenda99Dishonored +func (Addenda99Dishonored *Addenda99Dishonored) Validate() error { + if Addenda99Dishonored.TypeCode == "" { + return fieldError("TypeCode", ErrConstructor, Addenda99Dishonored.TypeCode) + } + if Addenda99Dishonored.TypeCode != "99" { + return fieldError("TypeCode", ErrAddendaTypeCode, Addenda99Dishonored.TypeCode) + } + + // Verify the DishonoredReturnReasonCode matches expected values + if Addenda99Dishonored.validateOpts == nil || !Addenda99Dishonored.validateOpts.CustomReturnCodes { + // We can validate the Dishonored ReturnCode + if !IsDishonoredReturnCode(Addenda99Dishonored.DishonoredReturnReasonCode) { + return fieldError("DishonoredReturnReasonCode", ErrAddenda99DishonoredReturnCode, Addenda99Dishonored.DishonoredReturnReasonCode) + } + } + + return nil +} + +func (Addenda99Dishonored *Addenda99Dishonored) DishonoredReturnReasonCodeField() string { + return Addenda99Dishonored.stringField(Addenda99Dishonored.DishonoredReturnReasonCode, 3) +} + +// OriginalEntryTraceNumberField returns a zero padded TraceNumber string +func (Addenda99Dishonored *Addenda99Dishonored) OriginalEntryTraceNumberField() string { + return Addenda99Dishonored.stringField(Addenda99Dishonored.OriginalEntryTraceNumber, 15) +} + +func (Addenda99Dishonored *Addenda99Dishonored) OriginalReceivingDFIIdentificationField() string { + return Addenda99Dishonored.stringField(Addenda99Dishonored.OriginalReceivingDFIIdentification, 8) +} + +func (Addenda99Dishonored *Addenda99Dishonored) ReturnTraceNumberField() string { + return Addenda99Dishonored.stringField(Addenda99Dishonored.ReturnTraceNumber, 15) +} + +func (Addenda99Dishonored *Addenda99Dishonored) ReturnSettlementDateField() string { + return Addenda99Dishonored.stringField(Addenda99Dishonored.ReturnSettlementDate, 3) +} + +func (Addenda99Dishonored *Addenda99Dishonored) ReturnReasonCodeField() string { + return Addenda99Dishonored.stringField(Addenda99Dishonored.ReturnReasonCode, 2) +} + +func (Addenda99Dishonored *Addenda99Dishonored) AddendaInformationField() string { + return Addenda99Dishonored.alphaField(Addenda99Dishonored.AddendaInformation, 21) +} + +// TraceNumberField returns a zero padded TraceNumber string +func (Addenda99Dishonored *Addenda99Dishonored) TraceNumberField() string { + return Addenda99Dishonored.stringField(Addenda99Dishonored.TraceNumber, 15) +} diff --git a/addenda99_dishonored_return_test.go b/addenda99_dishonored_return_test.go new file mode 100644 index 000000000..331890987 --- /dev/null +++ b/addenda99_dishonored_return_test.go @@ -0,0 +1,55 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func mockAddenda99Dishonored() *Addenda99Dishonored { + addenda99 := NewAddenda99Dishonored() + addenda99.DishonoredReturnReasonCode = "R68" + addenda99.OriginalEntryTraceNumber = "059999990000301" + addenda99.OriginalReceivingDFIIdentification = "12391871" + addenda99.ReturnTraceNumber = "123918710000001" + addenda99.ReturnSettlementDate = "179" + addenda99.ReturnReasonCode = "01" + addenda99.AddendaInformation = "Untimely Return" + addenda99.TraceNumber = "059999990000001" + return addenda99 +} + +func TestAddenda99Dishonored__Fields(t *testing.T) { + addenda99 := mockAddenda99Dishonored() + + // shorten some fields + addenda99.OriginalEntryTraceNumber = "0599999900301" + addenda99.ReturnTraceNumber = "123918710001" + addenda99.TraceNumber = "05999900001" + + require.Equal(t, "R68", addenda99.DishonoredReturnReasonCodeField()) + require.Equal(t, "000599999900301", addenda99.OriginalEntryTraceNumberField()) + require.Equal(t, "12391871", addenda99.OriginalReceivingDFIIdentificationField()) + require.Equal(t, "000123918710001", addenda99.ReturnTraceNumberField()) + require.Equal(t, "179", addenda99.ReturnSettlementDateField()) + require.Equal(t, "01", addenda99.ReturnReasonCodeField()) + require.Equal(t, "Untimely Return ", addenda99.AddendaInformationField()) + require.Equal(t, "000005999900001", addenda99.TraceNumberField()) +} diff --git a/addenda99_test.go b/addenda99_test.go new file mode 100644 index 000000000..1a3884069 --- /dev/null +++ b/addenda99_test.go @@ -0,0 +1,417 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "path/filepath" + "strings" + "testing" + + "github.com/moov-io/base" +) + +func mockAddenda99() *Addenda99 { + addenda99 := NewAddenda99() + addenda99.ReturnCode = "R07" + addenda99.OriginalTrace = "99912340000015" + addenda99.AddendaInformation = "Authorization Revoked" + addenda99.OriginalDFI = "9101298" + + return addenda99 +} + +func testAddenda99Parse(t testing.TB) { + addenda99 := NewAddenda99() + line := "799R07099912340000015 09101298Authorization revoked 091012980000066" + addenda99.Parse(line) + // walk the Addenda99 struct + if addenda99.TypeCode != "99" { + t.Errorf("expected %v got %v", "99", addenda99.TypeCode) + } + if addenda99.ReturnCode != "R07" { + t.Errorf("expected %v got %v", "R07", addenda99.ReturnCode) + } + if addenda99.OriginalTrace != "099912340000015" { + t.Errorf("expected: %v got: %v", "099912340000015", addenda99.OriginalTrace) + } + if addenda99.DateOfDeathField() == "" { + t.Errorf("got: %v", addenda99.DateOfDeath) + } + if addenda99.OriginalDFI != "09101298" { + t.Errorf("expected: %s got: %s", "09101298", addenda99.OriginalDFI) + } + if addenda99.AddendaInformation != "Authorization revoked" { + t.Errorf("expected: %v got: %v", "Authorization revoked", addenda99.AddendaInformation) + } + if addenda99.TraceNumber != "091012980000066" { + t.Errorf("expected: %v got: %v", "091012980000066", addenda99.TraceNumber) + } + if code := addenda99.ReturnCodeField(); code != nil { + if code.Code != "R07" || code.Reason != "Authorization Revoked by Customer" { + t.Errorf("code.Code=%q code.Reason=%q", code.Code, code.Reason) + } + } else { + t.Errorf("got nil ReturnCode") + } +} + +func TestAddenda99__LookupReturnCode(t *testing.T) { + if code := LookupReturnCode(""); code != nil { + t.Error("expected nil ReturnCode") + } + if code := LookupReturnCode("R02"); code == nil { + t.Error("expected ReturnCode") + } else { + if code.Code != "R02" { + t.Errorf("code.Code=%s", code.Code) + } + if code.Reason != "Account Closed" { + t.Errorf("code.Reason=%s", code.Reason) + } + } + if code := LookupReturnCode("R99"); code != nil { + t.Errorf("expected nil: %#v", code) + } +} + +func TestAddenda99Parse(t *testing.T) { + testAddenda99Parse(t) +} + +func BenchmarkAddenda99Parse(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda99Parse(b) + } +} + +func testAddenda99String(t testing.TB) { + addenda99 := NewAddenda99() + line := "799R07099912340000015 09101298Authorization revoked 091012980000066" + addenda99.Parse(line) + + if addenda99.String() != line { + t.Errorf("\n expected: %v\n got : %v", line, addenda99.String()) + } +} + +func TestAddenda99String(t *testing.T) { + testAddenda99String(t) +} + +func BenchmarkAddenda99String(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda99String(b) + } +} + +// This is not an exported function but utilized for validation +func testAddenda99MakeReturnCodeDict(t testing.TB) { + codes := makeReturnCodeDict() + // check if known code is present + _, prs := codes["R01"] + if !prs { + t.Error("Return Code R01 was not found in the ReturnCodeDict") + } + // check if invalid code is present + _, prs = codes["ABC"] + if prs { + t.Error("Valid return for an invalid return code key") + } +} + +func TestAddenda99MakeReturnCodeDict(t *testing.T) { + testAddenda99MakeReturnCodeDict(t) +} + +func BenchmarkAddenda99MakeReturnCodeDict(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda99MakeReturnCodeDict(b) + } +} + +func testAddenda99ValidateTrue(t testing.TB) { + addenda99 := mockAddenda99() + addenda99.ReturnCode = "R13" + err := addenda99.Validate() + // no error expected + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +func TestAddenda99ValidateTrue(t *testing.T) { + testAddenda99ValidateTrue(t) +} + +func BenchmarkAddenda99ValidateTrue(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda99ValidateTrue(b) + } +} + +func testAddenda99ValidateReturnCodeFalse(t testing.TB) { + addenda99 := mockAddenda99() + addenda99.ReturnCode = "" + err := addenda99.Validate() + if !base.Match(err, ErrAddenda99ReturnCode) { + t.Errorf("%T: %s", err, err) + } +} + +func TestAddenda99ValidateReturnCodeFalse(t *testing.T) { + testAddenda99ValidateReturnCodeFalse(t) +} + +func BenchmarkAddenda99ValidateReturnCodeFalse(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda99ValidateReturnCodeFalse(b) + } +} + +func testAddenda99OriginalTraceField(t testing.TB) { + addenda99 := mockAddenda99() + addenda99.OriginalTrace = "12345" + if addenda99.OriginalTraceField() != "000000000012345" { + t.Errorf("expected %v received %v", "000000000012345", addenda99.OriginalTraceField()) + } +} + +func TestAddenda99OriginalTraceField(t *testing.T) { + testAddenda99OriginalTraceField(t) +} + +func BenchmarkAddenda99OriginalTraceField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda99OriginalTraceField(b) + } +} + +func testAddenda99DateOfDeathField(t testing.TB) { + addenda99 := mockAddenda99() + // Check for all zeros + if addenda99.DateOfDeathField() != " " { + t.Errorf("expected %v received %v", " ", addenda99.DateOfDeathField()) + } + // Year: 1978 Month: October Day: 23 + addenda99.DateOfDeath = "781023" + if addenda99.DateOfDeathField() != "781023" { + t.Errorf("expected %v received %v", "781023", addenda99.DateOfDeathField()) + } +} + +func TestAddenda99DateOfDeathField(t *testing.T) { + testAddenda99DateOfDeathField(t) +} +func BenchmarkAddenda99DateOfDeathField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda99DateOfDeathField(b) + } +} + +func testAddenda99OriginalDFIField(t testing.TB) { + addenda99 := mockAddenda99() + exp := "09101298" + if addenda99.OriginalDFIField() != exp { + t.Errorf("expected %v received %v", exp, addenda99.OriginalDFIField()) + } +} + +func TestAddenda99OriginalDFIField(t *testing.T) { + testAddenda99OriginalDFIField(t) +} + +func BenchmarkAddenda99OriginalDFIField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda99OriginalDFIField(b) + } +} + +func testAddenda99AddendaInformationField(t testing.TB) { + addenda99 := mockAddenda99() + exp := "Authorization Revoked " + if addenda99.AddendaInformationField() != exp { + t.Errorf("expected %v received %v", exp, addenda99.AddendaInformationField()) + } +} + +func TestAddenda99AddendaInformationField(t *testing.T) { + testAddenda99AddendaInformationField(t) +} + +func BenchmarkAddenda99AddendaInformationField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda99AddendaInformationField(b) + } +} + +func testAddenda99TraceNumberField(t testing.TB) { + addenda99 := mockAddenda99() + addenda99.TraceNumber = "91012980000066" + exp := "091012980000066" + if addenda99.TraceNumberField() != exp { + t.Errorf("expected %v received %v", exp, addenda99.TraceNumberField()) + } +} + +func TestAddenda99TraceNumberField(t *testing.T) { + testAddenda99TraceNumberField(t) +} + +func BenchmarkAddenda99TraceNumberField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda99TraceNumberField(b) + } +} + +// testAddenda99TypeCode99 TypeCode is 99 +func testAddenda99TypeCode99(t testing.TB) { + addenda99 := mockAddenda99() + addenda99.TypeCode = "05" + err := addenda99.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda99TypeCode99 tests TypeCode is 99 +func TestAddenda99TypeCode99(t *testing.T) { + testAddenda99TypeCode99(t) +} + +// BenchmarkAddenda99TypeCode99 benchmarks TypeCode is 99 +func BenchmarkAddenda99TypeCode99(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda99TypeCode99(b) + } +} + +// testAddenda99TypeCodeNil validates TypeCode is "" +func testAddenda99TypeCodeNil(t testing.TB) { + addenda99 := mockAddenda99() + addenda99.TypeCode = "" + err := addenda99.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda99TypeCodeES tests TypeCode is "" +func TestAddenda99TypeCodeNil(t *testing.T) { + testAddenda99TypeCodeNil(t) +} + +// BenchmarkAddenda99TypeCodeNil benchmarks TypeCode is "" +func BenchmarkAddenda99TypeCodeNil(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda99TypeCodeNil(b) + } +} + +// TestAddenda99RuneCountInString validates RuneCountInString +func TestAddenda99RuneCountInString(t *testing.T) { + addenda99 := NewAddenda99() + var line = "799" + addenda99.Parse(line) + + if addenda99.DateOfDeath != "" { + t.Error("Parsed with an invalid RuneCountInString not equal to 94") + } +} + +func TestAddenda99__MissingFileHeaderControl(t *testing.T) { + // We have a usecase where returned files don't contain the FileHeader + // or FileControl records. Our parser can handle this, but returns + // errors are they're expected per the NACHA spec. + // + // This test just checks we can parse the file and get the expected errors. + file, err := ReadFile(filepath.Join("test", "testdata", "return-no-file-header-control.ach")) + if err == nil { + t.Error("expected an error") + } + if !strings.Contains(err.Error(), ErrFileHeader.Error()) { + t.Errorf("unexpected error: %v", err) + } + if !strings.Contains(err.Error(), ErrFileControl.Error()) { + t.Errorf("unexpected error: %v", err) + } + + if len(file.Batches) != 1 { + t.Errorf("got %d batches", len(file.Batches)) + } + if entries := file.Batches[0].GetEntries(); len(entries) != 1 { + t.Errorf("got %d entries", len(entries)) + } +} + +func TestAddenda99__SetValidation(t *testing.T) { + addenda99 := NewAddenda99() + addenda99.SetValidation(&ValidateOpts{ + CustomReturnCodes: true, + }) + + addenda99.ReturnCode = "@#$" // can safely say this will never be a real ReasonCode + if err := addenda99.Validate(); err != nil { + t.Fatal(err) + } + + addenda99.SetValidation(&ValidateOpts{ + CustomReturnCodes: false, + }) + if err := addenda99.Validate(); err == nil { + t.Fatal("Did not flag invalid reason code") + } + addenda99 = nil + addenda99.SetValidation(&ValidateOpts{}) +} + +func TestAddenda99__CustomReturnCode(t *testing.T) { + mockBatch := mockBatch() + // Add a Addenda Return to the mock batch + if len(mockBatch.Entries) != 1 { + t.Fatal("Expected 1 batch entry") + } + addenda99 := NewAddenda99() + addenda99.SetValidation(&ValidateOpts{ + CustomReturnCodes: true, + }) + + addenda99.ReturnCode = "@#$" // can safely say this will never be a real ReasonCode + mockBatch.Entries[0].Addenda99 = addenda99 + mockBatch.Entries[0].Category = CategoryReturn + mockBatch.Entries[0].AddendaRecordIndicator = 1 + + // replace last 2 of TraceNumber + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + if err := mockBatch.verify(); err != nil { + t.Errorf("%T: %s", err, err) + } +} diff --git a/addenda_internal_test.go b/addenda_internal_test.go deleted file mode 100644 index 82425c83d..000000000 --- a/addenda_internal_test.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2017 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. - -package ach - -import ( - "strings" - "testing" -) - -func mockAddenda() Addenda { - addenda := NewAddenda() - addenda.EntryDetailSequenceNumber = 1234567 - return addenda -} - -func TestMockAddenda(t *testing.T) { - addenda := mockAddenda() - if err := addenda.Validate(); err != nil { - t.Error("mockAddenda does not validate and will break other tests") - } - if addenda.EntryDetailSequenceNumber != 1234567 { - t.Error("EntryDetailSequenceNumber dependent default value has changed") - } -} - -func TestParseAddenda(t *testing.T) { - var line = "710WEB DIEGO MAY 00010000001" - - r := NewReader(strings.NewReader(line)) - r.addCurrentBatch(NewBatchPPD()) - r.currentBatch.GetHeader().StandardEntryClassCode = "PPD" - r.currentBatch.AddEntry(&EntryDetail{TransactionCode: 22, AddendaRecordIndicator: 1}) - r.line = line - err := r.parseAddenda() - if err != nil { - t.Errorf("%T: %s", err, err) - } - record := r.currentBatch.GetEntries()[0].Addendum[0] - - if record.recordType != "7" { - t.Errorf("RecordType Expected '7' got: %v", record.recordType) - } - if record.TypeCode != "10" { - t.Errorf("TypeCode Expected 10 got: %v", record.TypeCode) - } - if record.PaymentRelatedInformationField() != "WEB DIEGO MAY " { - t.Errorf("PaymentRelatedInformation Expected 'WEB DIEGO MAY ' got: %v", record.PaymentRelatedInformationField()) - } - if record.SequenceNumberField() != "0001" { - t.Errorf("SequenceNumber Expected '0001' got: %v", record.SequenceNumberField()) - } - if record.EntryDetailSequenceNumberField() != "0000001" { - t.Errorf("EntryDetailSequenceNumber Expected '0000001' got: %v", record.EntryDetailSequenceNumberField()) - } -} - -// TestAddendaString validats that a known parsed file can be return to a string of the same value -func TestAddendaString(t *testing.T) { - var line = "710WEB DIEGO MAY 00010000001" - r := NewReader(strings.NewReader(line)) - r.addCurrentBatch(NewBatchPPD()) - r.currentBatch.GetHeader().StandardEntryClassCode = "PPD" - r.currentBatch.AddEntry(&EntryDetail{AddendaRecordIndicator: 1}) - r.line = line - err := r.parseAddenda() - if err != nil { - t.Errorf("%T: %s", err, err) - } - record := r.currentBatch.GetEntries()[0].Addendum[0] - if record.String() != line { - t.Errorf("Strings do not match") - } -} - -func TestValidateAddendaRecordType(t *testing.T) { - addenda := mockAddenda() - addenda.recordType = "2" - if err := addenda.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "recordType" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestValidateAddendaTypeCode(t *testing.T) { - addenda := mockAddenda() - addenda.TypeCode = "23" - if err := addenda.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "TypeCode" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestAddendaFieldInclusion(t *testing.T) { - addenda := mockAddenda() - addenda.EntryDetailSequenceNumber = 0 - if err := addenda.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "EntryDetailSequenceNumber" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestAddendaFieldInclusionRecordType(t *testing.T) { - addenda := mockAddenda() - addenda.recordType = "" - if err := addenda.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestAddendaPaymentRelatedInformationAlphaNumeric(t *testing.T) { - addenda := mockAddenda() - addenda.PaymentRelatedInformation = "®©" - if err := addenda.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "PaymentRelatedInformation" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestAddendaTyeCodeNil(t *testing.T) { - addenda := mockAddenda() - addenda.TypeCode = "" - if err := addenda.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "TypeCode" { - t.Errorf("%T: %s", err, err) - } - } - } -} diff --git a/advBatchControl.go b/advBatchControl.go new file mode 100644 index 000000000..67dad4eae --- /dev/null +++ b/advBatchControl.go @@ -0,0 +1,175 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "fmt" + "strconv" + "strings" + "unicode/utf8" +) + +// ADVBatchControl contains entry counts, dollar total and has totals for all +// entries contained in the preceding batch +type ADVBatchControl struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // This should be the same as BatchHeader ServiceClassCode for ADV: AutomatedAccountingAdvices. + ServiceClassCode int `json:"serviceClassCode"` + // EntryAddendaCount is a tally of each Entry Detail Record and each Addenda + // Record processed, within either the batch or file as appropriate. + EntryAddendaCount int `json:"entryAddendaCount"` + // validate the Receiving DFI Identification in each Entry Detail Record is hashed + // to provide a check against inadvertent alteration of data contents due + // to hardware failure or program error + // + // In this context the Entry Hash is the sum of the corresponding fields in the + // Entry Detail Records on the file. + EntryHash int `json:"entryHash"` + // TotalDebitEntryDollarAmount Contains accumulated Entry debit totals within the batch. + TotalDebitEntryDollarAmount int `json:"totalDebit"` + // TotalCreditEntryDollarAmount Contains accumulated Entry credit totals within the batch. + TotalCreditEntryDollarAmount int `json:"totalCredit"` + // ACHOperatorData is an alphanumeric code used to identify an ACH Operator + ACHOperatorData string `json:"achOperatorData"` + // ODFIIdentification the routing number is used to identify the DFI originating entries within a given branch. + ODFIIdentification string `json:"ODFIIdentification"` + // BatchNumber this number is assigned in ascending sequence to each batch by the ODFI + // or its Sending Point in a given file of entries. Since the batch number + // in the Batch Header Record and the Batch Control Record is the same, + // the ascending sequence number should be assigned by batch and not by record. + BatchNumber int `json:"batchNumber"` + // validator is composed for data validation + validator + // converters is composed for ACH to golang Converters + converters +} + +// Parse takes the input record string and parses the EntryDetail values +func (bc *ADVBatchControl) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + // 1-1 Always "8" + // 2-4 This is the same as the "Service code" field in previous Batch Header Record + bc.ServiceClassCode = bc.parseNumField(record[1:4]) + // 5-10 Total number of Entry Detail Record in the batch + bc.EntryAddendaCount = bc.parseNumField(record[4:10]) + // 11-20 Total of all positions 4-11 on each Entry Detail Record in the batch. This is essentially the sum of all the RDFI routing numbers in the batch. + // If the sum exceeds 10 digits (because you have lots of Entry Detail Records), lop off the most significant digits of the sum until there are only 10 + bc.EntryHash = bc.parseNumField(record[10:20]) + // 21-32 Number of cents of debit entries within the batch + bc.TotalDebitEntryDollarAmount = bc.parseNumField(record[20:40]) + // 33-44 Number of cents of credit entries within the batch + bc.TotalCreditEntryDollarAmount = bc.parseNumField(record[40:60]) + // 45-54 ACH Operator Data + bc.ACHOperatorData = strings.TrimSpace(record[60:79]) + // 80-87 This is the same as the "ODFI identification" field in previous Batch Header Record + bc.ODFIIdentification = bc.parseStringField(record[79:87]) + // 88-94 This is the same as the "Batch number" field in previous Batch Header Record + bc.BatchNumber = bc.parseNumField(record[87:94]) +} + +// NewADVBatchControl returns a new ADVBatchControl with default values for none exported fields +func NewADVBatchControl() *ADVBatchControl { + return &ADVBatchControl{ + ServiceClassCode: AutomatedAccountingAdvices, + EntryHash: 1, + BatchNumber: 1, + } +} + +// String writes the ADVBatchControl struct to a 94 character string. +func (bc *ADVBatchControl) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(batchControlPos) + buf.WriteString(fmt.Sprintf("%v", bc.ServiceClassCode)) + buf.WriteString(bc.EntryAddendaCountField()) + buf.WriteString(bc.EntryHashField()) + buf.WriteString(bc.TotalDebitEntryDollarAmountField()) + buf.WriteString(bc.TotalCreditEntryDollarAmountField()) + buf.WriteString(bc.ACHOperatorDataField()) + buf.WriteString(bc.ODFIIdentificationField()) + buf.WriteString(bc.BatchNumberField()) + return buf.String() +} + +// Validate performs NACHA format rule checks on the record and returns an error if not Validated +// The first error encountered is returned and stops that parsing. +func (bc *ADVBatchControl) Validate() error { + if err := bc.fieldInclusion(); err != nil { + return err + } + if err := bc.isServiceClass(bc.ServiceClassCode); err != nil { + return fieldError("ServiceClassCode", err, strconv.Itoa(bc.ServiceClassCode)) + } + + if err := bc.isAlphanumeric(bc.ACHOperatorData); err != nil { + return fieldError("ACHOperatorData", err, bc.ACHOperatorData) + } + return nil +} + +// fieldInclusion validate mandatory fields are not default values. If fields are +// invalid the ACH transfer will be returned. +func (bc *ADVBatchControl) fieldInclusion() error { + if bc.ServiceClassCode == 0 { + return fieldError("ServiceClassCode", ErrConstructor, strconv.Itoa(bc.ServiceClassCode)) + } + if bc.ODFIIdentification == "000000000" || bc.ODFIIdentification == "" { + return fieldError("ODFIIdentification", ErrConstructor, bc.ODFIIdentificationField()) + } + return nil +} + +// EntryAddendaCountField gets a string of the addenda count zero padded +func (bc *ADVBatchControl) EntryAddendaCountField() string { + return bc.numericField(bc.EntryAddendaCount, 6) +} + +// EntryHashField get a zero padded EntryHash +func (bc *ADVBatchControl) EntryHashField() string { + return bc.numericField(bc.EntryHash, 10) +} + +// TotalDebitEntryDollarAmountField get a zero padded Debit Entry Amount +func (bc *ADVBatchControl) TotalDebitEntryDollarAmountField() string { + return bc.numericField(bc.TotalDebitEntryDollarAmount, 20) +} + +// TotalCreditEntryDollarAmountField get a zero padded Credit Entry Amount +func (bc *ADVBatchControl) TotalCreditEntryDollarAmountField() string { + return bc.numericField(bc.TotalCreditEntryDollarAmount, 20) +} + +// ACHOperatorDataField get the ACHOperatorData right padded +func (bc *ADVBatchControl) ACHOperatorDataField() string { + return bc.alphaField(bc.ACHOperatorData, 19) +} + +// ODFIIdentificationField get the odfi number zero padded +func (bc *ADVBatchControl) ODFIIdentificationField() string { + return bc.stringField(bc.ODFIIdentification, 8) +} + +// BatchNumberField gets a string of the batch number zero padded +func (bc *ADVBatchControl) BatchNumberField() string { + return bc.numericField(bc.BatchNumber, 7) +} diff --git a/advBatchControl_test.go b/advBatchControl_test.go new file mode 100644 index 000000000..6c84cdd6e --- /dev/null +++ b/advBatchControl_test.go @@ -0,0 +1,293 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "testing" + + "github.com/moov-io/base" +) + +func mockADVBatchControl() *ADVBatchControl { + bc := NewADVBatchControl() + bc.ServiceClassCode = CreditsOnly + bc.ACHOperatorData = "T-BANK" + bc.ODFIIdentification = "12104288" + return bc +} + +// testMockADVBatchControl tests mock batch control +func testMockADVBatchControl(t testing.TB) { + bc := mockADVBatchControl() + if err := bc.Validate(); err != nil { + t.Error("mockADVBatchControl does not validate and will break other tests") + } + if bc.ServiceClassCode != CreditsOnly { + t.Error("ServiceClassCode depedendent default value has changed") + } + if bc.ACHOperatorData != "T-BANK" { + t.Error("ACHOperatorData depedendent default value has changed") + } + if bc.ODFIIdentification != "12104288" { + t.Error("ODFIIdentification depedendent default value has changed") + } +} + +// TestMockADVBatchControl test mock batch control +func TestMockADVBatchControl(t *testing.T) { + testMockADVBatchControl(t) +} + +// BenchmarkMockADVBatchControl benchmarks mock batch control +func BenchmarkMockADVBatchControl(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testMockADVBatchControl(b) + } +} + +// TestParseADVBatchControl parses a known Batch ControlRecord string. +func testParseADVBatchControl(t testing.TB) { + var line = "828000000100053200010000000000000001050000000000000000000000T-BANK 076401250000001" + r := NewReader(strings.NewReader(line)) + r.line = line + bh := BatchHeader{BatchNumber: 1, + StandardEntryClassCode: ADV, + ServiceClassCode: AutomatedAccountingAdvices, + CompanyIdentification: "origid", + ODFIIdentification: "12104288"} + r.addCurrentBatch(NewBatchADV(&bh)) + + r.currentBatch.AddADVEntry(mockADVEntryDetail()) + if err := r.parseBatchControl(); err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.currentBatch.GetADVControl() + + if record.ServiceClassCode != AutomatedAccountingAdvices { + t.Errorf("ServiceClassCode Expected '280' got: %v", record.ServiceClassCode) + } + if record.EntryAddendaCountField() != "000001" { + t.Errorf("EntryAddendaCount Expected '000001' got: %v", record.EntryAddendaCountField()) + } + if record.EntryHashField() != "0005320001" { + t.Errorf("EntryHash Expected '0005320001' got: %v", record.EntryHashField()) + } + if record.TotalDebitEntryDollarAmountField() != "00000000000000010500" { + t.Errorf("TotalDebitEntryDollarAmount Expected '00000000000000010500' got: %v", record.TotalDebitEntryDollarAmountField()) + } + if record.TotalCreditEntryDollarAmountField() != "00000000000000000000" { + t.Errorf("TotalCreditEntryDollarAmount Expected '00000000000000000000' got: %v", record.TotalCreditEntryDollarAmountField()) + } + if record.ACHOperatorDataField() != "T-BANK " { + t.Errorf("ACHOperatorData Expected 'T-BANK ' got: %v", record.ACHOperatorDataField()) + } + if record.ODFIIdentificationField() != "07640125" { + t.Errorf("OdfiIdentification Expected '07640125' got: %v", record.ODFIIdentificationField()) + } + if record.BatchNumberField() != "0000001" { + t.Errorf("BatchNumber Expected '0000001' got: %v", record.BatchNumberField()) + } +} + +// TestParseADVBatchControl tests parsing a known Batch ControlRecord string. +func TestParseADVBatchControl(t *testing.T) { + testParseADVBatchControl(t) +} + +// BenchmarkParseADVBatchControl benchmarks parsing a known Batch ControlRecord string. +func BenchmarkParseADVBatchControl(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testParseADVBatchControl(b) + } +} + +// testADVBCString validates that a known parsed file can be return to a string of the same value +func testADVBCString(t testing.TB) { + var line = "822500000100053200010000000000000001050000000000000000000000T-BANK 076401250000001" + r := NewReader(strings.NewReader(line)) + r.line = line + bh := BatchHeader{BatchNumber: 1, + StandardEntryClassCode: ADV, + ServiceClassCode: AutomatedAccountingAdvices, + CompanyIdentification: "origid", + ODFIIdentification: "12104288"} + r.addCurrentBatch(NewBatchADV(&bh)) + + r.currentBatch.AddADVEntry(mockADVEntryDetail()) + if err := r.parseBatchControl(); err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.currentBatch.GetADVControl() + + if record.String() != line { + t.Errorf("Strings do not match") + } +} + +// TestADVBCString tests validating that a known parsed file can be return to a string of the same value +func TestADVBCString(t *testing.T) { + testADVBCString(t) +} + +// BenchmarkADVBCString benchmarks validating that a known parsed file can be return to a string of the same value +func BenchmarkADVBCString(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testADVBCString(b) + } +} + +// testADVisServiceClassErr verifies service class code +func testADVBCisServiceClassErr(t testing.TB) { + bc := mockADVBatchControl() + bc.ServiceClassCode = 123 + err := bc.Validate() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVBCisServiceClassErr tests verifying service class code +func TestADVBCisServiceClassErr(t *testing.T) { + testADVBCisServiceClassErr(t) +} + +// BenchmarkADVBCisServiceClassErr benchmarks verifying service class code +func BenchmarkADVBCisServiceClassErr(b *testing.B) { + for i := 0; i < b.N; i++ { + testADVBCisServiceClassErr(b) + } +} + +// testADVBCBatchNumber verifies batch number +func testADVBCBatchNumber(t testing.TB) { + bc := mockADVBatchControl() + bc.BatchNumber = 0 + err := bc.Validate() + // TODO: are we expecting there to be no errors here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVBCBatchNumber tests verifying batch number +func TestADVBCBatchNumber(t *testing.T) { + testADVBCBatchNumber(t) +} + +// BenchmarkADVBCBatchNumber benchmarks verifying batch number +func BenchmarkADVBCBatchNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testADVBCBatchNumber(b) + } +} + +// testADVBCACHOperatorDataAlphaNumeric verifies Company Identification is AlphaNumeric +func testADVBCACHOperatorDataAlphaNumeric(t testing.TB) { + bc := mockADVBatchControl() + bc.ACHOperatorData = "®" + err := bc.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVBCACHOperatorDataAlphaNumeric tests verifying Company Identification is AlphaNumeric +func TestADVBCACHOperatorDataAlphaNumeric(t *testing.T) { + testADVBCACHOperatorDataAlphaNumeric(t) +} + +// BenchmarkADVACHOperatorDataAlphaNumeric benchmarks verifying Company Identification is AlphaNumeric +func BenchmarkADVACHOperatorDataAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testADVBCACHOperatorDataAlphaNumeric(b) + } +} + +// testADVBCFieldInclusionServiceClassCode verifies Service Class Code is included +func testADVBCFieldInclusionServiceClassCode(t testing.TB) { + bc := mockADVBatchControl() + bc.ServiceClassCode = 0 + err := bc.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVBCFieldInclusionServiceClassCode tests verifying Service Class Code is included +func TestADVBCFieldInclusionServiceClassCode(t *testing.T) { + testADVBCFieldInclusionServiceClassCode(t) +} + +// BenchmarkADVBCFieldInclusionServiceClassCod benchmarks verifying Service Class Code is included +func BenchmarkADVBCFieldInclusionServiceClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testADVBCFieldInclusionServiceClassCode(b) + } +} + +// testADVBCFieldInclusionODFIIdentification verifies batch control ODFIIdentification +func testADVBCFieldInclusionODFIIdentification(t testing.TB) { + bc := mockADVBatchControl() + bc.ODFIIdentification = "000000000" + err := bc.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVBCFieldInclusionODFIIdentification tests verifying batch control ODFIIdentification +func TestADVBCFieldInclusionODFIIdentification(t *testing.T) { + testADVBCFieldInclusionODFIIdentification(t) +} + +// BenchmarkADVBCFieldInclusionODFIIdentification benchmarks verifying batch control ODFIIdentification +func BenchmarkADVBCFieldInclusionODFIIdentification(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testADVBCFieldInclusionODFIIdentification(b) + } +} + +// testADVBatchControlLength verifies batch control length +func testADVBatchControlLength(t testing.TB) { + bc := NewADVBatchControl() + recordLength := len(bc.String()) + if recordLength != 94 { + t.Errorf("Instantiated length of Batch Control string is not 94 but %v", recordLength) + } +} + +// TestADVBatchControlLength tests verifying batch control length +func TestADVBatchControlLength(t *testing.T) { + testADVBatchControlLength(t) +} + +// BenchmarkADVBatchControlLength benchmarks verifying batch control length +func BenchmarkADVBatchControlLength(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testADVBatchControlLength(b) + } +} diff --git a/advEntryDetail.go b/advEntryDetail.go new file mode 100644 index 000000000..7e141e8a0 --- /dev/null +++ b/advEntryDetail.go @@ -0,0 +1,309 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "fmt" + "strconv" + "strings" + "unicode/utf8" +) + +// ADVEntryDetail contains the actual transaction data for an individual entry. +// Fields include those designating the entry as a deposit (credit) or +// withdrawal (debit), the transit routing number for the entry recipient's financial +// institution, the account number (left justify,no zero fill), name, and dollar amount. +type ADVEntryDetail struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // TransactionCode representing Accounting Entries + // Credit for ACH debits originated - 81 + // Debit for ACH credits originated - 82 + // Credit for ACH credits received 83 + // Debit for ACH debits received 84 + // Credit for ACH credits in rejected batches 85 + // Debit for ACH debits in rejected batches - 86 + // Summary credit for respondent ACH activity - 87 + // Summary debit for respondent ACH activity - 88 + TransactionCode int `json:"transactionCode"` + // RDFIIdentification is the RDFI's routing number without the last digit. + // Receiving Depository Financial Institution + RDFIIdentification string `json:"RDFIIdentification"` + // CheckDigit the last digit of the RDFI's routing number + CheckDigit string `json:"checkDigit"` + // DFIAccountNumber is the receiver's bank account number you are crediting/debiting. + // It important to note that this is an alphanumeric field, so its space padded, no zero padded + DFIAccountNumber string `json:"DFIAccountNumber"` + // Amount Number of cents you are debiting/crediting this account + Amount int `json:"amount"` + // AdviceRoutingNumber + AdviceRoutingNumber string `json:"adviceRoutingNumber"` + // FileIdentification + FileIdentification string `json:"fileIdentification,omitempty"` + // ACHOperatorData + ACHOperatorData string `json:"achOperatorData,omitempty"` + // IndividualName The name of the receiver, usually the name on the bank account + IndividualName string `json:"individualName"` + // DiscretionaryData allows ODFIs to include codes, of significance only to them, + // to enable specialized handling of the entry. There will be no + // standardized interpretation for the value of this field. It can either + // be a single two-character code, or two distinct one-character codes, + // according to the needs of the ODFI and/or Originator involved. This + // field must be returned intact for any returned entry. + DiscretionaryData string `json:"discretionaryData,omitempty"` + // AddendaRecordIndicator indicates the existence of an Addenda Record. + // A value of "1" indicates that one ore more addenda records follow, + // and "0" means no such record is present. + AddendaRecordIndicator int `json:"addendaRecordIndicator,omitempty"` + // ACHOperatorRoutingNumber + ACHOperatorRoutingNumber string `json:"achOperatorRoutingNumber"` + // JulianDay + JulianDay int `json:"julianDay"` + // SequenceNumber + SequenceNumber int `json:"sequenceNumber"` + // Addenda99 for use with Returns + Addenda99 *Addenda99 `json:"addenda99,omitempty"` + // Category defines if the entry is a Forward, Return, or NOC + Category string `json:"category,omitempty"` + // validator is composed for data validation + validator + // converters is composed for ACH to golang Converters + converters +} + +const ( + // ADV Transaction Code Values + // These transaction codes represent accounting entries + + // CreditForDebitsOriginated is an accounting entry credit for ACH debits originated + CreditForDebitsOriginated = 81 + // CreditForCreditsReceived is an accounting entry credits for ACH credits received + CreditForCreditsReceived = 83 + // CreditForCreditsRejected is an accounting entry credit for ACH credits in rejected batches + CreditForCreditsRejected = 85 + // CreditSummary is an accounting entry for summary credit for respondent ACH activity + CreditSummary = 87 + + // DebitForCreditsOriginated is an accounting entry debit for ACH credits originated + DebitForCreditsOriginated = 82 + // DebitForDebitsReceived is an accounting entry debit for for ACH debits received + DebitForDebitsReceived = 84 + // DebitForDebitsRejectedBatches is an accounting entry debit for ACH debits in rejected batches + DebitForDebitsRejectedBatches = 86 + // DebitSummary is an accounting entry for summary debit for respondent ACH activity + DebitSummary = 88 +) + +// NewADVEntryDetail returns a new ADVEntryDetail with default values for non exported fields +func NewADVEntryDetail() *ADVEntryDetail { + entry := &ADVEntryDetail{ + Category: CategoryForward, + } + return entry +} + +// Parse takes the input record string and parses the ADVEntryDetail values +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm +// successful parsing and data validity. + +// Parse ADVEntryDetail +func (ed *ADVEntryDetail) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + // 1-1 Always "6" + // 2-3 is checking credit 22 debit 27 savings credit 32 debit 37 + ed.TransactionCode = ed.parseNumField(record[1:3]) + // 4-11 the RDFI's routing number without the last digit. + ed.RDFIIdentification = ed.parseStringField(record[3:11]) + // 12-12 The last digit of the RDFI's routing number + ed.CheckDigit = ed.parseStringField(record[11:12]) + // 13-27 The receiver's bank account number you are crediting/debiting + ed.DFIAccountNumber = record[12:27] + // 28-39 Number of cents you are debiting/crediting this account + ed.Amount = ed.parseNumField(record[27:39]) + // 40-48 Advice Routing Number + ed.AdviceRoutingNumber = ed.parseStringField(record[39:48]) + // 49-53 File Identification + ed.FileIdentification = ed.parseStringField(record[48:53]) + // 54-54 ACH Operator Data + ed.ACHOperatorData = ed.parseStringField(record[53:54]) + // 55-76 Individual Name + ed.IndividualName = record[54:76] + // 77-78 allows ODFIs to include codes of significance only to them, normally blank + ed.DiscretionaryData = record[76:78] + // 79-79 1 if addenda exists 0 if it does not + ed.AddendaRecordIndicator = ed.parseNumField(record[78:79]) + // 80-87 + ed.ACHOperatorRoutingNumber = ed.parseStringField(record[79:87]) + // 88-90 + ed.JulianDay = ed.parseNumField(record[87:90]) + // 91-94 + ed.SequenceNumber = ed.parseNumField(record[90:94]) +} + +// String writes the ADVEntryDetail struct to a 94 character string. +func (ed *ADVEntryDetail) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(entryDetailPos) + buf.WriteString(fmt.Sprintf("%v", ed.TransactionCode)) + buf.WriteString(ed.RDFIIdentificationField()) + buf.WriteString(ed.CheckDigit) + buf.WriteString(ed.DFIAccountNumberField()) + buf.WriteString(ed.AmountField()) + buf.WriteString(ed.AdviceRoutingNumberField()) + buf.WriteString(ed.FileIdentificationField()) + buf.WriteString(ed.ACHOperatorDataField()) + buf.WriteString(ed.IndividualNameField()) + buf.WriteString(ed.DiscretionaryDataField()) + buf.WriteString(fmt.Sprintf("%v", ed.AddendaRecordIndicator)) + buf.WriteString(ed.ACHOperatorRoutingNumberField()) + buf.WriteString(ed.JulianDateDayField()) + buf.WriteString(ed.SequenceNumberField()) + return buf.String() +} + +// Validate performs NACHA format rule checks on the record and returns an error if not Validated +// The first error encountered is returned and stops that parsing. +func (ed *ADVEntryDetail) Validate() error { + if err := ed.fieldInclusion(); err != nil { + return err + } + if err := ed.isTransactionCode(ed.TransactionCode); err != nil { + return fieldError("TransactionCode", err, strconv.Itoa(ed.TransactionCode)) + } + if err := ed.isAlphanumeric(ed.DFIAccountNumber); err != nil { + return fieldError("DFIAccountNumber", err, ed.DFIAccountNumber) + } + if err := ed.isAlphanumeric(ed.AdviceRoutingNumber); err != nil { + return fieldError("AdviceRoutingNumber", err, ed.AdviceRoutingNumber) + } + if err := ed.isAlphanumeric(ed.IndividualName); err != nil { + return fieldError("IndividualName", err, ed.IndividualName) + } + if err := ed.isAlphanumeric(ed.DiscretionaryData); err != nil { + return fieldError("DiscretionaryData", err, ed.DiscretionaryData) + } + if err := ed.isAlphanumeric(ed.ACHOperatorRoutingNumber); err != nil { + return fieldError("ACHOperatorRoutingNumber", err, ed.ACHOperatorRoutingNumber) + } + calculated := ed.CalculateCheckDigit(ed.RDFIIdentificationField()) + + edCheckDigit, _ := strconv.Atoi(ed.CheckDigit) + + if calculated != edCheckDigit { + return fieldError("RDFIIdentification", NewErrValidCheckDigit(calculated), ed.CheckDigit) + } + return nil +} + +// fieldInclusion validate mandatory fields are not default values. If fields are +// invalid the ACH transfer will be returned. +func (ed *ADVEntryDetail) fieldInclusion() error { + if ed.TransactionCode == 0 { + return fieldError("TransactionCode", ErrConstructor, strconv.Itoa(ed.TransactionCode)) + } + if ed.RDFIIdentification == "" { + return fieldError("RDFIIdentification", ErrConstructor, ed.RDFIIdentificationField()) + } + if ed.DFIAccountNumber == "" { + return fieldError("DFIAccountNumber", ErrConstructor, ed.DFIAccountNumber) + } + if ed.AdviceRoutingNumber == "" { + return fieldError("AdviceRoutingNumber", ErrConstructor, ed.AdviceRoutingNumber) + } + if ed.IndividualName == "" { + return fieldError("IndividualName", ErrFieldRequired, ed.IndividualName) + } + if ed.ACHOperatorRoutingNumber == "" { + return fieldError("ACHOperatorRoutingNumber", ErrConstructor, ed.ACHOperatorRoutingNumber) + } + if ed.JulianDay <= 0 { + return fieldError("JulianDay", ErrConstructor, strconv.Itoa(ed.JulianDay)) + } + + if ed.SequenceNumber == 0 { + return fieldError("SequenceNumber", ErrConstructor, strconv.Itoa(ed.SequenceNumber)) + } + return nil +} + +// SetRDFI takes the 9 digit RDFI account number and separates it for RDFIIdentification and CheckDigit +func (ed *ADVEntryDetail) SetRDFI(rdfi string) *ADVEntryDetail { + s := ed.stringField(rdfi, 9) + ed.RDFIIdentification = ed.parseStringField(s[:8]) + ed.CheckDigit = ed.parseStringField(s[8:9]) + return ed +} + +// RDFIIdentificationField get the rdfiIdentification with zero padding +func (ed *ADVEntryDetail) RDFIIdentificationField() string { + return ed.stringField(ed.RDFIIdentification, 8) +} + +// DFIAccountNumberField gets the DFIAccountNumber with space padding +func (ed *ADVEntryDetail) DFIAccountNumberField() string { + return ed.alphaField(ed.DFIAccountNumber, 15) +} + +// AmountField returns a zero padded string of amount +func (ed *ADVEntryDetail) AmountField() string { + return ed.numericField(ed.Amount, 12) +} + +// AdviceRoutingNumberField gets the AdviceRoutingNumber with zero padding +func (ed *ADVEntryDetail) AdviceRoutingNumberField() string { + return ed.stringField(ed.AdviceRoutingNumber, 9) +} + +// FileIdentificationField returns a space padded string of FileIdentification +func (ed *ADVEntryDetail) FileIdentificationField() string { + return ed.alphaField(ed.FileIdentification, 5) +} + +// ACHOperatorDataField returns a space padded string of ACHOperatorData +func (ed *ADVEntryDetail) ACHOperatorDataField() string { + return ed.alphaField(ed.ACHOperatorData, 1) +} + +// IndividualNameField returns a space padded string of IndividualName +func (ed *ADVEntryDetail) IndividualNameField() string { + return ed.alphaField(ed.IndividualName, 22) +} + +// DiscretionaryDataField returns a space padded string of DiscretionaryData +func (ed *ADVEntryDetail) DiscretionaryDataField() string { + return ed.alphaField(ed.DiscretionaryData, 2) +} + +// ACHOperatorRoutingNumberField returns a space padded string of ACHOperatorRoutingNumber +func (ed *ADVEntryDetail) ACHOperatorRoutingNumberField() string { + return ed.alphaField(ed.ACHOperatorRoutingNumber, 8) +} + +// JulianDateDayField returns a zero padded string of JulianDay +func (ed *ADVEntryDetail) JulianDateDayField() string { + return ed.numericField(ed.JulianDay, 3) +} + +// SequenceNumberField returns a zero padded string of SequenceNumber +func (ed *ADVEntryDetail) SequenceNumberField() string { + return ed.numericField(ed.SequenceNumber, 4) +} diff --git a/advEntryDetail_test.go b/advEntryDetail_test.go new file mode 100644 index 000000000..99c267886 --- /dev/null +++ b/advEntryDetail_test.go @@ -0,0 +1,347 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/moov-io/base" +) + +// mockADVEntryDetail creates a ADV entry detail +func mockADVEntryDetail() *ADVEntryDetail { + entry := NewADVEntryDetail() + entry.TransactionCode = CreditForDebitsOriginated + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 50000 + entry.AdviceRoutingNumber = "121042882" + entry.FileIdentification = "11131" + entry.ACHOperatorData = "" + entry.IndividualName = "Name" + entry.DiscretionaryData = "" + entry.AddendaRecordIndicator = 0 + entry.ACHOperatorRoutingNumber = "01100001" + entry.JulianDay = 50 + entry.SequenceNumber = 1 + return entry +} + +// testMockADVEntryDetail validates an ADV entry detail record +func testMockADVEntryDetail(t testing.TB) { + entry := mockADVEntryDetail() + if err := entry.Validate(); err != nil { + t.Error("mockADVEntryDetail does not validate and will break other tests") + } + if entry.TransactionCode != CreditForDebitsOriginated { + t.Error("TransactionCode dependent default value has changed") + } + if entry.RDFIIdentification != "23138010" { + t.Error("RDFIIdentification dependent default value has changed") + } + if entry.AdviceRoutingNumber != "121042882" { + t.Error("AdviceRoutingNumber dependent default value has changed") + } + if entry.DFIAccountNumber != "744-5678-99" { + t.Error("DFIAccountNumber dependent default value has changed") + } + if entry.FileIdentification != "11131" { + t.Error("FileIdentification dependent default value has changed") + } + if entry.Amount != 50000 { + t.Error("Amount dependent default value has changed") + } + if entry.IndividualName != "Name" { + t.Error("IndividualName dependent default value has changed") + } + if entry.ACHOperatorRoutingNumber != "01100001" { + t.Error("ACHOperatorRoutingNumber dependent default value has changed") + } + if entry.DiscretionaryData != "" { + t.Error("DiscretionaryData dependent default value has changed") + } + if entry.JulianDay != 50 { + t.Error("JulianDay dependent default value has changed") + } + if entry.SequenceNumber != 1 { + t.Error("SequenceNumber dependent default value has changed") + } + +} + +// TestMockADVEntryDetail tests validating an entry detail record +func TestMockADVEntryDetail(t *testing.T) { + testMockADVEntryDetail(t) +} + +// BenchmarkMockEntryDetail benchmarks validating an entry detail record +func BenchmarkMockADVEntryDetail(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testMockADVEntryDetail(b) + } +} + +// testADVEDString validates that a known parsed ADV entry +// detail can be returned to a string of the same value +func testADVEDString(t testing.TB) { + var line = "681231380104744-5678-99 00000005000012104288211131 Name 0011000010500001" + r := NewReader(strings.NewReader(line)) + r.line = line + bh := BatchHeader{BatchNumber: 1, + StandardEntryClassCode: ADV, + ServiceClassCode: AutomatedAccountingAdvices, + CompanyIdentification: "origid", + ODFIIdentification: "121042882"} + r.addCurrentBatch(NewBatchADV(&bh)) + + r.currentBatch.AddADVEntry(mockADVEntryDetail()) + if err := r.parseEntryDetail(); err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.currentBatch.GetADVEntries()[0] + + if record.String() != line { + t.Errorf("Strings do not match") + } +} + +// TestADVEDString tests validating that a known parsed entry +// detail can be returned to a string of the same value +func TestADVEDString(t *testing.T) { + testADVEDString(t) +} + +// BenchmarkADVEDString benchmarks validating that a known parsed entry +// detail can be returned to a string of the same value +func BenchmarkADVEDString(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testADVEDString(b) + } +} + +// TestValidateADVEDTransactionCode validates error if transaction code is not valid +func TestValidateADVEDTransactionCode(t *testing.T) { + ed := mockADVEntryDetail() + ed.TransactionCode = 63 + err := ed.Validate() + if !base.Match(err, ErrTransactionCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVEDDFIAccountNumberAlphaNumeric validates DFI account number is alpha numeric +func TestADVEDDFIAccountNumberAlphaNumeric(t *testing.T) { + ed := mockADVEntryDetail() + ed.DFIAccountNumber = "®" + err := ed.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVEDAdviceRoutingNumberAlphaNumeric validates Advice Routing Number is alpha numeric +func TestADVEDAdviceRoutingNumberAlphaNumeric(t *testing.T) { + ed := mockADVEntryDetail() + ed.AdviceRoutingNumber = "®" + err := ed.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVEDIndividualNameAlphaNumeric validates IndividualName is alpha numeric +func TestADVEDIndividualNameAlphaNumeric(t *testing.T) { + ed := mockADVEntryDetail() + ed.IndividualName = "®" + err := ed.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVEDDiscretionaryDataAlphaNumeric validates DiscretionaryData is alpha numeric +func TestADVEDDiscretionaryDataAlphaNumeric(t *testing.T) { + ed := mockADVEntryDetail() + ed.DiscretionaryData = "®" + err := ed.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVEDACHOperatorRoutingNumberAlphaNumeric validates ACHOperatorRoutingNumber is alpha numeric +func TestADVEDACHOperatorRoutingNumberAlphaNumeric(t *testing.T) { + ed := mockADVEntryDetail() + ed.ACHOperatorRoutingNumber = "®" + err := ed.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVEDisCheckDigit validates check digit +func TestADVEDisCheckDigit(t *testing.T) { + ed := mockADVEntryDetail() + ed.CheckDigit = "1" + err := ed.Validate() + if !base.Match(err, NewErrValidCheckDigit(7)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVEDFieldInclusionTransactionCode validates TransactionCode field inclusion +func TestADVEDFieldInclusionTransactionCode(t *testing.T) { + entry := mockADVEntryDetail() + entry.TransactionCode = 0 + err := entry.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVEDFieldInclusionRDFIIdentification validates RDFIIdentification field inclusion +func TestADVEDFieldInclusionRDFIIdentification(t *testing.T) { + entry := mockADVEntryDetail() + entry.RDFIIdentification = "" + err := entry.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVEDFieldInclusionDFIAccountNumber validates DFIAccountNumber field inclusion +func TestADVEDFieldInclusionDFIAccountNumber(t *testing.T) { + entry := mockADVEntryDetail() + entry.DFIAccountNumber = "" + err := entry.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVEDFieldInclusionAdviceRoutingNumber validates AdviceRoutingNumber field inclusion +func TestADVEDFieldInclusionAdviceRoutingNumber(t *testing.T) { + entry := mockADVEntryDetail() + entry.AdviceRoutingNumber = "" + err := entry.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVEDFieldInclusionIndividualName validates IndividualName field inclusion +func TestADVEDFieldInclusionIndividualName(t *testing.T) { + entry := mockADVEntryDetail() + entry.IndividualName = "" + err := entry.Validate() + if !base.Match(err, ErrFieldRequired) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVEDFieldInclusionACHOperatorRoutingNumber validates ACHOperatorRoutingNumber field inclusion +func TestADVEDFieldInclusionACHOperatorRoutingNumber(t *testing.T) { + entry := mockADVEntryDetail() + entry.ACHOperatorRoutingNumber = "" + err := entry.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVEDFieldInclusionJulianDateDay validates JulianDay field inclusion +func TestADVEDFieldInclusionJulianDateDay(t *testing.T) { + entry := mockADVEntryDetail() + entry.JulianDay = 0 + err := entry.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVEDFieldInclusionSequenceNumber validates SequenceNumber field inclusion +func TestADVEDFieldInclusionSequenceNumber(t *testing.T) { + entry := mockADVEntryDetail() + entry.SequenceNumber = 0 + err := entry.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVEDBadTransactionCode validates TransactionCode field inclusion +func TestBadTransactionCode(t *testing.T) { + entry := mockADVEntryDetail() + entry.TransactionCode = CheckingDebit + err := entry.Validate() + // TODO: are we expecting there to be no errors here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestInvalidADVEDParse returns an error when parsing an ADV Entry Detail +func TestInvalidADVEDParse(t *testing.T) { + var line = "681231380104744-5678-99 000000050000121042882FILE1 Name" + r := NewReader(strings.NewReader(line)) + r.line = line + bh := BatchHeader{BatchNumber: 1, + StandardEntryClassCode: ADV, + ServiceClassCode: AutomatedAccountingAdvices, + CompanyIdentification: "origid", + ODFIIdentification: "121042882"} + r.addCurrentBatch(NewBatchADV(&bh)) + + err := r.parseEntryDetail() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +func TestADVUnmarshal(t *testing.T) { + raw := []byte(`{ + "id": "adv-01", + "transactionCode": 81, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "81967038518 ", + "amount": 100000, + "adviceRoutingNumber": "121042882", + "fileIdentification": "11131", + "achOperatorData": "", + "individualName": "Steven Tander ", + "discretionaryData": " ", + "addendaRecordIndicator": 0, + "achOperatorRoutingNumber": "01100001", + "julianDay": 2, + "sequenceNumber": 1 + }`) + + var ed ADVEntryDetail + if err := json.Unmarshal(raw, &ed); err != nil { + t.Fatal(err) + } + + if ed.JulianDay != 2 { + t.Errorf("ed.JulianDay=%d", ed.JulianDay) + } +} diff --git a/advFileControl.go b/advFileControl.go new file mode 100644 index 000000000..224efe034 --- /dev/null +++ b/advFileControl.go @@ -0,0 +1,155 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "unicode/utf8" +) + +// ADVFileControl record contains entry counts, dollar totals and hash +// totals accumulated from each batchADV control record in the file. +type ADVFileControl struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + + // BatchCount total number of batches (i.e., '5' records) in the file + BatchCount int `json:"batchCount"` + + // BlockCount total number of records in the file (include all headers and trailer) divided + // by 10 (This number must be evenly divisible by 10. If not, additional records consisting of all 9's are added to the file after the initial '9' record to fill out the block 10.) + BlockCount int `json:"blockCount,omitempty"` + + // EntryAddendaCount total detail and addenda records in the file + EntryAddendaCount int `json:"entryAddendaCount"` + + // EntryHash calculated in the same manner as the batch has total but includes total from entire file + EntryHash int `json:"entryHash"` + + // TotalDebitEntryDollarAmountInFile contains accumulated Batch debit totals within the file. + TotalDebitEntryDollarAmountInFile int `json:"totalDebit"` + + // TotalCreditEntryDollarAmountInFile contains accumulated Batch credit totals within the file. + TotalCreditEntryDollarAmountInFile int `json:"totalCredit"` + // validator is composed for data validation + validator + // converters is composed for ACH to golang Converters + converters +} + +// Parse takes the input record string and parses the FileControl values +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm +// successful parsing and data validity. +func (fc *ADVFileControl) Parse(record string) { + if utf8.RuneCountInString(record) < 71 { + return + } + + // 1-1 Always "9" + // 2-7 The total number of Batch Header Record in the file. For example: "000003 + fc.BatchCount = fc.parseNumField(record[1:7]) + // 8-13 e total number of blocks on the file, including the File Header and File Control records. One block is 10 lines, so it's effectively the number of lines in the file divided by 10. + fc.BlockCount = fc.parseNumField(record[7:13]) + // 14-21 Total number of Entry Detail Record in the file + fc.EntryAddendaCount = fc.parseNumField(record[13:21]) + // 22-31 Total of all positions 4-11 on each Entry Detail Record in the file. This is essentially the sum of all the RDFI routing numbers in the file. + // If the sum exceeds 10 digits (because you have lots of Entry Detail Records), lop off the most significant digits of the sum until there are only 10 + fc.EntryHash = fc.parseNumField(record[21:31]) + // 32-51 Number of cents of debit entries within the file + fc.TotalDebitEntryDollarAmountInFile = fc.parseNumField(record[31:51]) + // 52-71 Number of cents of credit entries within the file + fc.TotalCreditEntryDollarAmountInFile = fc.parseNumField(record[51:71]) + // 72-94 Reserved Always blank (just fill with spaces) +} + +// NewADVFileControl returns a new ADVFileControl with default values for none exported fields +func NewADVFileControl() ADVFileControl { + return ADVFileControl{} +} + +// String writes the ADVFileControl struct to a 94 character string. +func (fc *ADVFileControl) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(fileControlPos) + buf.WriteString(fc.BatchCountField()) + buf.WriteString(fc.BlockCountField()) + buf.WriteString(fc.EntryAddendaCountField()) + buf.WriteString(fc.EntryHashField()) + buf.WriteString(fc.TotalDebitEntryDollarAmountInFileField()) + buf.WriteString(fc.TotalCreditEntryDollarAmountInFileField()) + buf.WriteString(" ") + return buf.String() +} + +// Validate performs NACHA format rule checks on the record and returns an error if not Validated +// The first error encountered is returned and stops that parsing. +func (fc *ADVFileControl) Validate() error { + if err := fc.fieldInclusion(); err != nil { + return err + } + return nil +} + +// fieldInclusion validate mandatory fields are not default values. If fields are +// invalid the ACH transfer will be returned. +func (fc *ADVFileControl) fieldInclusion() error { + if fc.BatchCount == 0 { + return fieldError("BatchCount", ErrConstructor, fc.BatchCountField()) + } + if fc.BlockCount == 0 { + return fieldError("BlockCount", ErrConstructor, fc.BlockCountField()) + } + if fc.EntryAddendaCount == 0 { + return fieldError("EntryAddendaCount", ErrConstructor, fc.EntryAddendaCountField()) + } + if fc.EntryHash == 0 { + return fieldError("EntryHash", ErrConstructor, fc.EntryHashField()) + } + return nil +} + +// BatchCountField gets a string of the batch count zero padded +func (fc *ADVFileControl) BatchCountField() string { + return fc.numericField(fc.BatchCount, 6) +} + +// BlockCountField gets a string of the block count zero padded +func (fc *ADVFileControl) BlockCountField() string { + return fc.numericField(fc.BlockCount, 6) +} + +// EntryAddendaCountField gets a string of entry addenda batch count zero padded +func (fc *ADVFileControl) EntryAddendaCountField() string { + return fc.numericField(fc.EntryAddendaCount, 8) +} + +// EntryHashField gets a string of entry hash zero padded +func (fc *ADVFileControl) EntryHashField() string { + return fc.numericField(fc.EntryHash, 10) +} + +// TotalDebitEntryDollarAmountInFileField get a zero padded Total debit Entry Amount +func (fc *ADVFileControl) TotalDebitEntryDollarAmountInFileField() string { + return fc.numericField(fc.TotalDebitEntryDollarAmountInFile, 20) +} + +// TotalCreditEntryDollarAmountInFileField get a zero padded Total credit Entry Amount +func (fc *ADVFileControl) TotalCreditEntryDollarAmountInFileField() string { + return fc.numericField(fc.TotalCreditEntryDollarAmountInFile, 20) +} diff --git a/advFileControl_test.go b/advFileControl_test.go new file mode 100644 index 000000000..1088b7960 --- /dev/null +++ b/advFileControl_test.go @@ -0,0 +1,254 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "testing" + + "github.com/moov-io/base" +) + +// mockADVFileControl create a file control +func mockADVFileControl() ADVFileControl { + fc := NewADVFileControl() + fc.BatchCount = 1 + fc.BlockCount = 1 + fc.EntryAddendaCount = 1 + fc.EntryHash = 5320001 + return fc +} + +// testMockADVFileControl validates a file control record +func testMockADVFileControl(t testing.TB) { + fc := mockADVFileControl() + if err := fc.Validate(); err != nil { + t.Error("mockADVFileControl does not validate and will break other tests") + } + if fc.BatchCount != 1 { + t.Error("BatchCount dependent default value has changed") + } + if fc.BlockCount != 1 { + t.Error("BlockCount dependent default value has changed") + } + if fc.EntryAddendaCount != 1 { + t.Error("EntryAddendaCount dependent default value has changed") + } + if fc.EntryHash != 5320001 { + t.Error("EntryHash dependent default value has changed") + } +} + +// TestMockADVFileControl tests validating a file control record +func TestMockADVFileControl(t *testing.T) { + testMockADVFileControl(t) +} + +// BenchmarkMockADVFileControl benchmarks validating a file control record +func BenchmarkMockADVFileControl(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testMockADVFileControl(b) + } +} + +// testParseADVFileControl parses a known file control record string +func testParseADVFileControl(t testing.TB) { + var line = "90000010000010000000100053200010000000000000001050000000000000000000000 " + r := NewReader(strings.NewReader(line)) + r.line = line + batchADV := mockBatchADV() + r.File.AddBatch(batchADV) + + err := r.parseFileControl() + if err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.File.ADVControl + + if record.BatchCountField() != "000001" { + t.Errorf("BatchCount Expected '000001' got: %v", record.BatchCountField()) + } + if record.BlockCountField() != "000001" { + t.Errorf("BlockCount Expected '000001' got: %v", record.BlockCountField()) + } + if record.EntryAddendaCountField() != "00000001" { + t.Errorf("EntryAddendaCount Expected '00000001' got: %v", record.EntryAddendaCountField()) + } + if record.EntryHashField() != "0005320001" { + t.Errorf("EntryHash Expected '0005320001' got: %v", record.EntryHashField()) + } + if record.TotalDebitEntryDollarAmountInFileField() != "00000000000000010500" { + t.Errorf("TotalDebitEntryDollarAmountInFile Expected '0000000000000010500' got: %v", record.TotalDebitEntryDollarAmountInFileField()) + } + if record.TotalCreditEntryDollarAmountInFileField() != "00000000000000000000" { + t.Errorf("TotalCreditEntryDollarAmountInFile Expected '00000000000000000000' got: %v", record.TotalCreditEntryDollarAmountInFileField()) + } +} + +// TestParseADVFileControl tests parsing a known file control record string +func TestParseADVFileControl(t *testing.T) { + testParseADVFileControl(t) +} + +// BenchmarkParseADVFileControl benchmarks parsing a known file control record string +func BenchmarkParseADVFileControl(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testParseADVFileControl(b) + } +} + +// testADVFCString validates that a known parsed file can be return to a string of the same value +func testADVFCString(t testing.TB) { + var line = "90000010000010000000100053200010000000000000001050000000000000000000000 " + r := NewReader(strings.NewReader(line)) + r.line = line + batchADV := mockBatchADV() + r.File.AddBatch(batchADV) + + err := r.parseFileControl() + if err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.File.ADVControl + if record.String() != line { + t.Errorf("\nStrings do not match %s\n %s", line, record.String()) + } +} + +// TestADVFCString tests validating that a known parsed file can be return to a string of the same value +func TestADVFCString(t *testing.T) { + testADVFCString(t) +} + +// BenchmarkADVFCString benchmarks validating that a known parsed file can be return to a string of the same value +func BenchmarkADVFCString(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testADVFCString(b) + } +} + +// testADVFCFieldInclusion validates file control field inclusion +func testADVFCFieldInclusion(t testing.TB) { + fc := mockADVFileControl() + fc.BatchCount = 0 + err := fc.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVFCFieldInclusion tests validating file control field inclusion +func TestADVFCFieldInclusion(t *testing.T) { + testADVFCFieldInclusion(t) +} + +// BenchmarkADVFCFieldInclusion benchmarks validating file control field inclusion +func BenchmarkADVFCFieldInclusion(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testADVFCFieldInclusion(b) + } +} + +// testADVFCFieldInclusionBlockCount validates file control block count field inclusion +func testADVFCFieldInclusionBlockCount(t testing.TB) { + fc := mockADVFileControl() + fc.BlockCount = 0 + err := fc.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVFCFieldInclusionBlockCount tests validating file control block count field inclusion +func TestADVFCFieldInclusionBlockCount(t *testing.T) { + testADVFCFieldInclusionBlockCount(t) +} + +// BenchmarkADVFCFieldInclusionBlockCount benchmarks validating file control block count field inclusion +func BenchmarkADVFCFieldInclusionBlockCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testADVFCFieldInclusionBlockCount(b) + } +} + +// testADVFCFieldInclusionEntryAddendaCount validates file control addenda count field inclusion +func testADVFCFieldInclusionEntryAddendaCount(t testing.TB) { + fc := mockADVFileControl() + fc.EntryAddendaCount = 0 + err := fc.Validate() + if !base.Match(err, ErrConstructor) { + + t.Errorf("%T: %s", err, err) + + } +} + +// TestADVFCFieldInclusionEntryAddendaCount tests validating file control addenda count field inclusion +func TestADVFCFieldInclusionEntryAddendaCount(t *testing.T) { + testADVFCFieldInclusionEntryAddendaCount(t) +} + +// BenchmarkADVFCFieldInclusionEntryAddendaCount benchmarks validating file control addenda count field inclusion +func BenchmarkADVFCFieldInclusionEntryAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testADVFCFieldInclusionEntryAddendaCount(b) + } +} + +// testADVFCFieldInclusionEntryHash validates file control entry hash field inclusion +func testADVFCFieldInclusionEntryHash(t testing.TB) { + fc := mockADVFileControl() + fc.EntryHash = 0 + err := fc.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVFCFieldInclusionEntryHash tests validating file control entry hash field inclusion +func TestADVFCFieldInclusionEntryHash(t *testing.T) { + testADVFCFieldInclusionEntryHash(t) +} + +// BenchmarkADVFCFieldInclusionEntryHash benchmarks validating file control entry hash field inclusion +func BenchmarkADVFCFieldInclusionEntryHash(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testADVFCFieldInclusionEntryHash(b) + } +} + +// TestInvalidADVFCParse returns an error when parsing an ADV File Control +func TestInvalidADVFCParse(t *testing.T) { + var line = "9000001000001000000010005320001000000000000000105" + r := NewReader(strings.NewReader(line)) + r.line = line + batchADV := mockBatchADV() + r.File.AddBatch(batchADV) + + err := r.parseFileControl() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} diff --git a/batch.go b/batch.go index 97c4de0b0..f33b989f2 100644 --- a/batch.go +++ b/batch.go @@ -1,94 +1,387 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + package ach import ( + "encoding/json" + "errors" "fmt" "strconv" + "strings" + "unicode/utf8" ) -// Batch holds the Batch Header and Batch Control and all Entry Records for PPD Entries -type batch struct { - header *BatchHeader - entries []*EntryDetail - control *BatchControl +// Batch holds the Batch Header and Batch Control and all Entry Records +type Batch struct { + // id is a client defined string used as a reference to this record. accessed via ID/SetID + id string + Header *BatchHeader `json:"batchHeader"` + Entries []*EntryDetail `json:"entryDetails"` + Control *BatchControl `json:"batchControl"` + ADVEntries []*ADVEntryDetail `json:"advEntryDetails,omitempty"` + ADVControl *ADVBatchControl `json:"advBatchControl,omitempty"` + + // offset holds the information to build an EntryDetail record which + // balances the batch by debiting or crediting the sum of amounts in the batch. + offset *Offset + + // category defines if the entry is a Forward, Return, or NOC + category string // Converters is composed for ACH to GoLang Converters converters + + validateOpts *ValidateOpts } -// NewBatch takes a BatchParm and returns a matching SEC code batch type that is a batcher. Returns and error if the SEC code is not supported. -func NewBatch(bp BatchParam) (Batcher, error) { - switch sec := bp.StandardEntryClass; sec { - case "PPD": - return NewBatchPPD(bp), nil - case "WEB": - return NewBatchWEB(bp), nil - case "CCD": - return NewBatchCCD(bp), nil - case "COR": - return NewBatchCOR(bp), nil - default: - msg := fmt.Sprintf(msgFileNoneSEC, sec) - return nil, &FileError{FieldName: "StandardEntryClassCode", Msg: msg} +const ( + // ACK ACH Payment Acknowledgment - A code that indicates acknowledgment of receipt of a corporate credit payment + // (CCD). + ACK = "ACK" + // ADV Automated Accounting Advice – A code that provide accounting information regarding an Entry. It is an + // optional service. + ADV = "ADV" + // ARC Accounts Receivable Entry – A code that indicates a consumer check converted to a one-time ACH debit. + // The Accounts Receivable (ARC) Entry provides initiates a single-entry ACH debit to customer accounts by + // converting checks at the point of receipt through the U.S. mail, at a drop box location or in-person for + // payment of a bill at a manned location. + ARC = "ARC" + // ATX Financial EDI Acknowledgment - A code that indicates acknowledgement by the Receiving Depository Financial + // Institution (RDFI) that a Corporate Credit Exchange (CTX) has been received. + ATX = "ATX" + // BOC Back Office Conversion Entry - A code that indicates single entry debit initiated at the point of purchase + // or at a manned bill payment location to transfer funds through conversion to an ACH debit entry during back + // office processing. + BOC = "BOC" + // CCD Corporate Credit or Debit Entry - A code that indicates an entry initiated by an Organization to transfer + // funds to or from an account of that Organization or another Organization. For commercial accounts only. + CCD = "CCD" + // CIE Customer Initiated Entry - A code that indicates a credit entry initiated on behalf of, and upon the + // instruction of, a consumer to transfer funds to a non-consumer Receiver. + CIE = "CIE" + // COR Notification of Change or Refused Notification of Change - A code used by an RDFI or ODFI when originating a + // Notification of Change or Refused Notification of Change in automated format. + COR = "COR" + // CTX Corporate Trade Exchange - A code that indicates the ability to collect and disburse funds and information + // between companies. Generally it is used by businesses paying one another for goods or services. + CTX = "CTX" + // DNE Death Notification Entry - A code that United States Federal agencies (e.g. Social Security) use to notify + // depository financial institutions that the recipient of government benefit payments has died. + DNE = "DNE" + // ENR Automated Enrollment Entry - A code indicating enrollment of a person with an agency of the US government + // for a depository financial institution. + ENR = "ENR" + // IAT International ACH Transaction - A code IAT indicating a credit or debit ACH entry that is part of a payment + // transaction involving a financial agency's office (i.e., depository financial institution or business issuing + // money orders) that is not located in the territorial jurisdiction of the United States. IAT entries can be made + // to or from a corporate or consumer account and must be accompanied by seven (7) mandatory addenda records + // identifying the name and physical address of the Originator, name and physical address of the Receiver, + // Receiver's account number, Receiver's bank identity and reason for the payment. + IAT = "IAT" + // MTE Machine Transfer Entry - A code that indicates when a consumer uses their debit card at an Automated Teller + // Machine (ATM) to withdraw cash. MTE transactions cannot be aggregated together under a single Entry. + MTE = "MTE" + // POP Point of Purchase Entry - A code that indicates a check presented in-person to a merchant for purchase + // is presented as an ACH entry instead of a physical check. + POP = "POP" + // POS Point of Sale Entry - A code that indicates a debit entry initiated at an “electronic terminal” to a + // consumer account of the receiver to pay an obligation incurred in a point-of-sale transaction, or to effect a + // point-of-sale terminal cash withdrawal. + POS = "POS" + // PPD Prearranged Payment and Deposit Entry - A code tha indicates a an entry initiated by an organization based + // on a standing or a single entry authorization to transfer funds. + PPD = "PPD" + // RCK Re-presented Check Entry - A code that indicates a physical check that was presented but returned because of + //// insufficient funds may be represented as an ACH entry. + RCK = "RCK" + // SHR Shared Network Transaction - A code that indicates a debit Entry initiated at an “electronic terminal,” as + // that term is defined in Regulation E, to a Consumer Account of the Receiver to pay an obligation incurred in a + // point-of-sale transaction, or to effect a point-of-sale terminal cash withdrawal. Also an adjusting or other + // credit Entry related to such debit Entry, transfer of funds, or obligation. SHR Entries are initiated in a + // shared network where the ODFI and RDFI have an agreement in addition to these Rules to process such Entries. + SHR = "SHR" + // TEL Telephone Initiated Entry - A code indicating a Telephone-Initiated consumer debit transactions. The NACHA + // Operating Rules permit TEL entries when the originator obtains the Receiver's authorization for the debit entry + // orally via the telephone. An entry based upon a Receiver's oral authorization must utilize the TEL + // Standard Entry Class (SEC) Code. + TEL = "TEL" + // TRC Check Truncation Entry - is a code used to identify a debit entry of a truncated check. + TRC = "TRC" + // TRX Check Truncation Entries Exchange - used to identify a debit entry exchange of a truncated checks (multiple). + TRX = "TRX" + // WEB Internet-Initiated/Mobile Entry - A code indicating an entry submitted pursuant to an authorization obtained + // solely via the Internet or a mobile network. For consumer accounts only. + WEB = "WEB" + // XCK Destroyed Check Entry - A code indicating a debit entry initiated for a a destroyed check eligible items + XCK = "XCK" +) + +func (batch *Batch) MarshalJSON() ([]byte, error) { + type Alias Batch + aux := struct { + *Alias + Offset *Offset `json:"offset"` + }{ + (*Alias)(batch), + batch.offset, } + return json.Marshal(aux) } -// verify checks basic valid NACHA batch rules. Assumes properly parsed records. This does not mean it is a valid batch as validity is tied to each batch type -func (batch *batch) verify() error { - batchNumber := batch.header.BatchNumber +func (batch *Batch) UnmarshalJSON(p []byte) error { + if batch == nil { + batch = &Batch{} + } + // blank out the fields of our Batch before reading + batch.Header = NewBatchHeader() + batch.Control = NewBatchControl() + batch.ADVControl = NewADVBatchControl() - // verify field inclusion in all the records of the batch. - if err := batch.isFieldInclusion(); err != nil { - // convert the field error in to a batch error for a consistent api - if e, ok := err.(*FieldError); ok { - return &BatchError{BatchNumber: batchNumber, FieldName: e.FieldName, Msg: e.Msg} + type Alias Batch + aux := struct { + *Alias + }{ + (*Alias)(batch), + } + if err := json.Unmarshal(p, &aux); err != nil { + if e, ok := err.(*json.UnmarshalTypeError); ok { + return fmt.Errorf("%s: %v", e.Field, err) } - return &BatchError{BatchNumber: batchNumber, FieldName: "FieldError", Msg: err.Error()} + return err } - // validate batch header and control codes are the same - if batch.header.ServiceClassCode != batch.control.ServiceClassCode { - msg := fmt.Sprintf(msgBatchHeaderControlEquality, batch.header.ServiceClassCode, batch.control.ServiceClassCode) - return &BatchError{BatchNumber: batchNumber, FieldName: "ServiceClassCode", Msg: msg} + return nil +} + +// NewBatch takes a BatchHeader and returns a matching SEC code batch type that is a batcher. Returns an error if the SEC code is not supported. +func NewBatch(bh *BatchHeader) (Batcher, error) { + if bh == nil { + return nil, errors.New("nil BatchHeader provided") } - // Company Identification must match the Company ID from the batch header record - if batch.header.CompanyIdentification != batch.control.CompanyIdentification { - msg := fmt.Sprintf(msgBatchHeaderControlEquality, batch.header.CompanyIdentification, batch.control.CompanyIdentification) - return &BatchError{BatchNumber: batchNumber, FieldName: "CompanyIdentification", Msg: msg} + + switch bh.StandardEntryClassCode { + case ACK: + return NewBatchACK(bh), nil + case ADV: + return NewBatchADV(bh), nil + case ARC: + return NewBatchARC(bh), nil + case ATX: + return NewBatchATX(bh), nil + case BOC: + return NewBatchBOC(bh), nil + case CCD: + return NewBatchCCD(bh), nil + case CIE: + return NewBatchCIE(bh), nil + case COR: + return NewBatchCOR(bh), nil + case CTX: + return NewBatchCTX(bh), nil + case DNE: + return NewBatchDNE(bh), nil + case ENR: + return NewBatchENR(bh), nil + case IAT: + return nil, ErrFileIATSEC + case MTE: + return NewBatchMTE(bh), nil + case POP: + return NewBatchPOP(bh), nil + case POS: + return NewBatchPOS(bh), nil + case PPD: + return NewBatchPPD(bh), nil + case RCK: + return NewBatchRCK(bh), nil + case SHR: + return NewBatchSHR(bh), nil + case TEL: + return NewBatchTEL(bh), nil + case TRC: + return NewBatchTRC(bh), nil + case TRX: + return NewBatchTRX(bh), nil + case WEB: + return NewBatchWEB(bh), nil + case XCK: + return NewBatchXCK(bh), nil + default: } - // Control ODFI Identification must be the same as batch header - if batch.header.ODFIIdentification != batch.control.ODFIIdentification { - msg := fmt.Sprintf(msgBatchHeaderControlEquality, batch.header.ODFIIdentification, batch.control.ODFIIdentification) - return &BatchError{BatchNumber: batchNumber, FieldName: "ODFIIdentification", Msg: msg} + return nil, NewErrFileUnknownSEC(bh.StandardEntryClassCode) +} + +// ConvertBatchType will take a batch object and convert it into one of the correct batch type +func ConvertBatchType(b Batch) Batcher { + switch b.Header.StandardEntryClassCode { + case ACK: + return &BatchACK{b} + case ADV: + return &BatchADV{b} + case ARC: + return &BatchARC{b} + case ATX: + return &BatchATX{b} + case BOC: + return &BatchBOC{b} + case CCD: + return &BatchCCD{b} + case CIE: + return &BatchCIE{b} + case COR: + return &BatchCOR{b} + case CTX: + return &BatchCTX{b} + case DNE: + return &BatchDNE{b} + case ENR: + return &BatchENR{b} + case MTE: + return &BatchMTE{b} + case POP: + return &BatchPOP{b} + case POS: + return &BatchPOS{b} + case PPD: + return &BatchPPD{b} + case RCK: + return &BatchRCK{b} + case SHR: + return &BatchSHR{b} + case TEL: + return &BatchTEL{b} + case TRC: + return &BatchTRC{b} + case TRX: + return &BatchTRX{b} + case WEB: + return &BatchWEB{b} + case XCK: + return &BatchXCK{b} + default: + return &b } - // batch number header and control must match - if batch.header.BatchNumber != batch.control.BatchNumber { - msg := fmt.Sprintf(msgBatchHeaderControlEquality, batch.header.ODFIIdentification, batch.control.ODFIIdentification) - return &BatchError{BatchNumber: batchNumber, FieldName: "BatchNumber", Msg: msg} +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *Batch) Create() error { + return errors.New("use an implementation of batch or NewBatch") +} + +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the batch. +func (batch *Batch) Validate() error { + return errors.New("use an implementation of batch or NewBatch") +} + +// SetValidation stores ValidateOpts on the Batch which are to be used to override +// the default NACHA validation rules. +func (batch *Batch) SetValidation(opts *ValidateOpts) { + if batch == nil { + return } + batch.validateOpts = opts +} - if err := batch.isBatchEntryCount(); err != nil { - return err +// verify checks basic valid NACHA batch rules. Assumes properly parsed records. This does not mean it is a valid batch as validity is tied to each batch type +func (batch *Batch) verify() error { + // No entries in batch + if len(batch.Entries) <= 0 && len(batch.ADVEntries) <= 0 { + return batch.Error("entries", ErrBatchNoEntries) + } + // verify field inclusion in all the records of the batch. + if err := batch.isFieldInclusion(); err != nil { + // convert the field error in to a batch error for a consistent api + return batch.Error("FieldError", err) } - if err := batch.isSequenceAscending(); err != nil { - return err + if !batch.IsADV() { + // validate batch header and control codes are the same + if (batch.validateOpts == nil || !batch.validateOpts.UnequalServiceClassCode) && + batch.Header.ServiceClassCode != batch.Control.ServiceClassCode { + return batch.Error("ServiceClassCode", + NewErrBatchHeaderControlEquality(batch.Header.ServiceClassCode, batch.Control.ServiceClassCode)) + } + // Company Identification in the batch header and control must match if bypassCompanyIdentificationMatch is not enabled. + if batch.Header.CompanyIdentification != batch.Control.CompanyIdentification && + !(batch.validateOpts != nil && batch.validateOpts.BypassCompanyIdentificationMatch) { + return batch.Error("CompanyIdentification", + NewErrBatchHeaderControlEquality(batch.Header.CompanyIdentification, batch.Control.CompanyIdentification)) + } + + // Control ODFIIdentification must be the same as batch header + if batch.Header.ODFIIdentification != batch.Control.ODFIIdentification { + return batch.Error("ODFIIdentification", + NewErrBatchHeaderControlEquality(batch.Header.ODFIIdentification, batch.Control.ODFIIdentification)) + } + // batch number header and control must match + if batch.Header.BatchNumber != batch.Control.BatchNumber { + return batch.Error("BatchNumber", + NewErrBatchHeaderControlEquality(batch.Header.BatchNumber, batch.Control.BatchNumber)) + } + } else { + if (batch.validateOpts == nil || !batch.validateOpts.UnequalServiceClassCode) && + batch.Header.ServiceClassCode != batch.ADVControl.ServiceClassCode { + return batch.Error("ServiceClassCode", + NewErrBatchHeaderControlEquality(batch.Header.ServiceClassCode, batch.ADVControl.ServiceClassCode)) + } + // Control ODFIIdentification must be the same as batch header + if batch.Header.ODFIIdentification != batch.ADVControl.ODFIIdentification { + return batch.Error("ODFIIdentification", + NewErrBatchHeaderControlEquality(batch.Header.ODFIIdentification, batch.ADVControl.ODFIIdentification)) + } + // batch number header and control must match + if batch.Header.BatchNumber != batch.ADVControl.BatchNumber { + return batch.Error("BatchNumber", + NewErrBatchHeaderControlEquality(batch.Header.BatchNumber, batch.ADVControl.BatchNumber)) + } } + if err := batch.isBatchEntryCount(); err != nil { + return err + } + if batch.validateOpts == nil || !batch.validateOpts.CustomTraceNumbers { + if err := batch.isSequenceAscending(); err != nil { + return err + } + } if err := batch.isBatchAmount(); err != nil { return err } - if err := batch.isEntryHash(); err != nil { return err } - if err := batch.isOriginatorDNE(); err != nil { return err } - - if err := batch.isTraceNumberODFI(); err != nil { - return err + if batch.validateOpts == nil || !batch.validateOpts.CustomTraceNumbers { + if err := batch.isTraceNumberODFI(); err != nil { + return err + } + if err := batch.isAddendaSequence(); err != nil { + return err + } } - - if err := batch.isAddendaSequence(); err != nil { + if err := batch.isCategory(); err != nil { return err } return nil @@ -96,113 +389,265 @@ func (batch *batch) verify() error { // Build creates valid batch by building sequence numbers and batch batch control. An error is returned if // the batch being built has invalid records. -func (batch *batch) build() error { +func (batch *Batch) build() error { // Requires a valid BatchHeader - if err := batch.header.Validate(); err != nil { + if err := batch.Header.Validate(); err != nil { return err } - if len(batch.entries) <= 0 { - return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "entries", Msg: msgBatchEntries} + if len(batch.Entries) <= 0 && len(batch.ADVEntries) <= 0 { + return batch.Error("entries", ErrBatchNoEntries) } // Create record sequence numbers entryCount := 0 seq := 1 - for i, entry := range batch.entries { - entryCount = entryCount + 1 + len(entry.Addendum) - // Allows for manual override of trace numbers if current entry's trace number is already set before - // the batch is built. - currentTraceNumberODFI, err := strconv.Atoi(entry.TraceNumberField()[:8]) - if err != nil { - return err - } - if currentTraceNumberODFI != batch.header.ODFIIdentification { - batch.entries[i].setTraceNumber(batch.header.ODFIIdentification, seq) - } - seq++ - addendaSeq := 1 - for x := range entry.Addendum { - batch.entries[i].Addendum[x].SequenceNumber = addendaSeq - batch.entries[i].Addendum[x].EntryDetailSequenceNumber = batch.parseNumField(batch.entries[i].TraceNumberField()[8:]) - addendaSeq++ + + if !batch.IsADV() { + for i, entry := range batch.Entries { + entryCount += 1 + entry.addendaCount() + + currentTraceNumberODFI, err := strconv.Atoi(entry.TraceNumberField()[:8]) + if err != nil { + return err + } + + batchHeaderODFI, err := strconv.Atoi(batch.Header.ODFIIdentificationField()[:8]) + if err != nil { + return err + } + + // Add a sequenced TraceNumber if one is not already set. Have to keep original trance number Return and NOC entries + if currentTraceNumberODFI != batchHeaderODFI { + if opts := batch.validateOpts; opts == nil { + entry.SetTraceNumber(batch.Header.ODFIIdentification, seq) + } else { + // Automatically set the TraceNumber if we are validating Origin and don't have custom trace numbers + if !opts.BypassOriginValidation && !opts.CustomTraceNumbers { + entry.SetTraceNumber(batch.Header.ODFIIdentification, seq) + } + } + } + seq++ + addendaSeq := 1 + for _, a := range entry.Addenda05 { + // sequences don't exist in NOC or Return addenda + a.SequenceNumber = addendaSeq + a.EntryDetailSequenceNumber = batch.parseNumField(batch.Entries[i].TraceNumberField()[8:]) + addendaSeq++ + } } - } - // build a BatchControl record - bc := NewBatchControl() - bc.ServiceClassCode = batch.header.ServiceClassCode - bc.CompanyIdentification = batch.header.CompanyIdentification - bc.ODFIIdentification = batch.header.ODFIIdentification - bc.BatchNumber = batch.header.BatchNumber - bc.EntryAddendaCount = entryCount - bc.EntryHash = batch.parseNumField(batch.calculateEntryHash()) - bc.TotalCreditEntryDollarAmount, bc.TotalDebitEntryDollarAmount = batch.calculateBatchAmounts() - batch.control = bc + // build a BatchControl record + bc := NewBatchControl() + bc.ServiceClassCode = batch.Header.ServiceClassCode + bc.CompanyIdentification = batch.Header.CompanyIdentification + bc.ODFIIdentification = batch.Header.ODFIIdentification + bc.BatchNumber = batch.Header.BatchNumber + bc.EntryAddendaCount = entryCount + bc.EntryHash = batch.calculateEntryHash() + bc.TotalCreditEntryDollarAmount, bc.TotalDebitEntryDollarAmount = batch.calculateBatchAmounts() + batch.Control = bc + } else { + for i, entry := range batch.ADVEntries { + entryCount++ - return nil + if entry.Addenda99 != nil { + entryCount++ + } + // Set Sequence Number + batch.ADVEntries[i].SequenceNumber = seq + + seq++ + + if seq > 9999 { + return batch.Error("SequenceNumber", ErrBatchADVCount) + } + } + // build a BatchADVControl record + bcADV := NewADVBatchControl() + bcADV.ServiceClassCode = batch.Header.ServiceClassCode + bcADV.ACHOperatorData = batch.Header.CompanyName + bcADV.ODFIIdentification = batch.Header.ODFIIdentification + bcADV.BatchNumber = batch.Header.BatchNumber + bcADV.EntryAddendaCount = entryCount + bcADV.EntryHash = batch.calculateEntryHash() + bcADV.TotalCreditEntryDollarAmount, bcADV.TotalDebitEntryDollarAmount = batch.calculateADVBatchAmounts() + batch.ADVControl = bcADV + } + return batch.upsertOffsets() } // SetHeader appends an BatchHeader to the Batch -func (batch *batch) SetHeader(batchHeader *BatchHeader) { - batch.header = batchHeader +func (batch *Batch) SetHeader(batchHeader *BatchHeader) { + batch.Header = batchHeader } // GetHeader returns the current Batch header -func (batch *batch) GetHeader() *BatchHeader { - return batch.header +func (batch *Batch) GetHeader() *BatchHeader { + return batch.Header } // SetControl appends an BatchControl to the Batch -func (batch *batch) SetControl(batchControl *BatchControl) { - batch.control = batchControl +func (batch *Batch) SetControl(batchControl *BatchControl) { + batch.Control = batchControl } // GetControl returns the current Batch Control -func (batch *batch) GetControl() *BatchControl { - return batch.control +func (batch *Batch) GetControl() *BatchControl { + return batch.Control +} + +// SetADVControl appends an BatchADVControl to the Batch +func (batch *Batch) SetADVControl(batchADVControl *ADVBatchControl) { + batch.ADVControl = batchADVControl +} + +// GetADVControl returns the current Batch ADVControl +func (batch *Batch) GetADVControl() *ADVBatchControl { + return batch.ADVControl } // GetEntries returns a slice of entry details for the batch -func (batch *batch) GetEntries() []*EntryDetail { - return batch.entries +func (batch *Batch) GetEntries() []*EntryDetail { + return batch.Entries } // AddEntry appends an EntryDetail to the Batch -func (batch *batch) AddEntry(entry *EntryDetail) { - batch.entries = append(batch.entries, entry) +func (batch *Batch) AddEntry(entry *EntryDetail) { + if entry == nil { + return + } + + batch.category = entry.Category + batch.Entries = append(batch.Entries, entry) +} + +// AddADVEntry appends an ADV EntryDetail to the Batch +func (batch *Batch) AddADVEntry(entry *ADVEntryDetail) { + batch.category = entry.Category + batch.ADVEntries = append(batch.ADVEntries, entry) +} + +// GetADVEntries returns a slice of entry details for the batch +func (batch *Batch) GetADVEntries() []*ADVEntryDetail { + return batch.ADVEntries +} + +// Category returns batch category +func (batch *Batch) Category() string { + if len(batch.Entries) == 0 && batch.category != "" { + return batch.category + } + // If an Entry has NOC or Return that's the Batch's category + for i := range batch.Entries { + switch batch.Entries[i].Category { + case CategoryReturn, CategoryNOC: + return batch.Entries[i].Category + } + } + for i := range batch.ADVEntries { + switch batch.ADVEntries[i].Category { + case CategoryReturn, CategoryNOC: + return batch.ADVEntries[i].Category + } + } + return CategoryForward +} + +// ID returns the id of the batch +func (batch *Batch) ID() string { + return batch.id +} + +// SetID sets the batch id +func (batch *Batch) SetID(id string) { + batch.id = id } // isFieldInclusion iterates through all the records in the batch and verifies against default fields -func (batch *batch) isFieldInclusion() error { - if err := batch.header.Validate(); err != nil { +func (batch *Batch) isFieldInclusion() error { + if err := batch.Header.Validate(); err != nil { return err } - for _, entry := range batch.entries { + + if !batch.IsADV() { + for _, entry := range batch.Entries { + if err := entry.Validate(); err != nil { + return err + } + + if entry.Addenda02 != nil { + if err := entry.Addenda02.Validate(); err != nil { + return err + } + } + for _, addenda05 := range entry.Addenda05 { + if err := addenda05.Validate(); err != nil { + return err + } + } + if entry.Addenda98 != nil { + if err := entry.Addenda98.Validate(); err != nil { + return err + } + } + if entry.Addenda99 != nil { + if err := entry.Addenda99.Validate(); err != nil { + return err + } + } + if entry.Addenda99Dishonored != nil { + if err := entry.Addenda99Dishonored.Validate(); err != nil { + return err + } + } + if entry.Addenda99Contested != nil { + if err := entry.Addenda99Contested.Validate(); err != nil { + return err + } + } + + } + return batch.Control.Validate() + } + // ADV File/Batch + for _, entry := range batch.ADVEntries { if err := entry.Validate(); err != nil { return err } - for _, addenda := range entry.Addendum { - if err := addenda.Validate(); err != nil { - return nil + if entry.Addenda99 != nil { + if err := entry.Addenda99.Validate(); err != nil { + return err } } } - if err := batch.control.Validate(); err != nil { - return err - } - return nil + return batch.ADVControl.Validate() } // isBatchEntryCount validate Entry count is accurate // The Entry/Addenda Count Field is a tally of each Entry Detail and Addenda // Record processed within the batch -func (batch *batch) isBatchEntryCount() error { +func (batch *Batch) isBatchEntryCount() error { entryCount := 0 - for _, entry := range batch.entries { - entryCount = entryCount + 1 + len(entry.Addendum) + len(entry.ReturnAddendum) - } - if entryCount != batch.control.EntryAddendaCount { - msg := fmt.Sprintf(msgBatchCalculatedControlEquality, entryCount, batch.control.EntryAddendaCount) - return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "EntryAddendaCount", Msg: msg} + + if !batch.IsADV() { + for _, entry := range batch.Entries { + entryCount += 1 + entry.addendaCount() + } + if entryCount != batch.Control.EntryAddendaCount { + return batch.Error("EntryAddendaCount", + NewErrBatchCalculatedControlEquality(entryCount, batch.Control.EntryAddendaCount)) + } + } else { + for _, entry := range batch.ADVEntries { + entryCount++ + if entry.Addenda99 != nil { + entryCount++ + } + } + if entryCount != batch.ADVControl.EntryAddendaCount { + return batch.Error("EntryAddendaCount", + NewErrBatchCalculatedControlEquality(entryCount, batch.ADVControl.EntryAddendaCount)) + } } return nil } @@ -210,26 +655,70 @@ func (batch *batch) isBatchEntryCount() error { // isBatchAmount validate Amount is the same as what is in the Entries // The Total Debit and Credit Entry Dollar Amount fields contain accumulated // Entry Detail debit and credit totals within a given batch -func (batch *batch) isBatchAmount() error { - credit, debit := batch.calculateBatchAmounts() - if debit != batch.control.TotalDebitEntryDollarAmount { - msg := fmt.Sprintf(msgBatchCalculatedControlEquality, debit, batch.control.TotalDebitEntryDollarAmount) - return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "TotalDebitEntryDollarAmount", Msg: msg} - } +func (batch *Batch) isBatchAmount() error { + credit := 0 + debit := 0 + + //ToDo: Consider going back to one function for calculating BatchAmounts, but I'm not sure I want to have + // calculateBatchAmounts with ADV TransactionCodes. In addition the smaller functions help keep the -over for + // gocyclo lower, although since we are currently at 25 (originally it was 18 or 19) it probably won't matter now + // in this case. Based on what I see in other github go code, I'm not sure 25 is a high enough number either. + // Balancing easy to understand functions without having to create functions just for the purpose of meeting the + // -over number convinces me that it should be higher than 25. - if credit != batch.control.TotalCreditEntryDollarAmount { - msg := fmt.Sprintf(msgBatchCalculatedControlEquality, credit, batch.control.TotalCreditEntryDollarAmount) - return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "TotalCreditEntryDollarAmount", Msg: msg} + if !batch.IsADV() { + credit, debit = batch.calculateBatchAmounts() + if debit != batch.Control.TotalDebitEntryDollarAmount { + return batch.Error("TotalDebitEntryDollarAmount", + NewErrBatchCalculatedControlEquality(debit, batch.Control.TotalDebitEntryDollarAmount)) + } + if credit != batch.Control.TotalCreditEntryDollarAmount { + return batch.Error("TotalCreditEntryDollarAmount", + NewErrBatchCalculatedControlEquality(credit, batch.Control.TotalCreditEntryDollarAmount)) + } + } else { + credit, debit = batch.calculateADVBatchAmounts() + if debit != batch.ADVControl.TotalDebitEntryDollarAmount { + return batch.Error("TotalDebitEntryDollarAmount", + NewErrBatchCalculatedControlEquality(debit, batch.ADVControl.TotalDebitEntryDollarAmount)) + } + if credit != batch.ADVControl.TotalCreditEntryDollarAmount { + return batch.Error("TotalCreditEntryDollarAmount", + NewErrBatchCalculatedControlEquality(credit, batch.ADVControl.TotalCreditEntryDollarAmount)) + } } return nil } -func (batch *batch) calculateBatchAmounts() (credit int, debit int) { - for _, entry := range batch.entries { - if entry.TransactionCode == 21 || entry.TransactionCode == 22 || entry.TransactionCode == 23 || entry.TransactionCode == 31 || entry.TransactionCode == 32 || entry.TransactionCode == 33 { +func (batch *Batch) calculateBatchAmounts() (credit int, debit int) { + for _, entry := range batch.Entries { + switch entry.TransactionCode { + case CheckingCredit, CheckingReturnNOCCredit, CheckingPrenoteCredit, CheckingZeroDollarRemittanceCredit, + SavingsCredit, SavingsReturnNOCCredit, SavingsPrenoteCredit, SavingsZeroDollarRemittanceCredit, GLCredit, + GLReturnNOCCredit, GLPrenoteCredit, GLZeroDollarRemittanceCredit, LoanCredit, LoanReturnNOCCredit, + LoanPrenoteCredit, LoanZeroDollarRemittanceCredit: + credit = credit + entry.Amount + case CheckingDebit, CheckingReturnNOCDebit, CheckingPrenoteDebit, CheckingZeroDollarRemittanceDebit, + SavingsDebit, SavingsReturnNOCDebit, SavingsPrenoteDebit, SavingsZeroDollarRemittanceDebit, GLDebit, + GLReturnNOCDebit, GLPrenoteDebit, GLZeroDollarRemittanceDebit, LoanDebit, LoanReturnNOCDebit: + debit = debit + entry.Amount + } + } + return credit, debit +} + +func (batch *Batch) calculateADVBatchAmounts() (credit int, debit int) { + for _, entry := range batch.ADVEntries { + if entry.TransactionCode == CreditForDebitsOriginated || + entry.TransactionCode == CreditForCreditsReceived || + entry.TransactionCode == CreditForCreditsRejected || + entry.TransactionCode == CreditSummary { credit = credit + entry.Amount } - if entry.TransactionCode == 26 || entry.TransactionCode == 27 || entry.TransactionCode == 28 || entry.TransactionCode == 36 || entry.TransactionCode == 37 || entry.TransactionCode == 38 { + if entry.TransactionCode == DebitForCreditsOriginated || + entry.TransactionCode == DebitForDebitsReceived || + entry.TransactionCode == DebitForDebitsRejectedBatches || + entry.TransactionCode == DebitSummary { debit = debit + entry.Amount } } @@ -238,45 +727,71 @@ func (batch *batch) calculateBatchAmounts() (credit int, debit int) { // isSequenceAscending Individual Entry Detail Records within individual batches must // be in ascending Trace Number order (although Trace Numbers need not necessarily be consecutive). -func (batch *batch) isSequenceAscending() error { - lastSeq := -1 - for _, entry := range batch.entries { - if entry.TraceNumber <= lastSeq { - msg := fmt.Sprintf(msgBatchAscending, entry.TraceNumber, lastSeq) - return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "TraceNumber", Msg: msg} +func (batch *Batch) isSequenceAscending() error { + if !batch.IsADV() { + lastSeq := "0" + for _, entry := range batch.Entries { + if batch.validateOpts == nil || !batch.validateOpts.CustomTraceNumbers { + if entry.TraceNumber <= lastSeq { + return batch.Error("TraceNumber", NewErrBatchAscending(lastSeq, entry.TraceNumber)) + } + } + lastSeq = entry.TraceNumber } - lastSeq = entry.TraceNumber } return nil } // isEntryHash validates the hash by recalculating the result -func (batch *batch) isEntryHash() error { +func (batch *Batch) isEntryHash() error { + hashField := batch.calculateEntryHash() - if hashField != batch.control.EntryHashField() { - msg := fmt.Sprintf(msgBatchCalculatedControlEquality, hashField, batch.control.EntryHashField()) - return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "EntryHash", Msg: msg} + if !batch.IsADV() { + if hashField != batch.Control.EntryHash { + return batch.Error("EntryHash", + NewErrBatchCalculatedControlEquality(hashField, batch.Control.EntryHash)) + } + } else { + if hashField != batch.ADVControl.EntryHash { + return batch.Error("EntryHash", + NewErrBatchCalculatedControlEquality(hashField, batch.ADVControl.EntryHash)) + } } return nil } // calculateEntryHash This field is prepared by hashing the 8-digit Routing Number in each entry. // The Entry Hash provides a check against inadvertent alteration of data -func (batch *batch) calculateEntryHash() string { +func (batch *Batch) calculateEntryHash() int { hash := 0 - for _, entry := range batch.entries { - hash = hash + entry.RDFIIdentification + + if !batch.IsADV() { + for _, entry := range batch.Entries { + entryRDFI, _ := strconv.Atoi(aba8(entry.RDFIIdentification)) + hash += entryRDFI + } + } else { + for _, entry := range batch.ADVEntries { + entryRDFI, _ := strconv.Atoi(aba8(entry.RDFIIdentification)) + hash += entryRDFI + } } - return batch.numericField(hash, 10) + + // EntryHash is essentially the sum of all the RDFI routing numbers in the batch. If the sum exceeds 10 digits + // (because you have lots of Entry Detail Records), lop off the most significant digits of the sum until there + // are only 10. + return batch.leastSignificantDigits(hash, 10) } -// The Originator Status Code is not equal to “2” for DNE if the Transaction Code is 23 or 33 -func (batch *batch) isOriginatorDNE() error { - if batch.header.OriginatorStatusCode != 2 { - for _, entry := range batch.entries { - if entry.TransactionCode == 23 || entry.TransactionCode == 33 { - msg := fmt.Sprintf(msgBatchOriginatorDNE, batch.header.OriginatorStatusCode) - return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "OriginatorStatusCode", Msg: msg} +// "Only an agency of the United States Government may originate a DNE entry" - NACHA Operating Rules +// Origination code '2' is for government agencies. Codes 21, 23, 31, and 33 are the only transaction codes +// allowed for DNEs. Tranaction codes 21 and 31 are just for returns or NOCs of the 23 and 33 codes. +// So we check that the Originator Status Code is not equal to “2” for DNE if the Transaction Code is 23 or 33 +func (batch *Batch) isOriginatorDNE() error { + if batch.Header.OriginatorStatusCode != 2 && batch.Header.StandardEntryClassCode == DNE { + for _, entry := range batch.Entries { + if entry.TransactionCode == CheckingPrenoteCredit || entry.TransactionCode == SavingsPrenoteCredit { + return batch.Error("OriginatorStatusCode", ErrBatchOriginatorDNE, batch.Header.OriginatorStatusCode) } } } @@ -284,74 +799,422 @@ func (batch *batch) isOriginatorDNE() error { } // isTraceNumberODFI checks if the first 8 positions of the entry detail trace number -// match the batch header odfi -func (batch *batch) isTraceNumberODFI() error { - for _, entry := range batch.entries { - if batch.header.ODFIIdentificationField() != entry.TraceNumberField()[:8] { - msg := fmt.Sprintf(msgBatchTraceNumberNotODFI, batch.header.ODFIIdentificationField(), entry.TraceNumberField()[:8]) - return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "ODFIIdentificationField", Msg: msg} +// match the batch header ODFI +func (batch *Batch) isTraceNumberODFI() error { + if batch.validateOpts != nil && batch.validateOpts.BypassOriginValidation { + return nil + } + for _, entry := range batch.Entries { + if batch.Header.ODFIIdentificationField() != entry.TraceNumberField()[:8] { + return batch.Error("ODFIIdentificationField", + NewErrBatchTraceNumberNotODFI(batch.Header.ODFIIdentificationField(), entry.TraceNumberField()[:8])) } } - return nil } // isAddendaSequence check multiple errors on addenda records in the batch entries -func (batch *batch) isAddendaSequence() error { - for _, entry := range batch.entries { - if len(entry.Addendum) > 0 { +func (batch *Batch) isAddendaSequence() error { + for _, entry := range batch.Entries { + + if entry.Addenda02 != nil { + if entry.AddendaRecordIndicator != 1 { + return batch.Error("AddendaRecordIndicator", ErrBatchAddendaIndicator) + } + } + if len(entry.Addenda05) > 0 { // addenda without indicator flag of 1 if entry.AddendaRecordIndicator != 1 { - return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "AddendaRecordIndicator", Msg: msgBatchAddendaIndicator} + return batch.Error("AddendaRecordIndicator", ErrBatchAddendaIndicator) } lastSeq := -1 - // check if sequence is assending - for _, addenda := range entry.Addendum { - if addenda.SequenceNumber < lastSeq { - msg := fmt.Sprintf(msgBatchAscending, addenda.SequenceNumber, lastSeq) - return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "SequenceNumber", Msg: msg} + // check if sequence is ascending + for _, a := range entry.Addenda05 { + // sequences don't exist in NOC or Return addenda + + if a.SequenceNumber < lastSeq { + return batch.Error("SequenceNumber", NewErrBatchAscending(lastSeq, a.SequenceNumber)) } - lastSeq = addenda.SequenceNumber + lastSeq = a.SequenceNumber // check that we are in the correct Entry Detail - if !(addenda.EntryDetailSequenceNumberField() == entry.TraceNumberField()[8:]) { - msg := fmt.Sprintf(msgBatchAddendaTraceNumber, addenda.EntryDetailSequenceNumberField(), entry.TraceNumberField()[8:]) - return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "TraceNumber", Msg: msg} + if !(a.EntryDetailSequenceNumberField() == entry.TraceNumberField()[8:]) { + return batch.Error("TraceNumber", NewErrBatchAscending(lastSeq, a.SequenceNumber)) } } } + if entry.Addenda98 != nil { + if entry.AddendaRecordIndicator != 1 { + return batch.Error("AddendaRecordIndicator", ErrBatchAddendaIndicator) + } + } + if entry.Addenda99 != nil { + if entry.AddendaRecordIndicator != 1 { + return batch.Error("AddendaRecordIndicator", ErrBatchAddendaIndicator) + } + } + if entry.Addenda99Dishonored != nil { + if entry.AddendaRecordIndicator != 1 { + return batch.Error("AddendaRecordIndicator", ErrBatchAddendaIndicator) + } + } + if entry.Addenda99Contested != nil { + if entry.AddendaRecordIndicator != 1 { + return batch.Error("AddendaRecordIndicator", ErrBatchAddendaIndicator) + } + } } return nil } -// isAddendaCount iterates through each entry detail and checks the number of addendum is greater than the count paramater otherwise it returns an error. -// Following SEC codes allow for none or one Addendum -// "PPD", "WEB", "CCD", "CIE", "DNE", "MTE", "POS", "SHR" -func (batch *batch) isAddendaCount(count int) error { - for _, entry := range batch.entries { - if !entry.HasReturnAddenda() { - if len(entry.Addendum) > count { - msg := fmt.Sprintf(msgBatchAddendaCount, len(entry.Addendum), count, batch.header.StandardEntryClassCode) - return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "AddendaCount", Msg: msg} +// isCategory verifies that a Forward and Return Category are not in the same batch +func (batch *Batch) isCategory() error { + if !batch.IsADV() { + category := batch.GetEntries()[0].Category + if len(batch.Entries) > 1 { + for i := 0; i < len(batch.Entries); i++ { + if batch.Entries[i].Category == CategoryNOC { + continue + } + if batch.Entries[i].Category != category { + return batch.Error("Category", NewErrBatchCategory(batch.Entries[i].Category, category)) + } } - } else { - if len(entry.ReturnAddendum) > count { - msg := fmt.Sprintf(msgBatchAddendaCount, len(entry.ReturnAddendum), count, batch.header.StandardEntryClassCode) - return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "ReturnAddendaCount", Msg: msg} + } + } else { + category := batch.GetADVEntries()[0].Category + if len(batch.ADVEntries) > 1 { + for i := 0; i < len(batch.ADVEntries); i++ { + if batch.ADVEntries[i].Category != category { + return batch.Error("Category", NewErrBatchCategory(batch.ADVEntries[i].Category, category)) + } } } } + + return nil +} + +// addendaFieldInclusion verifies Addenda* Field Inclusion based on entry.Category and +// batchHeader.StandardEntryClassCode +// Forward Entries: +// MTE, POS, and SHR can only have Addenda02 +// ACK, ATX, CCD, CIE, CTX, DNE, ENR, WEB, PPD, TRX can only have Addenda05 +// ARC, BOC, POP, RCK, TEL, TRC, XCK cannot have Addenda02 or Addenda05 +// Notification of Change: +// COR and Addenda98 +// Return: +// Addenda99, Addenda99Dishonored, Addenda99Contested +func (batch *Batch) addendaFieldInclusion(entry *EntryDetail) error { + switch entry.Category { + case CategoryForward: + if err := batch.addendaFieldInclusionForward(entry); err != nil { + return err + } + case CategoryNOC: + if err := batch.addendaFieldInclusionNOC(entry); err != nil { + return err + } + case CategoryReturn, CategoryDishonoredReturn, CategoryDishonoredReturnContested: + if err := batch.addendaFieldInclusionReturn(entry); err != nil { + return err + } + } + return nil +} + +// addendaFieldInclusionForward verifies Addenda* Field Inclusion for entry.Category Forward +func (batch *Batch) addendaFieldInclusionForward(entry *EntryDetail) error { + switch batch.Header.StandardEntryClassCode { + case MTE, POS, SHR: + if entry.Addenda02 == nil { + return batch.Error("Addenda02", ErrFieldInclusion) + } + if entry.Addenda05 != nil { + return batch.Error("Addenda05", ErrBatchAddendaCategory, entry.Category) + } + // ACK, ATX, CCD, CIE, CTX, DNE, ENR WEB, PPD, TRX can only have Addenda05 + case ACK, ATX, CCD, CIE, CTX, DNE, ENR, WEB, PPD, TRX: + if entry.Addenda02 != nil { + return batch.Error("Addenda02", ErrBatchAddendaCategory, entry.Category) + } + case ARC, BOC, COR, POP, RCK, TEL, TRC, XCK: + if entry.Addenda02 != nil { + return batch.Error("Addenda02", ErrBatchAddendaCategory, entry.Category) + } + if entry.Addenda05 != nil { + return batch.Error("Addenda05", ErrBatchAddendaCategory, entry.Category) + } + } + if batch.Header.StandardEntryClassCode != COR { + if entry.Addenda98 != nil { + return batch.Error("Addenda98", ErrBatchAddendaCategory, entry.Category) + } + } + if entry.Addenda99 != nil { + return batch.Error("Addenda99", ErrBatchAddendaCategory, entry.Category) + } return nil } -// isTypeCode takes a typecode string and verifies addenda records match -func (batch *batch) isTypeCode(typeCode string) error { - for _, entry := range batch.entries { - for _, addenda := range entry.Addendum { - if addenda.TypeCode != typeCode { - msg := fmt.Sprintf(msgBatchTypeCode, addenda.TypeCode, typeCode, batch.header.StandardEntryClassCode) - return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "TypeCode", Msg: msg} +// addendaFieldInclusionNOC verifies Addenda* Field Inclusion for entry.Category NOC +func (batch *Batch) addendaFieldInclusionNOC(entry *EntryDetail) error { + if entry.Addenda02 != nil { + return batch.Error("Addenda02", ErrBatchAddendaCategory, entry.Category) + } + if entry.Addenda05 != nil { + return batch.Error("Addenda05", ErrBatchAddendaCategory, entry.Category) + } + if batch.Header.StandardEntryClassCode != COR { + if entry.Addenda98 != nil { + return batch.Error("Addenda98", ErrFieldInclusion) + } + } + if entry.Addenda99 != nil { + return batch.Error("Addenda99", ErrBatchAddendaCategory, entry.Category) + } + return nil +} + +// addendaFieldInclusionReturn verifies Addenda* Field Inclusion for entry.Category Return +func (batch *Batch) addendaFieldInclusionReturn(entry *EntryDetail) error { + if entry.Addenda02 != nil { + return batch.Error("Addenda02", ErrBatchAddendaCategory, entry.Category) + } + if entry.Addenda05 != nil { + return batch.Error("Addenda05", ErrBatchAddendaCategory, entry.Category) + } + if entry.Addenda98 != nil { + return batch.Error("Addenda98", ErrBatchAddendaCategory, entry.Category) + } + if entry.Addenda99 == nil && entry.Addenda99Dishonored == nil && entry.Addenda99Contested == nil { + // Offset entries within a Return batch will not have an Addenda99 record as they might be + // used to zero accounting entries. + // + // See: https://github.com/moov-io/ach/issues/1010 + if entry.IndividualName == offsetIndividualName { + return nil + } + return batch.Error("Addenda99", ErrFieldInclusion) + } + return nil +} + +// IsADV determines if a batch is batch type ADV - BatchADV +func (batch *Batch) IsADV() bool { + ok := batch.GetHeader().StandardEntryClassCode == ADV + return ok +} + +// ValidTranCodeForServiceClassCode validates a TransactionCode is valid for a ServiceClassCode +func (batch *Batch) ValidTranCodeForServiceClassCode(entry *EntryDetail) error { + // ADV should use ADVEntryDetail + switch entry.TransactionCode { + case CreditForDebitsOriginated, CreditForCreditsReceived, CreditForCreditsRejected, CreditSummary, + DebitForCreditsOriginated, DebitForDebitsReceived, DebitForDebitsRejectedBatches, DebitSummary: + return batch.Error("TransactionCode", ErrBatchTransactionCode, entry.TransactionCode) + } + + if entry.validateOpts != nil && entry.validateOpts.CheckTransactionCode != nil { + // We're unable to validate the ServiceClassCode with custom TransactionCode validation. + return nil + } + + switch batch.Header.ServiceClassCode { + case AutomatedAccountingAdvices: + return batch.Error("ServiceClassCode", ErrBatchServiceClassCode, batch.Header.ServiceClassCode) + + case MixedDebitsAndCredits: + return nil + + case CreditsOnly: + if entry.CreditOrDebit() != "C" { + return batch.Error("TransactionCode", NewErrBatchServiceClassTranCode(batch.Header.ServiceClassCode, entry.TransactionCode)) + } + case DebitsOnly: + if entry.CreditOrDebit() != "D" { + return batch.Error("TransactionCode", NewErrBatchServiceClassTranCode(batch.Header.ServiceClassCode, entry.TransactionCode)) + } + } + return nil +} + +// Equal returns true only if two Batch (or any Batcher) objects are equal. Equality is determined by +// many of the ACH Batch and EntryDetail properties. +func (batch *Batch) Equal(other Batcher) bool { + // Some fields are intentionally not compared as they could vary between batches that would otherwise be the same. + if batch == nil || other == nil || batch.Header == nil || other.GetHeader() == nil { + return false + } + if !batch.Header.Equal(other.GetHeader()) { + return false + } + oentries := other.GetEntries() + if len(batch.Entries) != len(oentries) { + return false + } + equalEntries := 0 + for i := range batch.Entries { + for j := range oentries { + if batch.Entries[i].TransactionCode != oentries[j].TransactionCode { + continue // skip to next EntryDetail + } + if batch.Entries[i].RDFIIdentification != oentries[j].RDFIIdentification { + continue // skip to next EntryDetail + } + if batch.Entries[i].CheckDigit != oentries[j].CheckDigit { + continue // skip to next EntryDetail + } + if batch.Entries[i].DFIAccountNumber != oentries[j].DFIAccountNumber { + continue // skip to next EntryDetail + } + if batch.Entries[i].Amount != oentries[j].Amount { + continue // skip to next EntryDetail + } + if batch.Entries[i].IdentificationNumber != oentries[j].IdentificationNumber { + continue // skip to next EntryDetail + } + if batch.Entries[i].IndividualName != oentries[j].IndividualName { + continue // skip to next EntryDetail + } + if batch.Entries[i].DiscretionaryData != oentries[j].DiscretionaryData { + continue // skip to next EntryDetail + } + equalEntries++ + } + } + return len(batch.Entries) == equalEntries && equalEntries != 0 +} + +// WithOffset sets the Offset information onto a Batch so that during Create a balanced offset record(s) at the end of each batch. +// +// If there are debits, there is a credit offset matching the sum of the debits. If there are credits, there is a debit offset matching +// the sum of the credits. They are mutually exclusive. +func (b *Batch) WithOffset(off *Offset) { + b.offset = off +} + +const offsetIndividualName = "OFFSET" + +func (b *Batch) upsertOffsets() error { + if b == nil || b.offset == nil { + return nil + } + if err := CheckRoutingNumber(b.offset.RoutingNumber); err != nil { + return fmt.Errorf("offset: invalid routing number %s: %v", b.offset.RoutingNumber, err) + } + + // remove any Offset records already on the batch + for i := 0; i < len(b.Entries); i++ { + // TODO(adam): Should we remove this based on checking the last element is + // debit/credit and sums to all the other elements (which are mutually exclusive to + // the last record being debit or credit)? + // See: https://github.com/moov-io/ach/issues/540 + if strings.EqualFold(b.Entries[i].IndividualName, offsetIndividualName) { + // fixup BatchControl records for our conditional after this for loop + if b.Entries[i].TransactionCode == CheckingCredit || b.Entries[i].TransactionCode == SavingsCredit { + b.Control.TotalCreditEntryDollarAmount -= b.Entries[i].Amount + } else { + b.Control.TotalDebitEntryDollarAmount -= b.Entries[i].Amount } + // remove the EntryDetail + b.Control.EntryAddendaCount -= 1 + b.Entries = append(b.Entries[:i], b.Entries[i+i:]...) + i-- } } + + // Make sure the offset account type is valid + if err := b.offset.AccountType.validate(); err != nil { + return err + } + + // Create our debit offset EntryDetail + debitED := createOffsetEntryDetail(b.offset, b) + debitED.TraceNumber = strconv.Itoa(lastTraceNumber(b.Entries) + 1) + debitED.Amount = b.Control.TotalCreditEntryDollarAmount + switch b.offset.AccountType { + case OffsetChecking: + debitED.TransactionCode = CheckingDebit + case OffsetSavings: + debitED.TransactionCode = SavingsDebit + } + if debitED.Amount == 0 { + debitED = nil // zero out so we don't add an empty OFFSET EntryDetail + } + + // Create our credit offset EntryDetail + creditED := createOffsetEntryDetail(b.offset, b) + creditED.TraceNumber = strconv.Itoa(lastTraceNumber(b.Entries) + 2) + creditED.Amount = b.Control.TotalDebitEntryDollarAmount + switch b.offset.AccountType { + case OffsetChecking: + creditED.TransactionCode = CheckingCredit + case OffsetSavings: + creditED.TransactionCode = SavingsCredit + } + if creditED.Amount == 0 { + creditED = nil // zero out so we don't add an empty OFFSET EntryDetail + } + + // Add both EntryDetails to our Batch and recalculate some fields + if debitED != nil { + b.AddEntry(debitED) + b.Control.EntryAddendaCount += 1 + b.Control.TotalDebitEntryDollarAmount += debitED.Amount + } + if creditED != nil { + b.AddEntry(creditED) + b.Control.EntryAddendaCount += 1 + b.Control.TotalCreditEntryDollarAmount += creditED.Amount + } + b.Header.ServiceClassCode = MixedDebitsAndCredits + + b.Control.ServiceClassCode = MixedDebitsAndCredits + b.Control.EntryHash = b.calculateEntryHash() + return nil } + +func createOffsetEntryDetail(off *Offset, batch *Batch) *EntryDetail { + ed := NewEntryDetail() + ed.RDFIIdentification = batch.offset.RoutingNumber[:8] + ed.CheckDigit = batch.offset.RoutingNumber[8:9] + ed.DFIAccountNumber = batch.offset.AccountNumber + ed.IdentificationNumber = "" // left empty + ed.IndividualName = offsetIndividualName + ed.DiscretionaryData = batch.offset.Description + if len(batch.Entries) > 0 { + ed.Category = batch.Entries[0].Category + } + return ed +} + +// aba8 returns the first 8 digits of an ABA routing number. +// If the input is invalid then an empty string is returned. +func aba8(rtn string) string { + n := utf8.RuneCountInString(rtn) + switch { + case n > 10: + return "" + case n == 10: + if rtn[0] == '0' || rtn[0] == '1' { + return rtn[1:9] // ACH server will prefix with space, 0, or 1 + } + return "" + case n != 8 && n != 9: + return "" + default: + return rtn[:8] + } +} + +func lastTraceNumber(entries []*EntryDetail) int { + if len(entries) == 0 { + return 0 + } + n, err := strconv.Atoi(entries[len(entries)-1].TraceNumber) + if err != nil { + return 0 + } + return n +} diff --git a/batchACK.go b/batchACK.go new file mode 100644 index 000000000..df2f0fece --- /dev/null +++ b/batchACK.go @@ -0,0 +1,87 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +// BatchACK is a batch file that handles SEC payment type ACK and ACK+. +// Acknowledgement of a Corporate credit by the Receiving Depository Financial Institution (RDFI). +// For commercial accounts only. +type BatchACK struct { + Batch +} + +// NewBatchACK returns a *BatchACK +func NewBatchACK(bh *BatchHeader) *BatchACK { + batch := new(BatchACK) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} + +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the batch. +func (batch *BatchACK) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + // Add configuration and type specific validation. + if batch.Header.StandardEntryClassCode != ACK { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, ACK) + } + // Range through Entries + for _, entry := range batch.Entries { + // Amount must be zero for Acknowledgement Entries + if entry.Amount > 0 { + return batch.Error("Amount", ErrBatchAmountNonZero, entry.Amount) + } + if len(entry.Addenda05) > 1 { + return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 1)) + } + switch entry.TransactionCode { + case CheckingZeroDollarRemittanceCredit, SavingsZeroDollarRemittanceCredit: + default: + return batch.Error("TransactionCode", ErrBatchTransactionCode, entry.TransactionCode) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + } + + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchACK) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + return batch.Validate() +} diff --git a/batchACK_test.go b/batchACK_test.go new file mode 100644 index 000000000..45f8cf20a --- /dev/null +++ b/batchACK_test.go @@ -0,0 +1,325 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "log" + "testing" + + "github.com/moov-io/base" +) + +// mockBatchACKHeader creates a ACK batch header +func mockBatchACKHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = ACK + bh.CompanyName = "Your Company, inc" + bh.CompanyIdentification = "231380104" + bh.CompanyEntryDescription = "Vndr Pay" + bh.ODFIIdentification = "23138010" + return bh +} + +// mockACKEntryDetail creates a ACK entry detail +func mockACKEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingZeroDollarRemittanceCredit + entry.SetRDFI("121042882") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.SetOriginalTraceNumber("121042880000001") + entry.SetReceivingCompany("Best Co. #23") + entry.SetTraceNumber(mockBatchACKHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "S" + entry.AddendaRecordIndicator = 1 + entry.AddAddenda05(mockAddenda05()) + return entry +} + +// mockBatchACK creates a ACK batch +func mockBatchACK() *BatchACK { + mockBatch := NewBatchACK(mockBatchACKHeader()) + mockBatch.AddEntry(mockACKEntryDetail()) + if err := mockBatch.Create(); err != nil { + log.Fatal(err) + } + return mockBatch +} + +// testBatchACKHeader creates a ACK batch header +func testBatchACKHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchACKHeader()) + _, ok := batch.(*BatchACK) + if !ok { + t.Error("Expecting BatchACK") + } +} + +// TestBatchACKHeader tests creating a ACK batch header +func TestBatchACKHeader(t *testing.T) { + testBatchACKHeader(t) +} + +// BenchmarkBatchACKHeader benchmark creating a ACK batch header +func BenchmarkBatchACKHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchACKHeader(b) + } +} + +// testBatchACKAddendumCount batch control ACK can only have one addendum per entry detail +func testBatchACKAddendumCount(t testing.TB) { + mockBatch := mockBatchACK() + // Adding a second addenda to the mock entry + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + err := mockBatch.Create() + if !base.Match(err, NewErrBatchAddendaCount(2, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchACKAddendumCount tests batch control ACK can only have one addendum per entry detail +func TestBatchACKAddendumCount(t *testing.T) { + testBatchACKAddendumCount(t) +} + +// BenchmarkBatchACKAddendumCount benchmarks batch control ACK can only have one addendum per entry detail +func BenchmarkBatchACKAddendumCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchACKAddendumCount(b) + } +} + +// TestBatchACKAddendum98 validates Addenda98 returns an error +func TestBatchACKAddendum98(t *testing.T) { + mockBatch := NewBatchACK(mockBatchACKHeader()) + mockBatch.AddEntry(mockACKEntryDetail()) + mockAddenda98 := mockAddenda98() + mockAddenda98.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchACKAddendum99 validates Addenda99 returns an error +func TestBatchACKAddendum99(t *testing.T) { + mockBatch := NewBatchACK(mockBatchACKHeader()) + mockBatch.AddEntry(mockACKEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// testBatchACKReceivingCompanyName validates Receiving company / Individual name is a mandatory field +func testBatchACKReceivingCompanyName(t testing.TB) { + mockBatch := mockBatchACK() + // modify the Individual name / receiving company to nothing + mockBatch.GetEntries()[0].SetReceivingCompany("") + err := mockBatch.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchACKReceivingCompanyName tests validating receiving company / Individual name is a mandatory field +func TestBatchACKReceivingCompanyName(t *testing.T) { + testBatchACKReceivingCompanyName(t) +} + +// BenchmarkBatchACKReceivingCompanyName benchmarks validating receiving company / Individual name is a mandatory field +func BenchmarkBatchACKReceivingCompanyName(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchACKReceivingCompanyName(b) + } +} + +// testBatchACKAddendaTypeCode validates addenda type code is 05 +func testBatchACKAddendaTypeCode(t testing.TB) { + mockBatch := mockBatchACK() + mockBatch.GetEntries()[0].Addenda05[0].TypeCode = "07" + err := mockBatch.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchACKAddendaTypeCode tests validating addenda type code is 05 +func TestBatchACKAddendaTypeCode(t *testing.T) { + testBatchACKAddendaTypeCode(t) +} + +// BenchmarkBatchACKAddendaTypeCod benchmarks validating addenda type code is 05 +func BenchmarkBatchACKAddendaTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchACKAddendaTypeCode(b) + } +} + +// testBatchACKSEC validates that the standard entry class code is ACK for batchACK +func testBatchACKSEC(t testing.TB) { + mockBatch := mockBatchACK() + mockBatch.Header.StandardEntryClassCode = RCK + err := mockBatch.Validate() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchACKSEC tests validating that the standard entry class code is ACK for batchACK +func TestBatchACKSEC(t *testing.T) { + testBatchACKSEC(t) +} + +// BenchmarkBatchACKSEC benchmarks validating that the standard entry class code is ACK for batch ACK +func BenchmarkBatchACKSEC(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchACKSEC(b) + } +} + +// testBatchACKAddendaCount validates batch ACK addenda count +func testBatchACKAddendaCount(t testing.TB) { + mockBatch := mockBatchACK() + addenda05 := mockAddenda05() + mockBatch.GetEntries()[0].AddAddenda05(addenda05) + err := mockBatch.Create() + if !base.Match(err, NewErrBatchAddendaCount(2, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchACKAddendaCount tests validating batch ACK addenda count +func TestBatchACKAddendaCount(t *testing.T) { + testBatchACKAddendaCount(t) +} + +// BenchmarkBatchACKAddendaCount benchmarks validating batch ACK addenda count +func BenchmarkBatchACKAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchACKAddendaCount(b) + } +} + +// testBatchACKServiceClassCode validates ServiceClassCode +func testBatchACKServiceClassCode(t testing.TB) { + mockBatch := mockBatchACK() + // Batch Header information is required to Create a batch. + mockBatch.GetHeader().ServiceClassCode = 0 + err := mockBatch.Create() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchACKServiceClassCode tests validating ServiceClassCode +func TestBatchACKServiceClassCode(t *testing.T) { + testBatchACKServiceClassCode(t) +} + +// BenchmarkBatchACKServiceClassCode benchmarks validating ServiceClassCode +func BenchmarkBatchACKServiceClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchACKServiceClassCode(b) + } +} + +// testBatchACKReceivingCompanyField validates ACKReceivingCompanyField +// underlying IndividualName +func testBatchACKReceivingCompanyField(t testing.TB) { + mockBatch := mockBatchACK() + ts := mockBatch.Entries[0].ReceivingCompanyField() + if ts != "Best Co. #23 " { + t.Error("Receiving Company Field is invalid") + } +} + +// TestBatchACKReceivingCompanyField tests validating ACKReceivingCompanyField +// underlying IndividualName +func TestBatchACKReceivingCompanyFieldField(t *testing.T) { + testBatchACKReceivingCompanyField(t) +} + +// BenchmarkBatchACKReceivingCompanyField benchmarks validating ACKReceivingCompanyField +// underlying IndividualName +func BenchmarkBatchACKReceivingCompanyField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchACKReceivingCompanyField(b) + } +} + +// TestBatchACKAmount validates Amount +func TestBatchACKAmount(t *testing.T) { + mockBatch := mockBatchACK() + // Batch Header information is required to Create a batch. + mockBatch.GetEntries()[0].Amount = 25000 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAmountNonZero) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchACKTransactionCode validates TransactionCode +func TestBatchACKTransactionCode(t *testing.T) { + mockBatch := mockBatchACK() + // Batch Header information is required to Create a batch. + mockBatch.GetEntries()[0].TransactionCode = CheckingCredit + err := mockBatch.Create() + if !base.Match(err, ErrBatchTransactionCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchACKAddendum99Category validates Addenda99 returns an error +func TestBatchACKAddendum99Category(t *testing.T) { + mockBatch := NewBatchACK(mockBatchACKHeader()) + mockBatch.AddEntry(mockACKEntryDetail()) + mockAddenda99 := mockAddenda99() + mockBatch.GetEntries()[0].Category = CategoryForward + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchACKValidTranCodeForServiceClassCode validates a transactionCode based on ServiceClassCode +func TestBatchACKValidTranCodeForServiceClassCode(t *testing.T) { + mockBatch := mockBatchACK() + mockBatch.GetHeader().ServiceClassCode = DebitsOnly + err := mockBatch.Create() + if !base.Match(err, NewErrBatchServiceClassTranCode(DebitsOnly, 24)) { + t.Errorf("%T: %s", err, err) + } +} diff --git a/batchADV.go b/batchADV.go new file mode 100644 index 000000000..4776a7761 --- /dev/null +++ b/batchADV.go @@ -0,0 +1,86 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +// BatchADV holds the Batch Header and Batch Control and all Entry Records for ADV Entries +// +// The ADV entry identifies a Non-Monetary Entry that is used by an ACH Operator to provide accounting information +// regarding an entry to participating DFI's. It's an optional service provided by ACH operators and must be requested +// by a DFI wanting the service. +type BatchADV struct { + Batch +} + +// NewBatchADV returns a *BatchADV +func NewBatchADV(bh *BatchHeader) *BatchADV { + batch := new(BatchADV) + batch.SetADVControl(NewADVBatchControl()) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} + +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the batch. +func (batch *BatchADV) Validate() error { + if batch.Header.StandardEntryClassCode != ADV { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, ADV) + } + if batch.Header.ServiceClassCode != AutomatedAccountingAdvices { + return batch.Error("ServiceClassCode", ErrBatchServiceClassCode, batch.Header.ServiceClassCode) + } + if batch.Header.OriginatorStatusCode != 0 { + return batch.Error("OriginatorStatusCode", ErrOrigStatusCode, batch.Header.OriginatorStatusCode) + } + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + // Add configuration and type specific validation for this type. + for _, entry := range batch.ADVEntries { + if entry.Category == CategoryForward { + switch entry.TransactionCode { + case CreditForDebitsOriginated, CreditForCreditsReceived, CreditForCreditsRejected, CreditSummary, + DebitForCreditsOriginated, DebitForDebitsReceived, DebitForDebitsRejectedBatches, DebitSummary: + default: + return batch.Error("TransactionCode", ErrBatchTransactionCode, entry.TransactionCode) + } + if entry.Addenda99 != nil { + return batch.Error("Addenda99", ErrBatchAddendaCategory, entry.Category) + } + } + } + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchADV) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + // Additional steps specific to batch type + // ... + return batch.Validate() +} diff --git a/batchADV_test.go b/batchADV_test.go new file mode 100644 index 000000000..72a995e2e --- /dev/null +++ b/batchADV_test.go @@ -0,0 +1,182 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + "time" + + "github.com/moov-io/base" +) + +// mockBatchADVHeader creates a ADV batch header +func mockBatchADVHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = AutomatedAccountingAdvices + bh.StandardEntryClassCode = ADV + bh.CompanyName = "Your Company, inc" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "Accounting" + bh.ODFIIdentification = "12104288" + bh.OriginatorStatusCode = 0 + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + return bh +} + +// mockBatchADV creates a ADV batch +func mockBatchADV() *BatchADV { + mockBatch := NewBatchADV(mockBatchADVHeader()) + mockBatch.AddADVEntry(mockADVEntryDetail()) + if err := mockBatch.Create(); err != nil { + panic(err) + } + return mockBatch +} + +// testBatchADVHeader creates a ADV batch header +func testBatchADVHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchADVHeader()) + _, ok := batch.(*BatchADV) + if !ok { + t.Error("Expecting BatchADV") + } +} + +// TestBatchADVHeader tests creating a ADV batch header +func TestBatchADVHeader(t *testing.T) { + testBatchADVHeader(t) +} + +// BenchmarkBatchADVHeader benchmark creating a ADV batch header +func BenchmarkBatchADVHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchADVHeader(b) + } +} + +// TestBatchADVAddendum99 validates Addenda99 returns an error +func TestBatchADVAddendum99(t *testing.T) { + mockBatch := NewBatchADV(mockBatchADVHeader()) + mockBatch.AddADVEntry(mockADVEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetADVEntries()[0].Category = CategoryReturn + mockBatch.GetADVEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// testBatchADVSEC validates that the standard entry class code is ADV for batchADV +func testBatchADVSEC(t testing.TB) { + mockBatch := mockBatchADV() + mockBatch.Header.StandardEntryClassCode = RCK + err := mockBatch.Validate() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchADVSEC tests validating that the standard entry class code is ADV for batchADV +func TestBatchADVSEC(t *testing.T) { + testBatchADVSEC(t) +} + +// BenchmarkBatchADVSEC benchmarks validating that the standard entry class code is ADV for batch ADV +func BenchmarkBatchADVSEC(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchADVSEC(b) + } +} + +// testBatchADVServiceClassCode validates ServiceClassCode +func testBatchADVServiceClassCode(t testing.TB) { + mockBatch := mockBatchADV() + // Batch Header information is required to Create a batch. + mockBatch.GetHeader().ServiceClassCode = 220 + err := mockBatch.Create() + if !base.Match(err, ErrBatchServiceClassCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchADVServiceClassCode tests validating ServiceClassCode +func TestBatchADVServiceClassCode(t *testing.T) { + testBatchADVServiceClassCode(t) +} + +// BenchmarkBatchADVServiceClassCode benchmarks validating ServiceClassCode +func BenchmarkBatchADVServiceClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchADVServiceClassCode(b) + } +} + +// TestBatchADVAddendum99Category validates Addenda99 returns an error +func TestBatchADVAddendum99Category(t *testing.T) { + mockBatch := NewBatchADV(mockBatchADVHeader()) + mockBatch.AddADVEntry(mockADVEntryDetail()) + mockAddenda99 := mockAddenda99() + mockBatch.GetADVEntries()[0].Category = CategoryForward + mockBatch.GetADVEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchADVInvalidTransactionCode validates TransactionCode +func TestBatchADVInvalidTransactionCode(t *testing.T) { + mockBatch := mockBatchADV() + // Batch Header information is required to Create a batch. + mockBatch.GetADVEntries()[0].TransactionCode = CheckingCredit + err := mockBatch.Create() + if !base.Match(err, ErrBatchTransactionCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVMaximumEntries validates maximum entries for an ADV ACH file +func TestADVMaximumEntries(t *testing.T) { + entry := mockADVEntryDetail() + entry.AddendaRecordIndicator = 0 + batch := NewBatchADV(mockBatchADVHeader()) + batch.SetHeader(mockBatchADVHeader()) + + for i := 0; i < 10000; i++ { + batch.AddADVEntry(entry) + } + err := batch.Create() + if !base.Match(err, ErrBatchADVCount) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchADVOriginatorStatusCode validates the originator status code +func TestBatchADVOriginatorStatusCode(t *testing.T) { + mockBatch := mockBatchADV() + mockBatch.Header.OriginatorStatusCode = 1 + err := mockBatch.Create() + if !base.Match(err, ErrOrigStatusCode) { + t.Errorf("%T: %s", err, err) + } +} diff --git a/batchARC.go b/batchARC.go new file mode 100644 index 000000000..5372973b8 --- /dev/null +++ b/batchARC.go @@ -0,0 +1,108 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +// BatchARC holds the BatchHeader and BatchControl and all EntryDetail for ARC Entries. +// +// Accounts Receivable Entry (ARC). A consumer check converted to a one-time ACH debit. +// The Accounts Receivable (ARC) Entry provides billers the opportunity to initiate single-entry ACH +// debits to customer accounts by converting checks at the point of receipt through the U.S. mail, at +// a drop box location or in-person for payment of a bill at a manned location. The biller is required +// to provide the customer with notice prior to the acceptance of the check that states the receipt of +// the customer's check will be deemed as the authorization for an ARC debit entry to the customer's +// account. The provision of the notice and the receipt of the check together constitute authorization +// for the ARC entry. The customer's check is solely be used as a source document to obtain the routing +// number, account number and check serial number. +// +// The difference between ARC and POP is that ARC can result from a check mailed in whereas POP is in-person. +type BatchARC struct { + Batch +} + +// NewBatchARC returns a *BatchARC +func NewBatchARC(bh *BatchHeader) *BatchARC { + batch := new(BatchARC) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} + +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the batch. +func (batch *BatchARC) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + // Add configuration and type specific validation for this type. + + if batch.Header.StandardEntryClassCode != ARC { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, ARC) + } + + // ARC detail entries can only be a debit, ServiceClassCode must allow debits + switch batch.Header.ServiceClassCode { + case CreditsOnly: + return batch.Error("ServiceClassCode", ErrBatchServiceClassCode, batch.Header.ServiceClassCode) + } + + for _, entry := range batch.Entries { + // ARC detail entries must be a debit + if entry.CreditOrDebit() != "D" { + return batch.Error("TransactionCode", ErrBatchDebitOnly, entry.TransactionCode) + } + + // Amount must be 25,000 or less + if entry.Amount > 2500000 { + return batch.Error("Amount", NewErrBatchAmount(entry.Amount, 2500000)) + } + + // CheckSerialNumber underlying IdentificationNumber, must be defined + if entry.IdentificationNumber == "" { + return batch.Error("CheckSerialNumber", ErrBatchCheckSerialNumber) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + } + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchARC) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + // Additional steps specific to batch type + // ... + + return batch.Validate() +} diff --git a/batchARC_test.go b/batchARC_test.go new file mode 100644 index 000000000..b71c13c11 --- /dev/null +++ b/batchARC_test.go @@ -0,0 +1,426 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockBatchARCHeader creates a BatchARC BatchHeader +func mockBatchARCHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = ARC + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = ARC + bh.ODFIIdentification = "12104288" + return bh +} + +// mockARCEntryDetail creates a BatchARC EntryDetail +func mockARCEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.SetCheckSerialNumber("123456789") + entry.SetReceivingCompany("ABC Company") + entry.SetTraceNumber(mockBatchARCHeader().ODFIIdentification, 1) + entry.Category = CategoryForward + return entry +} + +// mockBatchARC creates a BatchARC +func mockBatchARC() *BatchARC { + mockBatch := NewBatchARC(mockBatchARCHeader()) + mockBatch.AddEntry(mockARCEntryDetail()) + if err := mockBatch.Create(); err != nil { + panic(err) + } + return mockBatch +} + +// mockBatchARCHeaderCredit creates a BatchARC BatchHeader +func mockBatchARCHeaderCredit() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = ARC + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = ARC + bh.ODFIIdentification = "12104288" + return bh +} + +// mockARCEntryDetailCredit creates a ARC EntryDetail with a credit entry +func mockARCEntryDetailCredit() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.SetCheckSerialNumber("123456789") + entry.SetReceivingCompany("ABC Company") + entry.SetTraceNumber(mockBatchARCHeader().ODFIIdentification, 1) + entry.Category = CategoryForward + return entry +} + +// mockBatchARCCredit creates a BatchARC with a Credit entry +func mockBatchARCCredit() *BatchARC { + mockBatch := NewBatchARC(mockBatchARCHeaderCredit()) + mockBatch.AddEntry(mockARCEntryDetailCredit()) + return mockBatch +} + +// testBatchARCHeader creates a BatchARC BatchHeader +func testBatchARCHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchARCHeader()) + err, ok := batch.(*BatchARC) + if !ok { + t.Errorf("Expecting BatchARC got %T", err) + } +} + +// TestBatchARCHeader tests validating BatchARC BatchHeader +func TestBatchARCHeader(t *testing.T) { + testBatchARCHeader(t) +} + +// BenchmarkBatchARCHeader benchmarks validating BatchARC BatchHeader +func BenchmarkBatchARCHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchARCHeader(b) + } +} + +// testBatchARCCreate validates BatchARC create +func testBatchARCCreate(t testing.TB) { + mockBatch := mockBatchARC() + if err := mockBatch.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchARCCreate tests validating BatchARC create +func TestBatchARCCreate(t *testing.T) { + testBatchARCCreate(t) +} + +// BenchmarkBatchARCCreate benchmarks validating BatchARC create +func BenchmarkBatchARCCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchARCCreate(b) + } +} + +// testBatchARCStandardEntryClassCode validates BatchARC create for an invalid StandardEntryClassCode +func testBatchARCStandardEntryClassCode(t testing.TB) { + mockBatch := mockBatchARC() + mockBatch.Header.StandardEntryClassCode = WEB + err := mockBatch.Create() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchARCStandardEntryClassCode tests validating BatchARC create for an invalid StandardEntryClassCode +func TestBatchARCStandardEntryClassCode(t *testing.T) { + testBatchARCStandardEntryClassCode(t) +} + +// BenchmarkBatchARCStandardEntryClassCode benchmarks validating BatchARC create for an invalid StandardEntryClassCode +func BenchmarkBatchARCStandardEntryClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchARCStandardEntryClassCode(b) + } +} + +// testBatchARCServiceClassCodeEquality validates service class code equality +func testBatchARCServiceClassCodeEquality(t testing.TB) { + mockBatch := mockBatchARC() + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(220, MixedDebitsAndCredits)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchARCServiceClassCodeEquality tests validating service class code equality +func TestBatchARCServiceClassCodeEquality(t *testing.T) { + testBatchARCServiceClassCodeEquality(t) +} + +// BenchmarkBatchARCServiceClassCodeEquality benchmarks validating service class code equality +func BenchmarkBatchARCServiceClassCodeEquality(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchARCServiceClassCodeEquality(b) + } +} + +// testBatchARCMixedCreditsAndDebits validates BatchARC create for an invalid MixedCreditsAndDebits +func testBatchARCMixedCreditsAndDebits(t testing.TB) { + mockBatch := mockBatchARC() + mockBatch.Header.ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(MixedDebitsAndCredits, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchARCMixedCreditsAndDebits tests validating BatchARC create for an invalid MixedCreditsAndDebits +func TestBatchARCMixedCreditsAndDebits(t *testing.T) { + testBatchARCMixedCreditsAndDebits(t) +} + +// BenchmarkBatchARCMixedCreditsAndDebits benchmarks validating BatchARC create for an invalid MixedCreditsAndDebits +func BenchmarkBatchARCMixedCreditsAndDebits(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchARCMixedCreditsAndDebits(b) + } +} + +// testBatchARCCreditsOnly validates BatchARC create for an invalid CreditsOnly +func testBatchARCCreditsOnly(t testing.TB) { + mockBatch := mockBatchARC() + mockBatch.Header.ServiceClassCode = CreditsOnly + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(CreditsOnly, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchARCCreditsOnly tests validating BatchARC create for an invalid CreditsOnly +func TestBatchARCCreditsOnly(t *testing.T) { + testBatchARCCreditsOnly(t) +} + +// BenchmarkBatchARCCreditsOnly benchmarks validating BatchARC create for an invalid CreditsOnly +func BenchmarkBatchARCCreditsOnly(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchARCCreditsOnly(b) + } +} + +// testBatchARCAutomatedAccountingAdvices validates BatchARC create for an invalid AutomatedAccountingAdvices +func testBatchARCAutomatedAccountingAdvices(t testing.TB) { + mockBatch := mockBatchARC() + mockBatch.Header.ServiceClassCode = AutomatedAccountingAdvices + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(AutomatedAccountingAdvices, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchARCAutomatedAccountingAdvices tests validating BatchARC create for an invalid AutomatedAccountingAdvices +func TestBatchARCAutomatedAccountingAdvices(t *testing.T) { + testBatchARCAutomatedAccountingAdvices(t) +} + +// BenchmarkBatchARCAutomatedAccountingAdvices benchmarks validating BatchARC create for an invalid AutomatedAccountingAdvices +func BenchmarkBatchARCAutomatedAccountingAdvices(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchARCAutomatedAccountingAdvices(b) + } +} + +// testBatchARCAmount validates BatchARC create for an invalid Amount +func testBatchARCAmount(t testing.TB) { + mockBatch := mockBatchARC() + mockBatch.Entries[0].Amount = 2600000 + err := mockBatch.Create() + if !base.Match(err, NewErrBatchAmount(2600000, 2500000)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchARCAmount validates BatchARC create for an invalid Amount +func TestBatchARCAmount(t *testing.T) { + testBatchARCAmount(t) +} + +// BenchmarkBatchARCAmount validates BatchARC create for an invalid Amount +func BenchmarkBatchARCAmount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchARCAmount(b) + } +} + +// testBatchARCCheckSerialNumber validates BatchARC CheckSerialNumber / IdentificationNumber is a mandatory field +func testBatchARCCheckSerialNumber(t testing.TB) { + mockBatch := mockBatchARC() + // modify CheckSerialNumber / IdentificationNumber to nothing + mockBatch.GetEntries()[0].SetCheckSerialNumber("") + err := mockBatch.Validate() + if !base.Match(err, ErrBatchCheckSerialNumber) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchARCCheckSerialNumber tests validating BatchARC +// CheckSerialNumber / IdentificationNumber is a mandatory field +func TestBatchARCCheckSerialNumber(t *testing.T) { + testBatchARCCheckSerialNumber(t) +} + +// BenchmarkBatchARCCheckSerialNumber benchmarks validating BatchARC +// CheckSerialNumber / IdentificationNumber is a mandatory field +func BenchmarkBatchARCCheckSerialNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchARCCheckSerialNumber(b) + } +} + +// testBatchARCTransactionCode validates BatchARC TransactionCode is not a credit +func testBatchARCTransactionCode(t testing.TB) { + mockBatch := mockBatchARCCredit() + err := mockBatch.Create() + if !base.Match(err, ErrBatchDebitOnly) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchARCTransactionCode tests validating BatchARC TransactionCode is not a credit +func TestBatchARCTransactionCode(t *testing.T) { + testBatchARCTransactionCode(t) +} + +// BenchmarkBatchARCTransactionCode benchmarks validating BatchARC TransactionCode is not a credit +func BenchmarkBatchARCTransactionCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchARCTransactionCode(b) + } +} + +// testBatchARCAddendaCount validates BatchARC Addenda count +func testBatchARCAddendaCount(t testing.TB) { + mockBatch := mockBatchARC() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchARCAddendaCount tests validating BatchARC Addenda count +func TestBatchARCAddendaCount(t *testing.T) { + testBatchARCAddendaCount(t) +} + +// BenchmarkBatchARCAddendaCount benchmarks validating BatchARC Addenda count +func BenchmarkBatchARCAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchARCAddendaCount(b) + } +} + +// testBatchARCInvalidBuild validates an invalid batch build +func testBatchARCInvalidBuild(t testing.TB) { + mockBatch := mockBatchARC() + mockBatch.GetHeader().ServiceClassCode = 3 + err := mockBatch.Create() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchARCInvalidBuild tests validating an invalid batch build +func TestBatchARCInvalidBuild(t *testing.T) { + testBatchARCInvalidBuild(t) +} + +// BenchmarkBatchARCInvalidBuild benchmarks validating an invalid batch build +func BenchmarkBatchARCInvalidBuild(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchARCInvalidBuild(b) + } +} + +// TestBatchARCAddendum98 validates Addenda98 returns an error +func TestBatchARCAddendum98(t *testing.T) { + mockBatch := NewBatchARC(mockBatchARCHeader()) + mockBatch.AddEntry(mockARCEntryDetail()) + mockAddenda98 := mockAddenda98() + mockAddenda98.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchARCAddendum99 validates Addenda99 returns an error +func TestBatchARCAddendum99(t *testing.T) { + mockBatch := NewBatchARC(mockBatchARCHeader()) + mockBatch.AddEntry(mockARCEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchARCAddendum99Category validates Addenda99 returns an error +func TestBatchARCAddendum99Category(t *testing.T) { + mockBatch := NewBatchARC(mockBatchARCHeader()) + mockBatch.AddEntry(mockARCEntryDetail()) + mockAddenda99 := mockAddenda99() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].Category = CategoryForward + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// testBatchARCMixedCreditsAndDebits validates BatchARC create for valid MixedCreditsAndDebits +func testBatchARCMixedCreditsAndDebitsBatchControlMixedDebitsAndCredits(t testing.TB) { + mockBatch := mockBatchARC() + mockBatch.Header.ServiceClassCode = MixedDebitsAndCredits + mockBatch.Batch.Control.ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchARCMixedCreditsAndDebitsBatchControlMixedDebitsAndCredits tests validating BatchARC create for valid MixedCreditsAndDebits +func TestBatchARCMixedCreditsAndDebitsBatchControlMixedDebitsAndCredits(t *testing.T) { + testBatchARCMixedCreditsAndDebitsBatchControlMixedDebitsAndCredits(t) +} diff --git a/batchATX.go b/batchATX.go new file mode 100644 index 000000000..8e1cca161 --- /dev/null +++ b/batchATX.go @@ -0,0 +1,105 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strconv" +) + +// BatchATX holds the BatchHeader and BatchControl and all EntryDetail for ATX (Acknowledgment) +// Entries. +// +// The ATX entry is an acknowledgement by the Receiving Depository Financial Institution (RDFI) that a +// Corporate Credit (CTX) has been received. +type BatchATX struct { + Batch +} + +// NewBatchATX returns a *BatchATX +func NewBatchATX(bh *BatchHeader) *BatchATX { + batch := new(BatchATX) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} + +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the batch. +func (batch *BatchATX) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + + // Add configuration and type specific validation for this type. + if batch.Header.StandardEntryClassCode != ATX { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, ATX) + } + + for _, entry := range batch.Entries { + // Amount must be zero for Acknowledgement Entries + if entry.Amount > 0 { + return batch.Error("Amount", ErrBatchAmountNonZero, entry.Amount) + } + switch entry.TransactionCode { + case CheckingZeroDollarRemittanceCredit, SavingsZeroDollarRemittanceCredit: + default: + return batch.Error("TransactionCode", ErrBatchTransactionCode, entry.TransactionCode) + } + + // Trapping this error, as entry.ATXAddendaRecordsField() can not be greater than 9999 + if len(entry.Addenda05) > 9999 { + return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 9999)) + + } + + // validate ATXAddendaRecord Field is equal to the actual number of Addenda records + // use 0 value if there is no Addenda records + addendaRecords, _ := strconv.Atoi(entry.CATXAddendaRecordsField()) + if len(entry.Addenda05) != addendaRecords { + return batch.Error("AddendaCount", NewErrBatchExpectedAddendaCount(len(entry.Addenda05), addendaRecords)) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + } + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchATX) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + // Additional steps specific to batch type + // ... + return batch.Validate() +} diff --git a/batchATX_test.go b/batchATX_test.go new file mode 100644 index 000000000..ee6d83bd6 --- /dev/null +++ b/batchATX_test.go @@ -0,0 +1,576 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "log" + "testing" + + "github.com/moov-io/base" +) + +// mockBatchATXHeader creates a BatchATX BatchHeader +func mockBatchATXHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = ATX + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "231380104" + bh.CompanyEntryDescription = "ACH ATX" + bh.ODFIIdentification = "23138010" + return bh +} + +// mockATXEntryDetail creates a BatchATX EntryDetail +func mockATXEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingZeroDollarRemittanceCredit + entry.SetRDFI("121042882") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.SetOriginalTraceNumber("121042880000001") + entry.SetCATXAddendaRecords(1) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.AddendaRecordIndicator = 1 + entry.AddAddenda05(mockAddenda05()) + entry.Category = CategoryForward + return entry +} + +// mockBatchATX creates a BatchATX +func mockBatchATX() *BatchATX { + mockBatch := NewBatchATX(mockBatchATXHeader()) + mockBatch.AddEntry(mockATXEntryDetail()) + if err := mockBatch.Create(); err != nil { + log.Fatal(err) + } + return mockBatch +} + +// testBatchATXHeader creates a BatchATX BatchHeader +func testBatchATXHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchATXHeader()) + err, ok := batch.(*BatchATX) + if !ok { + t.Errorf("Expecting BatchATX got %T", err) + } +} + +// TestBatchATXHeader tests validating BatchATX BatchHeader +func TestBatchATXHeader(t *testing.T) { + testBatchATXHeader(t) +} + +// BenchmarkBatchATXHeader benchmarks validating BatchATX BatchHeader +func BenchmarkBatchATXHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXHeader(b) + } +} + +// testBatchATXCreate validates BatchATX create +func testBatchATXCreate(t testing.TB) { + mockBatch := mockBatchATX() + if err := mockBatch.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchATXCreate tests validating BatchATX create +func TestBatchATXCreate(t *testing.T) { + testBatchATXCreate(t) +} + +// BenchmarkBatchATXCreate benchmarks validating BatchATX create +func BenchmarkBatchATXCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXCreate(b) + } +} + +// testBatchATXStandardEntryClassCode validates BatchATX create for an invalid StandardEntryClassCode +func testBatchATXStandardEntryClassCode(t testing.TB) { + mockBatch := mockBatchATX() + mockBatch.Header.StandardEntryClassCode = WEB + err := mockBatch.Create() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchATXStandardEntryClassCode tests validating BatchATX create for an invalid StandardEntryClassCode +func TestBatchATXStandardEntryClassCode(t *testing.T) { + testBatchATXStandardEntryClassCode(t) +} + +// BenchmarkBatchATXStandardEntryClassCode benchmarks validating BatchATX create for an invalid StandardEntryClassCode +func BenchmarkBatchATXStandardEntryClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXStandardEntryClassCode(b) + } +} + +// testBatchATXServiceClassCodeEquality validates service class code equality +func testBatchATXServiceClassCodeEquality(t testing.TB) { + mockBatch := mockBatchATX() + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(220, MixedDebitsAndCredits)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchATXServiceClassCodeEquality tests validating service class code equality +func TestBatchATXServiceClassCodeEquality(t *testing.T) { + testBatchATXServiceClassCodeEquality(t) +} + +// BenchmarkBatchATXServiceClassCodeEquality benchmarks validating service class code equality +func BenchmarkBatchATXServiceClassCodeEquality(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXServiceClassCodeEquality(b) + } +} + +// testBatchATXAddendaCount validates BatchATX Addenda05 count of 2 +func testBatchATXAddendaCount(t testing.TB) { + mockBatch := mockBatchATX() + mockBatch.GetEntries()[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + err := mockBatch.Create() + if !base.Match(err, NewErrBatchExpectedAddendaCount(2, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchATXAddendaCount tests validating BatchATX Addenda05 count of 2 +func TestBatchATXAddendaCount(t *testing.T) { + testBatchATXAddendaCount(t) +} + +// BenchmarkBatchATXAddendaCount benchmarks validating BatchATX Addendum count of 2 +func BenchmarkBatchATXAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXAddendaCount(b) + } +} + +// testBatchATXAddendaCountZero validates Addendum count of 0 +func testBatchATXAddendaCountZero(t testing.TB) { + mockBatch := NewBatchATX(mockBatchATXHeader()) + mockBatch.AddEntry(mockATXEntryDetail()) + //mockBatch.GetEntries()[0].Addenda05[0]. + err := mockBatch.Create() + // TODO: are we expecting there to be an error here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchATXAddendaCountZero tests validating Addendum count of 0 +func TestBatchATXAddendaCountZero(t *testing.T) { + testBatchATXAddendaCountZero(t) +} + +// BenchmarkBatchATXAddendaCountZero benchmarks validating Addendum count of 0 +func BenchmarkBatchATXAddendaCountZero(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXAddendaCountZero(b) + } +} + +// TestBatchATXInvalidAddenda02 validates Addenda must be Addenda05 +func TestBatchATXInvalidAddend02(t *testing.T) { + mockBatch := NewBatchATX(mockBatchATXHeader()) + entry := mockATXEntryDetail() + entry.Addenda02 = mockAddenda02() + mockBatch.AddEntry(entry) + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// testBatchATXInvalidAddenda validates Addendum must be Addenda05 with type code 05 +func testBatchATXInvalidAddenda(t testing.TB) { + mockBatch := NewBatchATX(mockBatchATXHeader()) + mockBatch.AddEntry(mockATXEntryDetail()) + addenda05 := mockAddenda05() + addenda05.TypeCode = "63" + mockBatch.GetEntries()[0].AddAddenda05(addenda05) + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchATXInvalidAddenda tests validating Addendum must be Addenda05 with type code 05 +func TestBatchATXInvalidAddenda(t *testing.T) { + testBatchATXInvalidAddenda(t) +} + +// BenchmarkBatchATXInvalidAddenda benchmarks validating Addendum must be Addenda05 with type code 05 +func BenchmarkBatchATXInvalidAddenda(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXInvalidAddenda(b) + } +} + +// testBatchATXInvalidBuild validates an invalid batch build +func testBatchATXInvalidBuild(t testing.TB) { + mockBatch := mockBatchATX() + mockBatch.GetHeader().ServiceClassCode = 3 + err := mockBatch.Create() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchATXInvalidBuild tests validating an invalid batch build +func TestBatchATXInvalidBuild(t *testing.T) { + testBatchATXInvalidBuild(t) +} + +// BenchmarkBatchATXInvalidBuild benchmarks validating an invalid batch build +func BenchmarkBatchATXInvalidBuild(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXInvalidBuild(b) + } +} + +// testBatchATXAddenda10000 validates error for 10000 Addenda +func testBatchATXAddenda10000(t testing.TB) { + + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = ATX + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "231380104" + bh.CompanyEntryDescription = "ACH ATX" + bh.ODFIIdentification = "23138010" + + entry := NewEntryDetail() + entry.TransactionCode = CheckingZeroDollarRemittanceCredit + entry.SetRDFI("121042882") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.SetOriginalTraceNumber("121042880000001") + entry.SetCATXAddendaRecords(9999) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + + mockBatch := NewBatchATX(bh) + mockBatch.AddEntry(entry) + mockBatch.GetEntries()[0].AddendaRecordIndicator = 1 + + for i := 0; i < 10000; i++ { + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + + } + err := mockBatch.Create() + if !base.Match(err, NewErrBatchAddendaCount(10000, 9999)) { + t.Errorf("%T: %s", err, err) + } + +} + +// TestBatchATXAddenda10000 tests validating error for 10000 Addenda +func TestBatchATXAddenda10000(t *testing.T) { + testBatchATXAddenda10000(t) +} + +// BenchmarkBatchATXAddenda10000 benchmarks validating error for 10000 Addenda +func BenchmarkBatchATXAddenda10000(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXAddenda10000(b) + } +} + +// testBatchATXAddendaRecords validates error for AddendaRecords not equal to addendum +func testBatchATXAddendaRecords(t testing.TB) { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = ATX + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "231380104" + bh.CompanyEntryDescription = "ACH ATX" + bh.ODFIIdentification = "23138010" + + entry := NewEntryDetail() + entry.TransactionCode = CheckingZeroDollarRemittanceCredit + entry.SetRDFI("121042882") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.SetOriginalTraceNumber("121042880000001") + entry.SetCATXAddendaRecords(565) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + entry.AddendaRecordIndicator = 1 + + mockBatch := NewBatchATX(bh) + mockBatch.AddEntry(entry) + + for i := 0; i < 565; i++ { + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + } + + err := mockBatch.Create() + // TODO: are we expecting there to be an error here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchATXAddendaRecords tests validating error for AddendaRecords not equal to addendum +func TestBatchATXAddendaRecords(t *testing.T) { + testBatchATXAddendaRecords(t) +} + +// BenchmarkBatchAddendaRecords benchmarks validating error for AddendaRecords not equal to addendum +func BenchmarkBatchATXAddendaRecords(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXAddendaRecords(b) + } +} + +// testBatchATXReceivingCompany validates ATXReceivingCompany +func testBatchATXReceivingCompany(t testing.TB) { + mockBatch := mockBatchATX() + //mockBatch.GetEntries()[0].SetCATXReceivingCompany("Receiver") + + if mockBatch.GetEntries()[0].CATXReceivingCompanyField() != "Receiver Company" { + t.Errorf("expected %v got %v", "Receiver Company", mockBatch.GetEntries()[0].CATXReceivingCompanyField()) + } +} + +// TestBatchATXReceivingCompany tests validating ATXReceivingCompany +func TestBatchATXReceivingCompany(t *testing.T) { + testBatchATXReceivingCompany(t) +} + +// BenchmarkBatchATXReceivingCompany benchmarks validating ATXReceivingCompany +func BenchmarkBatchATXReceivingCompany(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXReceivingCompany(b) + } +} + +// testBatchATXReserved validates ATXReservedField +func testBatchATXReserved(t testing.TB) { + mockBatch := mockBatchATX() + + if mockBatch.GetEntries()[0].CATXReservedField() != " " { + t.Errorf("expected %v got %v", " ", mockBatch.GetEntries()[0].CATXReservedField()) + } +} + +// TestBatchATXReserved tests validating ATXReservedField +func TestBatchATXReserved(t *testing.T) { + testBatchATXReserved(t) +} + +// BenchmarkBatchATXReserved benchmarks validating ATXReservedField +func BenchmarkBatchATXReserved(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXReserved(b) + } +} + +// testBatchATXZeroAddendaRecords validates zero addenda records +func testBatchATXZeroAddendaRecords(t testing.TB) { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = ATX + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "231380104" + bh.CompanyEntryDescription = "ACH ATX" + bh.ODFIIdentification = "23138010" + + entry := NewEntryDetail() + entry.TransactionCode = CheckingZeroDollarRemittanceCredit + entry.SetRDFI("121042882") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.SetOriginalTraceNumber("121042880000001") + entry.SetCATXAddendaRecords(1) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.AddAddenda05(mockAddenda05()) + entry.AddendaRecordIndicator = 1 + entry.Category = CategoryForward + + mockBatch := NewBatchATX(bh) + mockBatch.AddEntry(entry) + + err := mockBatch.Create() + // TODO: are we not expecting any errors here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchATXZeroAddendaRecords tests validating zero addenda records +func TestBatchATXZeroAddendaRecords(t *testing.T) { + testBatchATXZeroAddendaRecords(t) +} + +// BenchmarkBatchZeroAddendaRecords benchmarks validating zero addenda records +func BenchmarkBatchATXZeroAddendaRecords(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXZeroAddendaRecords(b) + } +} + +// testBatchATXTransactionCode validates TransactionCode +func testBatchATXTransactionCode(t testing.TB) { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = ATX + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "231380104" + bh.CompanyEntryDescription = "ACH ATX" + bh.ODFIIdentification = "23138010" + bh.OriginatorStatusCode = 2 + + entry := NewEntryDetail() + entry.TransactionCode = CheckingPrenoteCredit + entry.SetRDFI("121042882") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.SetOriginalTraceNumber("121042880000001") + entry.SetCATXAddendaRecords(1) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + + mockBatch := NewBatchATX(bh) + mockBatch.AddEntry(entry) + mockBatch.GetEntries()[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + + err := mockBatch.Create() + if !base.Match(err, ErrBatchTransactionCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchATXTransactionCode tests validating prenote addenda records +func TestBatchATXTransactionCode(t *testing.T) { + testBatchATXTransactionCode(t) +} + +// BenchmarkBatchATXTransactionCode benchmarks validating prenote addenda records +func BenchmarkBatchATXTransactionCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXTransactionCode(b) + } +} + +// TestBatchATXAmount validates Amount +func TestBatchATXAmount(t *testing.T) { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = ATX + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "231380104" + bh.CompanyEntryDescription = "ACH ATX" + bh.ODFIIdentification = "23138010" + bh.OriginatorStatusCode = 2 + + entry := NewEntryDetail() + entry.TransactionCode = CheckingPrenoteCredit + entry.SetRDFI("121042882") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.SetOriginalTraceNumber("121042880000001") + entry.SetCATXAddendaRecords(1) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + + mockBatch := NewBatchATX(bh) + mockBatch.AddEntry(entry) + mockBatch.GetEntries()[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + + err := mockBatch.Create() + if !base.Match(err, ErrBatchAmountNonZero) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchATXAddendum98 validates Addenda98 returns an error +func TestBatchATXAddendum98(t *testing.T) { + mockBatch := NewBatchATX(mockBatchATXHeader()) + mockBatch.AddEntry(mockATXEntryDetail()) + mockAddenda98 := mockAddenda98() + mockAddenda98.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchATXAddendum99 validates Addenda99 returns an error +func TestBatchATXAddendum99(t *testing.T) { + mockBatch := NewBatchATX(mockBatchATXHeader()) + mockBatch.AddEntry(mockATXEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchATXValidTranCodeForServiceClassCode validates a transactionCode based on ServiceClassCode +func TestBatchATXValidTranCodeForServiceClassCode(t *testing.T) { + mockBatch := mockBatchATX() + mockBatch.GetHeader().ServiceClassCode = DebitsOnly + err := mockBatch.Create() + if !base.Match(err, NewErrBatchServiceClassTranCode(DebitsOnly, 24)) { + t.Errorf("%T: %s", err, err) + } +} diff --git a/batchBOC.go b/batchBOC.go new file mode 100644 index 000000000..4d7a55ffe --- /dev/null +++ b/batchBOC.go @@ -0,0 +1,113 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +// BatchBOC holds the BatchHeader and BatchControl and all EntryDetail for BOC Entries. +// +// Back Office Conversion (BOC) A single entry debit initiated at the point of purchase +// or at a manned bill payment location to transfer funds through conversion to an +// ACH debit entry during back office processing. +// +// BOC allows retailers/billers, and ODFIs acting as Originators, +// to electronically convert checks received at the point-of-purchase as well as at a +// manned bill payment location into a single-entry ACH debit. The authorization to +// convert the check will be obtained through a notice at the checkout or manned bill +// payment location (e.g., loan payment at financial institution's teller window) and the +// receipt of the Receiver's check. The decision to process the check item as an ACH debit +// will be made in the “back office” instead of at the point-of-purchase. The customer's +// check will solely be used as a source document to obtain the routing number, account +// number and check serial number. +// +// Unlike ARC entries, BOC conversions require the customer to be present and a notice that +// checks may be converted to BOC ACH entries be posted. +type BatchBOC struct { + Batch +} + +// NewBatchBOC returns a *BatchBOC +func NewBatchBOC(bh *BatchHeader) *BatchBOC { + batch := new(BatchBOC) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} + +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the batch. +func (batch *BatchBOC) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + // Add configuration and type specific validation for this type. + + if batch.Header.StandardEntryClassCode != BOC { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, BOC) + } + + // BOC detail entries can only be a debit, ServiceClassCode must allow debits + switch batch.Header.ServiceClassCode { + case CreditsOnly: + return batch.Error("ServiceClassCode", ErrBatchServiceClassCode, batch.Header.ServiceClassCode) + } + + for _, entry := range batch.Entries { + // BOC detail entries must be a debit + if entry.CreditOrDebit() != "D" { + return batch.Error("TransactionCode", ErrBatchDebitOnly, entry.TransactionCode) + } + + // Amount must be 25,000 or less + if entry.Amount > 2500000 { + return batch.Error("Amount", NewErrBatchAmount(entry.Amount, 2500000)) + } + + // CheckSerialNumber underlying IdentificationNumber, must be defined + if entry.IdentificationNumber == "" { + return batch.Error("CheckSerialNumber", ErrBatchCheckSerialNumber) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + } + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchBOC) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + // Additional steps specific to batch type + // ... + + return batch.Validate() +} diff --git a/batchBOC_test.go b/batchBOC_test.go new file mode 100644 index 000000000..a197672ea --- /dev/null +++ b/batchBOC_test.go @@ -0,0 +1,413 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockBatchBOCHeader creates a BatchBOC BatchHeader +func mockBatchBOCHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = BOC + bh.CompanyName = "Company Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = BOC + bh.ODFIIdentification = "12104288" + return bh +} + +// mockBOCEntryDetail creates a BatchBOC EntryDetail +func mockBOCEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.SetCheckSerialNumber("123456789") + entry.SetReceivingCompany("ABC Company") + entry.SetTraceNumber(mockBatchBOCHeader().ODFIIdentification, 1) + entry.Category = CategoryForward + return entry +} + +// mockBatchBOC creates a BatchBOC +func mockBatchBOC() *BatchBOC { + mockBatch := NewBatchBOC(mockBatchBOCHeader()) + mockBatch.AddEntry(mockBOCEntryDetail()) + if err := mockBatch.Create(); err != nil { + panic(err) + } + return mockBatch +} + +// mockBatchBOCHeaderCredit creates a BatchBOC BatchHeader +func mockBatchBOCHeaderCredit() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = BOC + bh.CompanyName = "Company Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "REDEPCHECK" + bh.ODFIIdentification = "12104288" + return bh +} + +// mockBOCEntryDetailCredit creates a BatchBOC EntryDetail with a credit +func mockBOCEntryDetailCredit() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.SetCheckSerialNumber("123456789") + entry.SetReceivingCompany("ABC Company") + entry.SetTraceNumber(mockBatchBOCHeader().ODFIIdentification, 1) + entry.Category = CategoryForward + return entry +} + +// mockBatchBOCCredit creates a BatchBOC with a Credit entry +func mockBatchBOCCredit() *BatchBOC { + mockBatch := NewBatchBOC(mockBatchBOCHeaderCredit()) + mockBatch.AddEntry(mockBOCEntryDetailCredit()) + return mockBatch +} + +// testBatchBOCHeader creates a BatchBOC BatchHeader +func testBatchBOCHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchBOCHeader()) + err, ok := batch.(*BatchBOC) + if !ok { + t.Errorf("Expecting BatchBOC got %T", err) + } +} + +// TestBatchBOCHeader tests validating BatchBOC BatchHeader +func TestBatchBOCHeader(t *testing.T) { + testBatchBOCHeader(t) +} + +// BenchmarkBatchBOCHeader benchmarks validating BatchBOC BatchHeader +func BenchmarkBatchBOCHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchBOCHeader(b) + } +} + +// testBatchBOCCreate validates BatchBOC create +func testBatchBOCCreate(t testing.TB) { + mockBatch := mockBatchBOC() + if err := mockBatch.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchBOCCreate tests validating BatchBOC create +func TestBatchBOCCreate(t *testing.T) { + testBatchBOCCreate(t) +} + +// BenchmarkBatchBOCCreate benchmarks validating BatchBOC create +func BenchmarkBatchBOCCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchBOCCreate(b) + } +} + +// testBatchBOCStandardEntryClassCode validates BatchBOC create for an invalid StandardEntryClassCode +func testBatchBOCStandardEntryClassCode(t testing.TB) { + mockBatch := mockBatchBOC() + mockBatch.Header.StandardEntryClassCode = WEB + err := mockBatch.Create() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchBOCStandardEntryClassCode tests validating BatchBOC create for an invalid StandardEntryClassCode +func TestBatchBOCStandardEntryClassCode(t *testing.T) { + testBatchBOCStandardEntryClassCode(t) +} + +// BenchmarkBatchBOCStandardEntryClassCode benchmarks validating BatchBOC create for an invalid StandardEntryClassCode +func BenchmarkBatchBOCStandardEntryClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchBOCStandardEntryClassCode(b) + } +} + +// testBatchBOCServiceClassCodeEquality validates service class code equality +func testBatchBOCServiceClassCodeEquality(t testing.TB) { + mockBatch := mockBatchBOC() + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(220, MixedDebitsAndCredits)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchBOCServiceClassCodeEquality tests validating service class code equality +func TestBatchBOCServiceClassCodeEquality(t *testing.T) { + testBatchBOCServiceClassCodeEquality(t) +} + +// BenchmarkBatchBOCServiceClassCodeEquality benchmarks validating service class code equality +func BenchmarkBatchBOCServiceClassCodeEquality(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchBOCServiceClassCodeEquality(b) + } +} + +// testBatchBOCMixedCreditsAndDebits validates BatchBOC create for an invalid MixedCreditsAndDebits +func testBatchBOCMixedCreditsAndDebits(t testing.TB) { + mockBatch := mockBatchBOC() + mockBatch.Header.ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(MixedDebitsAndCredits, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchBOCMixedCreditsAndDebits tests validating BatchBOC create for an invalid MixedCreditsAndDebits +func TestBatchBOCMixedCreditsAndDebits(t *testing.T) { + testBatchBOCMixedCreditsAndDebits(t) +} + +// BenchmarkBatchBOCMixedCreditsAndDebits benchmarks validating BatchBOC create for an invalid MixedCreditsAndDebits +func BenchmarkBatchBOCMixedCreditsAndDebits(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchBOCMixedCreditsAndDebits(b) + } +} + +// testBatchBOCCreditsOnly validates BatchBOC create for an invalid CreditsOnly +func testBatchBOCCreditsOnly(t testing.TB) { + mockBatch := mockBatchBOC() + mockBatch.Header.ServiceClassCode = CreditsOnly + + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(CreditsOnly, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchBOCCreditsOnly tests validating BatchBOC create for an invalid CreditsOnly +func TestBatchBOCCreditsOnly(t *testing.T) { + testBatchBOCCreditsOnly(t) +} + +// BenchmarkBatchBOCCreditsOnly benchmarks validating BatchBOC create for an invalid CreditsOnly +func BenchmarkBatchBOCCreditsOnly(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchBOCCreditsOnly(b) + } +} + +// testBatchBOCAutomatedAccountingAdvices validates BatchBOC create for an invalid AutomatedAccountingAdvices +func testBatchBOCAutomatedAccountingAdvices(t testing.TB) { + mockBatch := mockBatchBOC() + mockBatch.Header.ServiceClassCode = AutomatedAccountingAdvices + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(AutomatedAccountingAdvices, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchBOCAutomatedAccountingAdvices tests validating BatchBOC create for an invalid AutomatedAccountingAdvices +func TestBatchBOCAutomatedAccountingAdvices(t *testing.T) { + testBatchBOCAutomatedAccountingAdvices(t) +} + +// BenchmarkBatchBOCAutomatedAccountingAdvices benchmarks validating BatchBOC create for an invalid AutomatedAccountingAdvices +func BenchmarkBatchBOCAutomatedAccountingAdvices(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchBOCAutomatedAccountingAdvices(b) + } +} + +// testBatchBOCAmount validates BatchBOC create for an invalid Amount +func testBatchBOCAmount(t testing.TB) { + mockBatch := mockBatchBOC() + mockBatch.Entries[0].Amount = 2500001 + err := mockBatch.Create() + if !base.Match(err, NewErrBatchAmount(2500001, 2500000)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchBOCAmount validates BatchBOC create for an invalid Amount +func TestBatchBOCAmount(t *testing.T) { + testBatchBOCAmount(t) +} + +// BenchmarkBatchBOCAmount validates BatchBOC create for an invalid Amount +func BenchmarkBatchBOCAmount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchBOCAmount(b) + } +} + +// testBatchBOCCheckSerialNumber validates BatchBOC CheckSerialNumber / IdentificationNumber is a mandatory field +func testBatchBOCCheckSerialNumber(t testing.TB) { + mockBatch := mockBatchBOC() + // modify CheckSerialNumber / IdentificationNumber to empty string + mockBatch.GetEntries()[0].SetCheckSerialNumber("") + err := mockBatch.Validate() + if !base.Match(err, ErrBatchCheckSerialNumber) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchBOCCheckSerialNumber tests validating BatchBOC CheckSerialNumber / IdentificationNumber is a mandatory field +func TestBatchBOCCheckSerialNumber(t *testing.T) { + testBatchBOCCheckSerialNumber(t) +} + +// BenchmarkBatchBOCCheckSerialNumber benchmarks validating BatchBOC +// CheckSerialNumber / IdentificationNumber is a mandatory field +func BenchmarkBatchBOCCheckSerialNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchBOCCheckSerialNumber(b) + } +} + +// testBatchBOCTransactionCode validates BatchBOC TransactionCode is not a credit +func testBatchBOCTransactionCode(t testing.TB) { + mockBatch := mockBatchBOCCredit() + err := mockBatch.Create() + if !base.Match(err, ErrBatchDebitOnly) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchBOCTransactionCode tests validating BatchBOC TransactionCode is not a credit +func TestBatchBOCTransactionCode(t *testing.T) { + testBatchBOCTransactionCode(t) +} + +// BenchmarkBatchBOCTransactionCode benchmarks validating BatchBOC TransactionCode is not a credit +func BenchmarkBatchBOCTransactionCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchBOCTransactionCode(b) + } +} + +// testBatchBOCAddenda05 validates BatchBOC Addenda count +func testBatchBOCAddenda05(t testing.TB) { + mockBatch := mockBatchBOC() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchBOCAddenda05 tests validating BatchBOC Addenda count +func TestBatchBOCAddenda05(t *testing.T) { + testBatchBOCAddenda05(t) +} + +// BenchmarkBatchBOCAddenda05 benchmarks validating BatchBOC Addenda count +func BenchmarkBatchBOCAddenda05(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchBOCAddenda05(b) + } +} + +// testBatchBOCInvalidBuild validates an invalid batch build +func testBatchBOCInvalidBuild(t testing.TB) { + mockBatch := mockBatchBOC() + mockBatch.GetHeader().ServiceClassCode = 3 + err := mockBatch.Create() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchBOCInvalidBuild tests validating an invalid batch build +func TestBatchBOCInvalidBuild(t *testing.T) { + testBatchBOCInvalidBuild(t) +} + +// BenchmarkBatchBOCInvalidBuild benchmarks validating an invalid batch build +func BenchmarkBatchBOCInvalidBuild(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchBOCInvalidBuild(b) + } +} + +// TestBatchBOCAddendum98 validates Addenda98 returns an error +func TestBatchBOCAddendum98(t *testing.T) { + mockBatch := NewBatchBOC(mockBatchBOCHeader()) + mockBatch.AddEntry(mockBOCEntryDetail()) + mockAddenda98 := mockAddenda98() + mockAddenda98.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchBOCAddendum99 validates Addenda99 returns an error +func TestBatchBOCAddendum99(t *testing.T) { + mockBatch := NewBatchBOC(mockBatchBOCHeader()) + mockBatch.AddEntry(mockBOCEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// testBatchBOCCreditsOnly validates BatchBOC create for Valid SCC MixedDebitsAndCredits with transCode Debit +func testBatchBOCMixedDebitsAndCreditsWithCreditTransCode(t testing.TB) { + mockBatch := mockBatchBOC() + mockBatch.Header.ServiceClassCode = MixedDebitsAndCredits + mockBatch.Batch.Control.ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchBOCCreditsOnly tests validating BatchBOC create for Valid SCC MixedDebitsAndCredits with transCode Debit +func TestMixedDebitsAndCreditsWithCreditTransCode(t *testing.T) { + testBatchBOCMixedDebitsAndCreditsWithCreditTransCode(t) +} diff --git a/batchCCD.go b/batchCCD.go index 26a4f2e9c..52a7e646e 100644 --- a/batchCCD.go +++ b/batchCCD.go @@ -1,30 +1,35 @@ -package ach +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. -import ( - "fmt" -) +package ach -// BatchCCD creates a batch file that handles SEC payment type CCD amd CCD+. +// BatchCCD is a batch file that handles SEC payment type CCD and CCD+. // Corporate credit or debit. Identifies an Entry initiated by an Organization to transfer funds to or from an account of that Organization or another Organization. // For commercial accounts only. type BatchCCD struct { - batch + Batch } // NewBatchCCD returns a *BatchCCD -func NewBatchCCD(params ...BatchParam) *BatchCCD { +func NewBatchCCD(bh *BatchHeader) *BatchCCD { batch := new(BatchCCD) batch.SetControl(NewBatchControl()) - - if len(params) > 0 { - bh := NewBatchHeader(params[0]) - bh.StandardEntryClassCode = "CCD" - batch.SetHeader(bh) - return batch - } - bh := NewBatchHeader() - bh.StandardEntryClassCode = "CCD" batch.SetHeader(bh) + batch.SetID(bh.ID) return batch } @@ -34,33 +39,39 @@ func (batch *BatchCCD) Validate() error { if err := batch.verify(); err != nil { return err } - // Add configuration based validation for this type. - // Web can have up to one addenda per entry record - if err := batch.isAddendaCount(1); err != nil { - return err - } - if err := batch.isTypeCode("05"); err != nil { - return err - } - // Add type specific validation. - if batch.header.StandardEntryClassCode != "CCD" { - msg := fmt.Sprintf(msgBatchSECType, batch.header.StandardEntryClassCode, "CCD") - return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "StandardEntryClassCode", Msg: msg} + // Add configuration and type specific validation. + if batch.Header.StandardEntryClassCode != CCD { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, CCD) } + for _, entry := range batch.Entries { + // CCD can have up to one Addenda05 record, + if len(entry.Addenda05) > 1 { + return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 1)) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + } return nil } -// Create builds the batch sequence numbers and batch control. Additional creation +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. func (batch *BatchCCD) Create() error { // generates sequence numbers and batch control if err := batch.build(); err != nil { return err } - if err := batch.Validate(); err != nil { - return err - } - return nil + return batch.Validate() } diff --git a/batchCCD_test.go b/batchCCD_test.go index b3292e9e8..4ade01119 100644 --- a/batchCCD_test.go +++ b/batchCCD_test.go @@ -1,151 +1,299 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + package ach import ( "testing" + + "github.com/moov-io/base" ) +// mockBatchCCDHeader creates a CCD batch header func mockBatchCCDHeader() *BatchHeader { bh := NewBatchHeader() - bh.ServiceClassCode = 220 - bh.StandardEntryClassCode = "CCD" + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = CCD bh.CompanyName = "Your Company, inc" - bh.CompanyIdentification = "123456789" + bh.CompanyIdentification = "121042882" bh.CompanyEntryDescription = "Vndr Pay" - bh.ODFIIdentification = 6200001 + bh.ODFIIdentification = "121042882" return bh } +// mockCCDEntryDetail creates a CCD entry detail func mockCCDEntryDetail() *EntryDetail { entry := NewEntryDetail() - entry.TransactionCode = 27 - entry.SetRDFI(9101298) + entry.TransactionCode = CheckingDebit + entry.SetRDFI("231380104") entry.DFIAccountNumber = "744-5678-99" entry.Amount = 5000000 entry.IdentificationNumber = "location #23" entry.SetReceivingCompany("Best Co. #23") - entry.TraceNumber = 123456789 + entry.SetTraceNumber(mockBatchCCDHeader().ODFIIdentification, 1) entry.DiscretionaryData = "S" return entry } +// mockBatchCCD creates a CCD batch func mockBatchCCD() *BatchCCD { - mockBatch := NewBatchCCD() - mockBatch.SetHeader(mockBatchCCDHeader()) + mockBatch := NewBatchCCD(mockBatchCCDHeader()) mockBatch.AddEntry(mockCCDEntryDetail()) - mockBatch.GetEntries()[0].AddAddenda(mockAddenda()) + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + mockBatch.Entries[0].AddendaRecordIndicator = 1 if err := mockBatch.Create(); err != nil { panic(err) } return mockBatch } -// A Batch CCD can only have one addendum per entry detail -func TestBatchCCDAddendumCount(t *testing.T) { +// testBatchCCDHeader creates a CCD batch header +func testBatchCCDHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchCCDHeader()) + _, ok := batch.(*BatchCCD) + if !ok { + t.Error("Expecting BatchCCD") + } +} + +// TestBatchCCDHeader tests creating a CCD batch header +func TestBatchCCDHeader(t *testing.T) { + testBatchCCDHeader(t) +} + +// BenchmarkBatchCCDHeader benchmark creating a CCD batch header +func BenchmarkBatchCCDHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCCDHeader(b) + } +} + +// testBatchCCDAddendumCount batch control CCD can only have one addendum per entry detail +func testBatchCCDAddendumCount(t testing.TB) { mockBatch := mockBatchCCD() // Adding a second addenda to the mock entry - mockBatch.GetEntries()[0].AddAddenda(mockAddenda()) - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "EntryAddendaCount" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchCalculatedControlEquality(3, 2)) { + t.Errorf("%T: %s", err, err) } } -// receiving company / Individual name is a mandatory field -func TestBatchCCDReceivingCompanyName(t *testing.T) { +// TestBatchCCDAddendumCount tests batch control CCD can only have one addendum per entry detail +func TestBatchCCDAddendumCount(t *testing.T) { + testBatchCCDAddendumCount(t) +} + +// BenchmarkBatchCCDAddendumCount benchmarks batch control CCD can only have one addendum per entry detail +func BenchmarkBatchCCDAddendumCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCCDAddendumCount(b) + } +} + +// TestBatchCCDAddendum98 validates Addenda98 returns an error +func TestBatchCCDAddendum98(t *testing.T) { + mockBatch := NewBatchCCD(mockBatchCCDHeader()) + mockBatch.AddEntry(mockCCDEntryDetail()) + mockAddenda98 := mockAddenda98() + mockAddenda98.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCCDAddendum99 validates Addenda99 returns an error +func TestBatchCCDAddendum99(t *testing.T) { + mockBatch := NewBatchCCD(mockBatchCCDHeader()) + mockBatch.AddEntry(mockCCDEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// testBatchCCDReceivingCompanyName validates Receiving company / Individual name is a mandatory field +func testBatchCCDReceivingCompanyName(t testing.TB) { mockBatch := mockBatchCCD() // modify the Individual name / receiving company to nothing mockBatch.GetEntries()[0].SetReceivingCompany("") - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "IndividualName" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + err := mockBatch.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) } } -// verify addenda type code is 05 +// TestBatchCCDReceivingCompanyName tests validating receiving company / Individual name is a mandatory field +func TestBatchCCDReceivingCompanyName(t *testing.T) { + testBatchCCDReceivingCompanyName(t) +} + +// BenchmarkBatchCCDReceivingCompanyName benchmarks validating receiving company / Individual name is a mandatory field +func BenchmarkBatchCCDReceivingCompanyName(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCCDReceivingCompanyName(b) + } +} + +// testBatchCCDAddendaTypeCode validates addenda type code is 05 +func testBatchCCDAddendaTypeCode(t testing.TB) { + mockBatch := mockBatchCCD() + mockBatch.GetEntries()[0].Addenda05[0].TypeCode = "07" + err := mockBatch.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCCDAddendaTypeCode tests validating addenda type code is 05 func TestBatchCCDAddendaTypeCode(t *testing.T) { + testBatchCCDAddendaTypeCode(t) +} + +// BenchmarkBatchCCDAddendaTypeCod benchmarks validating addenda type code is 05 +func BenchmarkBatchCCDAddendaTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCCDAddendaTypeCode(b) + } +} + +// testBatchCCDSEC validates that the standard entry class code is CCD for batchCCD +func testBatchCCDSEC(t testing.TB) { mockBatch := mockBatchCCD() - mockBatch.GetEntries()[0].Addendum[0].TypeCode = "07" - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "TypeCode" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + mockBatch.Header.StandardEntryClassCode = RCK + err := mockBatch.Validate() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) } } -// verify that the standard entry class code is CCD for batchCCD +// TestBatchCCDSEC tests validating that the standard entry class code is CCD for batchCCD func TestBatchCCDSEC(t *testing.T) { + testBatchCCDSEC(t) +} + +// BenchmarkBatchCCDSEC benchmarks validating that the standard entry class code is CCD for batch CCD +func BenchmarkBatchCCDSEC(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCCDSEC(b) + } +} + +// testBatchCCDAddendaCount validates batch CCD addenda count +func testBatchCCDAddendaCount(t testing.TB) { mockBatch := mockBatchCCD() - mockBatch.header.StandardEntryClassCode = "RCK" - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "StandardEntryClassCode" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + err := mockBatch.Create() + if !base.Match(err, NewErrBatchAddendaCount(0, 1)) { + t.Errorf("%T: %s", err, err) } } +// TestBatchCCDAddendaCount tests validating batch CCD addenda count func TestBatchCCDAddendaCount(t *testing.T) { - mockBatch := mockBatchCCD() - mockBatch.GetEntries()[0].AddAddenda(mockAddenda()) - mockBatch.Create() - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "AddendaCount" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + testBatchCCDAddendaCount(t) +} + +// BenchmarkBatchCCDAddendaCount benchmarks validating batch CCD addenda count +func BenchmarkBatchCCDAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCCDAddendaCount(b) } } -func TestBatchCCDCreate(t *testing.T) { +// testBatchCCDCreate creates a batch CCD +func testBatchCCDCreate(t testing.TB) { mockBatch := mockBatchCCD() // Batch Header information is required to Create a batch. mockBatch.GetHeader().ServiceClassCode = 0 - mockBatch.Create() - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "ServiceClassCode" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + err := mockBatch.Create() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) } } -func TestBatchCCDParam(t *testing.T) { +// TestBatchCCDCreate Test creating a batch CCD +func TestBatchCCDCreate(t *testing.T) { + testBatchCCDCreate(t) +} - batch, _ := NewBatch(BatchParam{ - ServiceClassCode: "220", - CompanyName: "Your Company, inc", - StandardEntryClass: "CCD", - CompanyIdentification: "123456789", - CompanyEntryDescription: "Vndr Pay", - CompanyDescriptiveDate: "Oct 23", - ODFIIdentification: "123456789"}) +// BenchmarkBatchCCDCreate benchmark creating a batch CCD +func BenchmarkBatchCCDCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCCDCreate(b) + } +} - _, ok := batch.(*BatchCCD) - if !ok { - t.Error("Expecting BachCCD") +// testBatchCCDReceivingCompanyField validates CCDReceivingCompanyField +// underlying IndividualName +func testBatchCCDReceivingCompanyField(t testing.TB) { + mockBatch := mockBatchCCD() + ts := mockBatch.Entries[0].ReceivingCompanyField() + if ts != "Best Co. #23 " { + t.Error("Receiving Company Field is invalid") } +} + +// TestBatchCCDReceivingCompanyField tests validating CCDReceivingCompanyField +// underlying IndividualName +func TestBatchCCDReceivingCompanyFieldField(t *testing.T) { + testBatchCCDReceivingCompanyField(t) +} +// BenchmarkBatchCCDReceivingCompanyField benchmarks validating CCDReceivingCompanyField +// underlying IndividualName +func BenchmarkBatchCCDReceivingCompanyField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCCDReceivingCompanyField(b) + } +} + +// TestBatchCCDValidTranCodeForServiceClassCode validates a transactionCode based on ServiceClassCode +func TestBatchCCDValidTranCodeForServiceClassCode(t *testing.T) { + mockBatch := mockBatchCCD() + mockBatch.GetHeader().ServiceClassCode = CreditsOnly + err := mockBatch.Create() + if !base.Match(err, NewErrBatchServiceClassTranCode(CreditsOnly, 27)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCCDAddenda02 validates BatchCCD cannot have Addenda02 +func TestBatchCCDAddenda02(t *testing.T) { + mockBatch := mockBatchCCD() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02() + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } } diff --git a/batchCIE.go b/batchCIE.go new file mode 100644 index 000000000..6ac52ac15 --- /dev/null +++ b/batchCIE.go @@ -0,0 +1,99 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +// BatchCIE holds the BatchHeader and BatchControl and all EntryDetail for CIE Entries. +// +// Customer-Initiated Entry (or CIE entry) is a credit entry initiated on behalf of, +// and upon the instruction of, a consumer to transfer funds to a non-consumer Receiver. +// CIE entries are usually transmitted to a company for payment of funds that the consumer +// owes to that company and are initiated by the consumer through some type of online +// banking product or bill payment service provider. With CIEs, funds owed by the consumer +// are “pushed” to the biller in the form of an ACH credit, as opposed to the biller's use of +// a debit application (e.g., PPD, WEB) to “pull” the funds from a customer's account. +type BatchCIE struct { + Batch +} + +// NewBatchCIE returns a *BatchCIE +func NewBatchCIE(bh *BatchHeader) *BatchCIE { + batch := new(BatchCIE) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} + +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the batch. +func (batch *BatchCIE) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + // Add configuration based validation for this type. + + // Add type specific validation. + + if batch.Header.StandardEntryClassCode != CIE { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, CIE) + } + + // CIE detail entries can only be a credit, ServiceClassCode must allow credit + switch batch.Header.ServiceClassCode { + case DebitsOnly: + return batch.Error("ServiceClassCode", ErrBatchServiceClassCode, batch.Header.ServiceClassCode) + } + + for _, entry := range batch.Entries { + // CIE detail entries must be a debit + if entry.CreditOrDebit() != "C" { + return batch.Error("TransactionCode", ErrBatchDebitOnly, entry.TransactionCode) + } + // CIE must have a maximum of one Addenda05 record + if len(entry.Addenda05) > 1 { + return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 1)) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + } + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchCIE) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + // Additional steps specific to batch type + // ... + return batch.Validate() +} diff --git a/batchCIE_test.go b/batchCIE_test.go new file mode 100644 index 000000000..0a4f1d2a9 --- /dev/null +++ b/batchCIE_test.go @@ -0,0 +1,455 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockBatchCIEHeader creates a BatchCIE BatchHeader +func mockBatchCIEHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = CIE + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH CIE" + bh.ODFIIdentification = "12104288" + return bh +} + +// mockCIEEntryDetail creates a BatchCIE EntryDetail +func mockCIEEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.IdentificationNumber = "45689033" + entry.IndividualName = "Receiver Account Name" + entry.SetTraceNumber(mockBatchCIEHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + return entry +} + +// mockBatchCIE creates a BatchCIE +func mockBatchCIE() *BatchCIE { + mockBatch := NewBatchCIE(mockBatchCIEHeader()) + mockBatch.AddEntry(mockCIEEntryDetail()) + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + mockBatch.Entries[0].AddendaRecordIndicator = 1 + if err := mockBatch.Create(); err != nil { + panic(err) + } + return mockBatch +} + +// testBatchCIEHeader creates a BatchCIE BatchHeader +func testBatchCIEHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchCIEHeader()) + err, ok := batch.(*BatchCIE) + if !ok { + t.Errorf("Expecting BatchCIE got %T", err) + } +} + +// TestBatchCIEHeader tests validating BatchCIE BatchHeader +func TestBatchCIEHeader(t *testing.T) { + testBatchCIEHeader(t) +} + +// BenchmarkBatchCIEHeader benchmarks validating BatchCIE BatchHeader +func BenchmarkBatchCIEHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCIEHeader(b) + } +} + +// testBatchCIECreate validates BatchCIE create +func testBatchCIECreate(t testing.TB) { + mockBatch := mockBatchCIE() + if err := mockBatch.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCIECreate tests validating BatchCIE create +func TestBatchCIECreate(t *testing.T) { + testBatchCIECreate(t) +} + +// BenchmarkBatchCIECreate benchmarks validating BatchCIE create +func BenchmarkBatchCIECreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCIECreate(b) + } +} + +// testBatchCIEStandardEntryClassCode validates BatchCIE create for an invalid StandardEntryClassCode +func testBatchCIEStandardEntryClassCode(t testing.TB) { + mockBatch := mockBatchCIE() + mockBatch.Header.StandardEntryClassCode = WEB + err := mockBatch.Create() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCIEStandardEntryClassCode tests validating BatchCIE create for an invalid StandardEntryClassCode +func TestBatchCIEStandardEntryClassCode(t *testing.T) { + testBatchCIEStandardEntryClassCode(t) +} + +// BenchmarkBatchCIEStandardEntryClassCode benchmarks validating BatchCIE create for an invalid StandardEntryClassCode +func BenchmarkBatchCIEStandardEntryClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCIEStandardEntryClassCode(b) + } +} + +// testBatchCIEServiceClassCodeEquality validates service class code equality +func testBatchCIEServiceClassCodeEquality(t testing.TB) { + mockBatch := mockBatchCIE() + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(220, MixedDebitsAndCredits)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCIEServiceClassCodeEquality tests validating service class code equality +func TestBatchCIEServiceClassCodeEquality(t *testing.T) { + testBatchCIEServiceClassCodeEquality(t) +} + +// BenchmarkBatchCIEServiceClassCodeEquality benchmarks validating service class code equality +func BenchmarkBatchCIEServiceClassCodeEquality(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCIEServiceClassCodeEquality(b) + } +} + +// testBatchCIEMixedCreditsAndDebits validates BatchCIE create for an invalid MixedCreditsAndDebits +func testBatchCIEMixedCreditsAndDebits(t testing.TB) { + mockBatch := mockBatchCIE() + mockBatch.Header.ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(MixedDebitsAndCredits, 220)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCIEMixedCreditsAndDebits tests validating BatchCIE create for an invalid MixedCreditsAndDebits +func TestBatchCIEMixedCreditsAndDebits(t *testing.T) { + testBatchCIEMixedCreditsAndDebits(t) +} + +// BenchmarkBatchCIEMixedCreditsAndDebits benchmarks validating BatchCIE create for an invalid MixedCreditsAndDebits +func BenchmarkBatchCIEMixedCreditsAndDebits(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCIEMixedCreditsAndDebits(b) + } +} + +// testBatchCIEDebitsOnly validates BatchCIE create for an invalid DebitsOnly +func testBatchCIEDebitsOnly(t testing.TB) { + mockBatch := mockBatchCIE() + mockBatch.Header.ServiceClassCode = DebitsOnly + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(DebitsOnly, 220)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCIEDebitsOnly tests validating BatchCIE create for an invalid DebitsOnly +func TestBatchCIEDebitsOnly(t *testing.T) { + testBatchCIEDebitsOnly(t) +} + +// BenchmarkBatchCIEDebitsOnly benchmarks validating BatchCIE create for an invalid DebitsOnly +func BenchmarkBatchCIEDebitsOnly(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCIEDebitsOnly(b) + } +} + +// testBatchCIEAutomatedAccountingAdvices validates BatchCIE create for an invalid AutomatedAccountingAdvices +func testBatchCIEAutomatedAccountingAdvices(t testing.TB) { + mockBatch := mockBatchCIE() + mockBatch.Header.ServiceClassCode = AutomatedAccountingAdvices + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(AutomatedAccountingAdvices, 220)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCIEAutomatedAccountingAdvices tests validating BatchCIE create for an invalid AutomatedAccountingAdvices +func TestBatchCIEAutomatedAccountingAdvices(t *testing.T) { + testBatchCIEAutomatedAccountingAdvices(t) +} + +// BenchmarkBatchCIEAutomatedAccountingAdvices benchmarks validating BatchCIE create for an invalid AutomatedAccountingAdvices +func BenchmarkBatchCIEAutomatedAccountingAdvices(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCIEAutomatedAccountingAdvices(b) + } +} + +// testBatchCIETransactionCode validates BatchCIE TransactionCode is not a debit +func testBatchCIETransactionCode(t testing.TB) { + mockBatch := mockBatchCIE() + mockBatch.GetEntries()[0].TransactionCode = CheckingDebit + err := mockBatch.Create() + if !base.Match(err, ErrBatchDebitOnly) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCIETransactionCode tests validating BatchCIE TransactionCode is not a credit +func TestBatchCIETransactionCode(t *testing.T) { + testBatchCIETransactionCode(t) +} + +// BenchmarkBatchCIETransactionCode benchmarks validating BatchCIE TransactionCode is not a credit +func BenchmarkBatchCIETransactionCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCIETransactionCode(b) + } +} + +// testBatchCIEAddendaCount validates BatchCIE Addendum count of 2 +func testBatchCIEAddendaCount(t testing.TB) { + mockBatch := mockBatchCIE() + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + err := mockBatch.Create() + if !base.Match(err, NewErrBatchAddendaCount(2, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCIEAddendaCount tests validating BatchCIE Addendum count of 2 +func TestBatchCIEAddendaCount(t *testing.T) { + testBatchCIEAddendaCount(t) +} + +// BenchmarkBatchCIEAddendaCount benchmarks validating BatchCIE Addendum count of 2 +func BenchmarkBatchCIEAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCIEAddendaCount(b) + } +} + +// testBatchCIEAddendaCountZero validates Addendum count of 0 +func testBatchCIEAddendaCountZero(t testing.TB) { + mockBatch := NewBatchCIE(mockBatchCIEHeader()) + mockBatch.AddEntry(mockCIEEntryDetail()) + err := mockBatch.Create() + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCIEAddendaCountZero tests validating Addendum count of 0 +func TestBatchCIEAddendaCountZero(t *testing.T) { + testBatchCIEAddendaCountZero(t) +} + +// BenchmarkBatchCIEAddendaCountZero benchmarks validating Addendum count of 0 +func BenchmarkBatchCIEAddendaCountZero(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCIEAddendaCountZero(b) + } +} + +// testBatchCIEInvalidAddendum validates Addendum must be Addenda05 +func testBatchCIEInvalidAddendum(t testing.TB) { + mockBatch := NewBatchCIE(mockBatchCIEHeader()) + mockBatch.AddEntry(mockCIEEntryDetail()) + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCIEInvalidAddendum tests validating Addendum must be Addenda05 +func TestBatchCIEInvalidAddendum(t *testing.T) { + testBatchCIEInvalidAddendum(t) +} + +// BenchmarkBatchCIEInvalidAddendum benchmarks validating Addendum must be Addenda05 +func BenchmarkBatchCIEInvalidAddendum(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCIEInvalidAddendum(b) + } +} + +// testBatchCIEInvalidAddenda validates Addendum must be Addenda05 with type code 05 +func testBatchCIEInvalidAddenda(t testing.TB) { + mockBatch := NewBatchCIE(mockBatchCIEHeader()) + mockBatch.AddEntry(mockCIEEntryDetail()) + addenda05 := mockAddenda05() + addenda05.TypeCode = "63" + mockBatch.GetEntries()[0].AddAddenda05(addenda05) + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCIEInvalidAddenda tests validating Addendum must be Addenda05 with type code 05 +func TestBatchCIEInvalidAddenda(t *testing.T) { + testBatchCIEInvalidAddenda(t) +} + +// BenchmarkBatchCIEInvalidAddenda benchmarks validating Addendum must be Addenda05 with type code 05 +func BenchmarkBatchCIEInvalidAddenda(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCIEInvalidAddenda(b) + } +} + +// testBatchCIEInvalidBuild validates an invalid batch build +func testBatchCIEInvalidBuild(t testing.TB) { + mockBatch := mockBatchCIE() + mockBatch.GetHeader().ServiceClassCode = 3 + err := mockBatch.Create() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCIEInvalidBuild tests validating an invalid batch build +func TestBatchCIEInvalidBuild(t *testing.T) { + testBatchCIEInvalidBuild(t) +} + +// BenchmarkBatchCIEInvalidBuild benchmarks validating an invalid batch build +func BenchmarkBatchCIEInvalidBuild(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCIEInvalidBuild(b) + } +} + +// testBatchCIECardTransactionType validates BatchCIE create for an invalid CardTransactionType +func testBatchCIECardTransactionType(t testing.TB) { + mockBatch := mockBatchCIE() + mockBatch.GetEntries()[0].DiscretionaryData = "555" + err := mockBatch.Validate() + // TODO: are we not expecting any errors here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCIECardTransactionType tests validating BatchCIE create for an invalid CardTransactionType +func TestBatchCIECardTransactionType(t *testing.T) { + testBatchCIECardTransactionType(t) +} + +// BenchmarkBatchCIECardTransactionType benchmarks validating BatchCIE create for an invalid CardTransactionType +func BenchmarkBatchCIECardTransactionType(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCIECardTransactionType(b) + } +} + +// TestBatchCIEAddendum98 validates Addenda98 returns an error +func TestBatchCIEAddendum98(t *testing.T) { + mockBatch := NewBatchCIE(mockBatchCIEHeader()) + mockBatch.AddEntry(mockCIEEntryDetail()) + mockAddenda98 := mockAddenda98() + mockAddenda98.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCIEAddendum99 validates Addenda99 returns an error +func TestBatchCIEAddendum99(t *testing.T) { + mockBatch := NewBatchCIE(mockBatchCIEHeader()) + mockBatch.AddEntry(mockCIEEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCIEAddenda validates no more than 1 addenda record per entry detail record can exist +func TestBatchCIEAddenda(t *testing.T) { + mockBatch := mockBatchCIE() + // mock batch already has one addenda. Creating two addenda should error + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + err := mockBatch.Create() + if !base.Match(err, NewErrBatchAddendaCount(2, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCIEAddenda02 validates BatchCIE cannot have Addenda02 +func TestBatchCIEAddenda02(t *testing.T) { + mockBatch := mockBatchCIE() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02() + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// testBatchCIEServiceClassCodeEquality validates service class code MixedDebitsAndCredits +func testBatchCIEMixedDebitsAndCreditsServiceClassCode(t testing.TB) { + mockBatch := mockBatchCIE() + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + mockBatch.Header.ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCIEMixedDebitsAndCreditsServiceClassCode tests validating SCC MixedDebitsAndCredits +func TestBatchCIEMixedDebitsAndCreditsServiceClassCode(t *testing.T) { + testBatchCIEMixedDebitsAndCreditsServiceClassCode(t) +} diff --git a/batchCOR.go b/batchCOR.go index a7fdd02cc..fa98ffe85 100644 --- a/batchCOR.go +++ b/batchCOR.go @@ -1,31 +1,35 @@ -package ach +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. -import ( - "fmt" -) +package ach +// BatchCOR COR - Automated Notification of Change (NOC) or Refused Notification of Change +// This Standard Entry Class Code is used by an RDFI or ODFI when originating a Notification of Change or Refused Notification of Change in automated format. +// A Notification of Change may be created by an RDFI to notify the ODFI that a posted Entry or Prenotification Entry contains invalid or erroneous information and should be changed. type BatchCOR struct { - batch + Batch } -var ( - msgBatchWebPaymentType = "%v is not a valid payment type S (single entry) or R (recurring)" -) - -// NewBatchCOR returns a *BatchWEB -func NewBatchCOR(params ...BatchParam) *BatchCOR { +// NewBatchCOR returns a *BatchCOR +func NewBatchCOR(bh *BatchHeader) *BatchCOR { batch := new(BatchCOR) batch.SetControl(NewBatchControl()) - - if len(params) > 0 { - bh := NewBatchHeader(params[0]) - bh.StandardEntryClassCode = "COR" - batch.SetHeader(bh) - return batch - } - bh := NewBatchHeader() - bh.StandardEntryClassCode = "COR" batch.SetHeader(bh) + batch.SetID(bh.ID) return batch } @@ -36,32 +40,73 @@ func (batch *BatchCOR) Validate() error { return err } // Add configuration based validation for this type. - // Web can have up to one addenda per entry record - if err := batch.isAddendaCount(1); err != nil { - return err - } - if err := batch.isTypeCode("05"); err != nil { + // COR Addenda must be Addenda98 + if err := batch.isAddenda98(); err != nil { return err } // Add type specific validation. - if batch.header.StandardEntryClassCode != "COR" { - msg := fmt.Sprintf(msgBatchSECType, batch.header.StandardEntryClassCode, "COR") - return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "StandardEntryClassCode", Msg: msg} + if batch.Header.StandardEntryClassCode != COR { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, COR) + } + // The Amount field must be zero + // batch.verify calls batch.isBatchAmount which ensures the batch.Control values are accurate. + if batch.Control.TotalCreditEntryDollarAmount != 0 { + return batch.Error("TotalCreditEntryDollarAmount", ErrBatchAmountNonZero, batch.Control.TotalCreditEntryDollarAmount) + } + if batch.Control.TotalDebitEntryDollarAmount != 0 { + return batch.Error("TotalDebitEntryDollarAmount", ErrBatchAmountNonZero, batch.Control.TotalDebitEntryDollarAmount) } + for _, entry := range batch.Entries { + /* COR TransactionCode must be a Return or NOC transaction Code + Return/NOC + Credit: 21, 31, 41, 51 + Debit: 26, 36, 46, 56 + */ + switch entry.TransactionCode { + case + CheckingCredit, CheckingDebit, CheckingPrenoteCredit, CheckingPrenoteDebit, + CheckingZeroDollarRemittanceCredit, CheckingZeroDollarRemittanceDebit, + SavingsCredit, SavingsDebit, SavingsPrenoteCredit, SavingsPrenoteDebit, + SavingsZeroDollarRemittanceCredit, SavingsZeroDollarRemittanceDebit, + GLCredit, GLDebit, GLPrenoteCredit, GLPrenoteDebit, GLZeroDollarRemittanceCredit, + GLZeroDollarRemittanceDebit, LoanCredit, LoanDebit, LoanPrenoteCredit, + LoanZeroDollarRemittanceCredit: + return batch.Error("TransactionCode", ErrBatchTransactionCode, entry.TransactionCode) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + } return nil } -// Create builds the batch sequence numbers and batch control. Additional creation +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. func (batch *BatchCOR) Create() error { // generates sequence numbers and batch control if err := batch.build(); err != nil { return err } - if err := batch.Validate(); err != nil { - return err + return batch.Validate() +} + +// isAddenda98 verifies that a Addenda98 exists for each EntryDetail and is Validated +func (batch *BatchCOR) isAddenda98() error { + for _, entry := range batch.Entries { + if entry.Addenda98 == nil { + return batch.Error("Addenda98", ErrBatchCORAddenda) + } } return nil } diff --git a/batchCOR_test.go b/batchCOR_test.go new file mode 100644 index 000000000..8bb663f30 --- /dev/null +++ b/batchCOR_test.go @@ -0,0 +1,409 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "log" + "testing" + + "github.com/moov-io/base" +) + +// mockBatchCORHeader creates a COR BatchHeader +func mockBatchCORHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = COR + bh.CompanyName = "Your Company, inc" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "Vendor Pay" + bh.ODFIIdentification = "121042882" + return bh +} + +// mockCOREntryDetail creates a COR EntryDetail +func mockCOREntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingReturnNOCCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.IdentificationNumber = "location #23" + entry.SetReceivingCompany("Best Co. #23") + entry.SetTraceNumber(mockBatchCORHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "S" + return entry +} + +// mockBatchCOR creates a BatchCOR +func mockBatchCOR() *BatchCOR { + mockBatch := NewBatchCOR(mockBatchCORHeader()) + mockBatch.AddEntry(mockCOREntryDetail()) + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + if err := mockBatch.Create(); err != nil { + log.Fatal(err) + } + return mockBatch +} + +// testBatchCORHeader creates a COR BatchHeader +func testBatchCORHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchCORHeader()) + + _, ok := batch.(*BatchCOR) + if !ok { + t.Error("Expecting BachCOR") + } +} + +// TestBatchCORHeader tests creating a COR BatchHeader +func TestBatchCORHeader(t *testing.T) { + testBatchCORHeader(t) +} + +// BenchmarkBatchCORHeader benchmarks creating a COR BatchHeader +func BenchmarkBatchCORHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCORHeader(b) + } +} + +// testBatchCORSEC validates BatchCOR SEC code +func testBatchCORSEC(t testing.TB) { + mockBatch := mockBatchCOR() + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.Header.StandardEntryClassCode = WEB + err := mockBatch.Validate() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCORSEC tests validating BatchCOR SEC code +func TestBatchCORSEC(t *testing.T) { + testBatchCORSEC(t) +} + +// BenchmarkBatchCORSEC benchmarks validating BatchCOR SEC code +func BenchmarkBatchCORSEC(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCORSEC(b) + } +} + +// testBatchCORAddendumCountTwo validates Addendum count of 2 +func testBatchCORAddendumCountTwo(t testing.TB) { + mockBatch := mockBatchCOR() + // Adding a second addenda to the mock entry + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98() + err := mockBatch.Create() + // TODO: are we expecting there to be an error here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCORAddendumCountTwo tests validating Addendum count of 2 +func TestBatchCORAddendumCountTwo(t *testing.T) { + testBatchCORAddendumCountTwo(t) +} + +// BenchmarkBatchCORAddendumCountTwo benchmarks validating Addendum count of 2 +func BenchmarkBatchCORAddendumCountTwo(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCORAddendumCountTwo(b) + } +} + +// testBatchCORAddendaCountZero validates Addendum count of 0 +func testBatchCORAddendaCountZero(t testing.TB) { + mockBatch := NewBatchCOR(mockBatchCORHeader()) + mockBatch.AddEntry(mockCOREntryDetail()) + err := mockBatch.Create() + if !base.Match(err, ErrBatchCORAddenda) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCORAddendaCountZero tests validating Addendum count of 0 +func TestBatchCORAddendaCountZero(t *testing.T) { + testBatchCORAddendaCountZero(t) +} + +// BenchmarkBatchCORAddendaCountZero benchmarks validating Addendum count of 0 +func BenchmarkBatchCORAddendaCountZero(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCORAddendaCountZero(b) + } +} + +// testBatchCORAddendaType validates that Addendum is of type Addenda98 +func testBatchCORAddendaType(t testing.TB) { + mockBatch := NewBatchCOR(mockBatchCORHeader()) + mockBatch.AddEntry(mockCOREntryDetail()) + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrBatchCORAddenda) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCORAddendaType tests validating that Addendum is of type Addenda98 +func TestBatchCORAddendaType(t *testing.T) { + testBatchCORAddendaType(t) +} + +// BenchmarkBatchCORAddendaType benchmarks validating that Addendum is of type Addenda98 +func BenchmarkBatchCORAddendaType(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCORAddendaType(b) + } +} + +// testBatchCORAddendaTypeCode validates TypeCode +func testBatchCORAddendaTypeCode(t testing.TB) { + mockBatch := mockBatchCOR() + mockBatch.GetEntries()[0].Addenda98.TypeCode = "07" + err := mockBatch.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCORAddendaTypeCode tests validating TypeCode +func TestBatchCORAddendaTypeCode(t *testing.T) { + testBatchCORAddendaTypeCode(t) +} + +// BenchmarkBatchCORAddendaTypeCode benchmarks validating TypeCode +func BenchmarkBatchCORAddendaTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCORAddendaTypeCode(b) + } +} + +// testBatchCORAmount validates BatchCOR Amount +func testBatchCORAmount(t testing.TB) { + mockBatch := mockBatchCOR() + // test a nonzero credit amount + mockBatch.GetEntries()[0].Amount = 9999 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAmountNonZero) { + t.Errorf("%T: %s", err, err) + } + + // test a nonzero debit amount + mockBatch.GetEntries()[0].TransactionCode = CheckingReturnNOCDebit + err = mockBatch.Create() + if !base.Match(err, ErrBatchAmountNonZero) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCORAmount tests validating BatchCOR Amount +func TestBatchCORAmount(t *testing.T) { + testBatchCORAmount(t) +} + +// BenchmarkBatchCORAmount benchmarks validating BatchCOR Amount +func BenchmarkBatchCORAmount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCORAmount(b) + } +} + +// testBatchCORTransactionCode27 validates BatchCOR TransactionCode 27 returns an error +func testBatchCORTransactionCode27(t testing.TB) { + mockBatch := mockBatchCOR() + mockBatch.GetEntries()[0].TransactionCode = CheckingDebit + err := mockBatch.Create() + if !base.Match(err, ErrBatchTransactionCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCORTransactionCode27 tests validating BatchCOR TransactionCode 27 returns an error +func TestBatchCORTransactionCode27(t *testing.T) { + testBatchCORTransactionCode27(t) +} + +// BenchmarkBatchCORTransactionCode27 benchmarks validating +// BatchCOR TransactionCode 27 returns an error +func BenchmarkBatchCORTransactionCode27(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCORTransactionCode27(b) + } +} + +// testBatchCORTransactionCode21 validates BatchCOR TransactionCode 21 is a valid TransactionCode to be used for NOC +// mockBatch.Create() should not return an error for this test +func testBatchCORTransactionCode21(t testing.TB) { + mockBatch := mockBatchCOR() + mockBatch.GetEntries()[0].TransactionCode = CheckingReturnNOCCredit + if err := mockBatch.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCORTransactionCode21 tests validating BatchCOR TransactionCode 21 +func TestBatchCORTransactionCode21(t *testing.T) { + testBatchCORTransactionCode21(t) +} + +// BenchmarkBatchCORTransactionCode21 benchmarks validating BatchCOR TransactionCode 21 +func BenchmarkBatchCORTransactionCode21(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCORTransactionCode21(b) + } +} + +// testBatchCORCreate creates BatchCOR +func testBatchCORCreate(t testing.TB) { + mockBatch := mockBatchCOR() + // Must have valid batch header to create a Batch + mockBatch.GetHeader().ServiceClassCode = 63 + err := mockBatch.Create() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCORCreate tests creating BatchCOR +func TestBatchCORCreate(t *testing.T) { + testBatchCORCreate(t) +} + +// BenchmarkBatchCORCreate benchmarks creating BatchCOR +func BenchmarkBatchCORCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCORCreate(b) + } +} + +// testBatchCORServiceClassCodeEquality validates service class code equality +func testBatchCORServiceClassCodeEquality(t testing.TB) { + mockBatch := mockBatchCOR() + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(220, MixedDebitsAndCredits)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCORServiceClassCodeEquality tests validating service class code equality +func TestBatchCORServiceClassCodeEquality(t *testing.T) { + testBatchCORServiceClassCodeEquality(t) +} + +// BenchmarkBatchCORServiceClassCodeEquality benchmarks validating service class code equality +func BenchmarkBatchCORServiceClassCodeEquality(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCORServiceClassCodeEquality(b) + } +} + +// TestBatchCORCategoryNOCAddenda05 validates that an error is returned if valid Addenda05 is defined for CategoryNOC +func TestBatchCORCategoryNOCAddenda05(t *testing.T) { + mockBatch := NewBatchCOR(mockBatchCORHeader()) + mockBatch.AddEntry(mockCOREntryDetail()) + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98() + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCORCategoryNOCAddenda02 validates that an error is returned if valid Addenda02 is defined for CategoryNOC +func TestBatchCORCategoryNOCAddenda02(t *testing.T) { + mockBatch := NewBatchCOR(mockBatchCORHeader()) + mockBatch.AddEntry(mockCOREntryDetail()) + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98() + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCORCategoryNOCAddenda98 validates that no error is returned if Addenda098 is defined for CategoryNOC +func TestBatchCORCategoryNOCAddenda98(t *testing.T) { + mockBatch := NewBatchCOR(mockBatchCORHeader()) + mockBatch.AddEntry(mockCOREntryDetail()) + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + // no error expected + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCORValidTranCodeForServiceClassCode validates a transactionCode based on ServiceClassCode +func TestBatchCORValidTranCodeForServiceClassCode(t *testing.T) { + mockBatch := mockBatchCOR() + mockBatch.GetHeader().ServiceClassCode = AutomatedAccountingAdvices + err := mockBatch.Create() + if !base.Match(err, ErrBatchServiceClassCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCORInvalidAddenda98 validates that an error is returned if Addenda98 is invalid +func TestBatchCORTestBatchCORInvalidAddenda98(t *testing.T) { + mockBatch := NewBatchCOR(mockBatchCORHeader()) + mockBatch.AddEntry(mockCOREntryDetail()) + mockBatch.GetEntries()[0].Category = CategoryNOC + addenda98 := mockAddenda98() + addenda98.TypeCode = "63" + mockBatch.GetEntries()[0].Addenda98 = addenda98 + + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCORTransactionCodeInvalid validates BatchCOR returns an error for an invalid TransactionCode +func TestBatchCORAutomatedAccountingAdvices(t *testing.T) { + mockBatch := mockBatchCOR() + mockBatch.GetEntries()[0].TransactionCode = 65 + err := mockBatch.Create() + if !base.Match(err, ErrTransactionCode) { + t.Errorf("%T: %s", err, err) + } +} diff --git a/batchCTX.go b/batchCTX.go new file mode 100644 index 000000000..7da49b3a7 --- /dev/null +++ b/batchCTX.go @@ -0,0 +1,105 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strconv" +) + +// BatchCTX holds the BatchHeader and BatchControl and all EntryDetail for CTX Entries. +// +// The Corporate Trade Exchange (CTX) application provides the ability to collect and disburse +// funds and information between companies. Generally it is used by businesses paying one another +// for goods or services. These payments replace checks with an electronic process of debiting and +// crediting invoices between the financial institutions of participating companies. +type BatchCTX struct { + Batch +} + +// NewBatchCTX returns a *BatchCTX +func NewBatchCTX(bh *BatchHeader) *BatchCTX { + batch := new(BatchCTX) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} + +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the batch. +func (batch *BatchCTX) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + + // Add configuration and type specific validation for this type. + if batch.Header.StandardEntryClassCode != CTX { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, CTX) + } + + for _, entry := range batch.Entries { + + // Trapping this error, as entry.CTXAddendaRecordsField() can not be greater than 9999 + if len(entry.Addenda05) > 9999 { + return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 9999)) + } + + // validate CTXAddendaRecord Field is equal to the actual number of Addenda records + // use 0 value if there is no Addenda records + addendaRecords, _ := strconv.Atoi(entry.CATXAddendaRecordsField()) + if len(entry.Addenda05) != addendaRecords { + return batch.Error("AddendaCount", NewErrBatchExpectedAddendaCount(len(entry.Addenda05), addendaRecords)) + } + // Verify TransactionCode for prenotes and regular entries + switch entry.TransactionCode { + case CheckingPrenoteCredit, CheckingPrenoteDebit, + SavingsPrenoteCredit, SavingsReturnNOCDebit, + GLPrenoteCredit, GLPrenoteDebit, LoanPrenoteCredit: + if entry.Amount != 0 { + return batch.Error("TransactionCode", ErrBatchTransactionCode, entry.TransactionCode) + } + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + } + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchCTX) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + // Additional steps specific to batch type + // ... + return batch.Validate() +} diff --git a/batchCTX_test.go b/batchCTX_test.go new file mode 100644 index 000000000..f19d850aa --- /dev/null +++ b/batchCTX_test.go @@ -0,0 +1,569 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "log" + "testing" + + "github.com/moov-io/base" +) + +// mockBatchCTXHeader creates a BatchCTX BatchHeader +func mockBatchCTXHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = CTX + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH CTX" + bh.ODFIIdentification = "12104288" + return bh +} + +// mockCTXEntryDetail creates a BatchCTX EntryDetail +func mockCTXEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(1) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchCTXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + return entry +} + +// mockBatchCTX creates a BatchCTX +func mockBatchCTX() *BatchCTX { + mockBatch := NewBatchCTX(mockBatchCTXHeader()) + mockBatch.AddEntry(mockCTXEntryDetail()) + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + mockBatch.Entries[0].AddendaRecordIndicator = 1 + if err := mockBatch.Create(); err != nil { + log.Fatal(err) + } + return mockBatch +} + +// testBatchCTXHeader creates a BatchCTX BatchHeader +func testBatchCTXHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchCTXHeader()) + err, ok := batch.(*BatchCTX) + if !ok { + t.Errorf("Expecting BatchCTX got %T", err) + } +} + +// TestBatchCTXHeader tests validating BatchCTX BatchHeader +func TestBatchCTXHeader(t *testing.T) { + testBatchCTXHeader(t) +} + +// BenchmarkBatchCTXHeader benchmarks validating BatchCTX BatchHeader +func BenchmarkBatchCTXHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCTXHeader(b) + } +} + +// testBatchCTXCreate validates BatchCTX create +func testBatchCTXCreate(t testing.TB) { + mockBatch := mockBatchCTX() + if err := mockBatch.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCTXCreate tests validating BatchCTX create +func TestBatchCTXCreate(t *testing.T) { + testBatchCTXCreate(t) +} + +// BenchmarkBatchCTXCreate benchmarks validating BatchCTX create +func BenchmarkBatchCTXCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCTXCreate(b) + } +} + +// testBatchCTXStandardEntryClassCode validates BatchCTX create for an invalid StandardEntryClassCode +func testBatchCTXStandardEntryClassCode(t testing.TB) { + mockBatch := mockBatchCTX() + mockBatch.Header.StandardEntryClassCode = WEB + err := mockBatch.Create() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCTXStandardEntryClassCode tests validating BatchCTX create for an invalid StandardEntryClassCode +func TestBatchCTXStandardEntryClassCode(t *testing.T) { + testBatchCTXStandardEntryClassCode(t) +} + +// BenchmarkBatchCTXStandardEntryClassCode benchmarks validating BatchCTX create for an invalid StandardEntryClassCode +func BenchmarkBatchCTXStandardEntryClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCTXStandardEntryClassCode(b) + } +} + +// testBatchCTXServiceClassCodeEquality validates service class code equality +func testBatchCTXServiceClassCodeEquality(t testing.TB) { + mockBatch := mockBatchCTX() + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(220, MixedDebitsAndCredits)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCTXServiceClassCodeEquality tests validating service class code equality +func TestBatchCTXServiceClassCodeEquality(t *testing.T) { + testBatchCTXServiceClassCodeEquality(t) +} + +// BenchmarkBatchCTXServiceClassCodeEquality benchmarks validating service class code equality +func BenchmarkBatchCTXServiceClassCodeEquality(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCTXServiceClassCodeEquality(b) + } +} + +// testBatchCTXAddendaCount validates BatchCTX Addendum count of 2 +func testBatchCTXAddendaCount(t testing.TB) { + mockBatch := mockBatchCTX() + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + err := mockBatch.Create() + if !base.Match(err, NewErrBatchExpectedAddendaCount(0, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCTXAddendaCount tests validating BatchCTX Addendum count of 2 +func TestBatchCTXAddendaCount(t *testing.T) { + testBatchCTXAddendaCount(t) +} + +// BenchmarkBatchCTXAddendaCount benchmarks validating BatchCTX Addendum count of 2 +func BenchmarkBatchCTXAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCTXAddendaCount(b) + } +} + +// testBatchCTXAddendaCountZero validates Addendum count of 0 +func testBatchCTXAddendaCountZero(t testing.TB) { + mockBatch := NewBatchCTX(mockBatchCTXHeader()) + mockBatch.AddEntry(mockCTXEntryDetail()) + err := mockBatch.Create() + if !base.Match(err, NewErrBatchExpectedAddendaCount(0, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCTXAddendaCountZero tests validating Addendum count of 0 +func TestBatchCTXAddendaCountZero(t *testing.T) { + testBatchCTXAddendaCountZero(t) +} + +// BenchmarkBatchCTXAddendaCountZero benchmarks validating Addendum count of 0 +func BenchmarkBatchCTXAddendaCountZero(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCTXAddendaCountZero(b) + } +} + +// testBatchCTXInvalidAddenda validates Addendum must be Addenda05 with type code 05 +func testBatchCTXInvalidAddenda(t testing.TB) { + mockBatch := NewBatchCTX(mockBatchCTXHeader()) + mockBatch.AddEntry(mockCTXEntryDetail()) + addenda05 := mockAddenda05() + addenda05.TypeCode = "63" + mockBatch.GetEntries()[0].AddAddenda05(addenda05) + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCTXInvalidAddenda tests validating Addendum must be Addenda05 with record type 7 +func TestBatchCTXInvalidAddenda(t *testing.T) { + testBatchCTXInvalidAddenda(t) +} + +// BenchmarkBatchCTXInvalidAddenda benchmarks validating Addendum must be Addenda05 with record type 7 +func BenchmarkBatchCTXInvalidAddenda(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCTXInvalidAddenda(b) + } +} + +// testBatchCTXInvalidBuild validates an invalid batch build +func testBatchCTXInvalidBuild(t testing.TB) { + mockBatch := mockBatchCTX() + mockBatch.GetHeader().ServiceClassCode = 3 + err := mockBatch.Create() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCTXInvalidBuild tests validating an invalid batch build +func TestBatchCTXInvalidBuild(t *testing.T) { + testBatchCTXInvalidBuild(t) +} + +// BenchmarkBatchCTXInvalidBuild benchmarks validating an invalid batch build +func BenchmarkBatchCTXInvalidBuild(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCTXInvalidBuild(b) + } +} + +// testBatchCTXAddenda10000 validates error for 10000 Addenda +func testBatchCTXAddenda10000(t testing.TB) { + + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = CTX + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH CTX" + bh.ODFIIdentification = "12104288" + + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(9999) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchCTXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + + mockBatch := NewBatchCTX(bh) + mockBatch.AddEntry(entry) + + for i := 0; i < 10000; i++ { + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + } + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, NewErrBatchAddendaCount(10000, 9999)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCTXAddenda10000 tests validating error for 10000 Addenda +func TestBatchCTXAddenda10000(t *testing.T) { + testBatchCTXAddenda10000(t) +} + +// BenchmarkBatchCTXAddenda10000 benchmarks validating error for 10000 Addenda +func BenchmarkBatchCTXAddenda10000(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCTXAddenda10000(b) + } +} + +// testBatchCTXAddendaRecords validates error for AddendaRecords not equal to addendum +func testBatchCTXAddendaRecords(t testing.TB) { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = CTX + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH CTX" + bh.ODFIIdentification = "12104288" + + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(500) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchCTXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + + mockBatch := NewBatchCTX(bh) + mockBatch.AddEntry(entry) + + for i := 0; i < 565; i++ { + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + } + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, NewErrBatchExpectedAddendaCount(0, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCTXAddendaRecords tests validating error for AddendaRecords not equal to addendum +func TestBatchCTXAddendaRecords(t *testing.T) { + testBatchCTXAddendaRecords(t) +} + +// BenchmarkBatchAddendaRecords benchmarks validating error for AddendaRecords not equal to addendum +func BenchmarkBatchCTXAddendaRecords(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCTXAddendaRecords(b) + } +} + +// testBatchCTXReceivingCompany validates CTXReceivingCompany +func testBatchCTXReceivingCompany(t testing.TB) { + mockBatch := mockBatchCTX() + //mockBatch.GetEntries()[0].SetCATXReceivingCompany("Receiver") + + if mockBatch.GetEntries()[0].CATXReceivingCompanyField() != "Receiver Company" { + t.Errorf("expected %v got %v", "Receiver Company", mockBatch.GetEntries()[0].CATXReceivingCompanyField()) + } +} + +// TestBatchCTXReceivingCompany tests validating CTXReceivingCompany +func TestBatchCTXReceivingCompany(t *testing.T) { + testBatchCTXReceivingCompany(t) +} + +// BenchmarkBatchCTXReceivingCompany benchmarks validating CTXReceivingCompany +func BenchmarkBatchCTXReceivingCompany(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCTXReceivingCompany(b) + } +} + +// testBatchCTXReserved validates CTXReservedField +func testBatchCTXReserved(t testing.TB) { + mockBatch := mockBatchCTX() + + if mockBatch.GetEntries()[0].CATXReservedField() != " " { + t.Errorf("expected %v got %v", " ", mockBatch.GetEntries()[0].CATXReservedField()) + } +} + +// TestBatchCTXReserved tests validating CTXReservedField +func TestBatchCTXReserved(t *testing.T) { + testBatchCTXReserved(t) +} + +// BenchmarkBatchCTXReserved benchmarks validating CTXReservedField +func BenchmarkBatchCTXReserved(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCTXReserved(b) + } +} + +// testBatchCTXZeroAddendaRecords validates zero addenda records +func testBatchCTXZeroAddendaRecords(t testing.TB) { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = CTX + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH CTX" + bh.ODFIIdentification = "12104288" + + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(1) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchCTXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + + mockBatch := NewBatchCTX(bh) + mockBatch.AddEntry(entry) + + err := mockBatch.Create() + if !base.Match(err, NewErrBatchExpectedAddendaCount(0, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCTXZeroAddendaRecords tests validating zero addenda records +func TestBatchCTXZeroAddendaRecords(t *testing.T) { + testBatchCTXZeroAddendaRecords(t) +} + +// BenchmarkBatchZeroAddendaRecords benchmarks validating zero addenda records +func BenchmarkBatchCTXZeroAddendaRecords(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCTXZeroAddendaRecords(b) + } +} + +// testBatchCTXPrenoteAddendaRecords validates prenote addenda records +func testBatchCTXPrenoteAddendaRecords(t testing.TB) { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = CTX + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH CTX" + bh.ODFIIdentification = "12104288" + bh.OriginatorStatusCode = 2 + + entry := NewEntryDetail() + entry.TransactionCode = CheckingPrenoteCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(1) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchCTXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + + mockBatch := NewBatchCTX(bh) + mockBatch.AddEntry(entry) + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if err != nil { + t.Errorf("%T: %v", err, err) + } +} + +// TestBatchCTXPrenoteAddendaRecords tests validating prenote addenda records +func TestBatchCTXPrenoteAddendaRecords(t *testing.T) { + testBatchCTXPrenoteAddendaRecords(t) +} + +// testBatchCTXPrenoteAddendaRecordsErr validates prenote addenda records +func testBatchCTXPrenoteAddendaRecordsErr(t testing.TB) { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = CTX + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH CTX" + bh.ODFIIdentification = "12104288" + bh.OriginatorStatusCode = 2 + + entry := NewEntryDetail() + entry.TransactionCode = CheckingPrenoteCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(1) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchCTXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + + mockBatch := NewBatchCTX(bh) + mockBatch.AddEntry(entry) + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrBatchTransactionCode) { + t.Errorf("%T %s", err, err) + } +} + +// TestBatchCTXPrenoteAddendaRecordsErr tests validating prenote addenda records +func TestBatchCTXPrenoteAddendaRecordsErr(t *testing.T) { + testBatchCTXPrenoteAddendaRecordsErr(t) +} + +// BenchmarkBatchPrenoteAddendaRecords benchmarks validating prenote addenda records +func BenchmarkBatchCTXPrenoteAddendaRecords(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCTXPrenoteAddendaRecords(b) + } +} + +// TestBatchCTXAddendum98 validates Addenda98 returns an error +func TestBatchCTXAddendum98(t *testing.T) { + mockBatch := NewBatchCTX(mockBatchCTXHeader()) + mockBatch.AddEntry(mockCTXEntryDetail()) + mockAddenda98 := mockAddenda98() + mockAddenda98.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCTXAddendum99 validates Addenda99 returns an error +func TestBatchCTXAddendum99(t *testing.T) { + mockBatch := NewBatchCTX(mockBatchCTXHeader()) + mockBatch.AddEntry(mockCTXEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCTXValidTranCodeForServiceClassCode validates a transactionCode based on ServiceClassCode +func TestBatchCTXValidTranCodeForServiceClassCode(t *testing.T) { + mockBatch := mockBatchCTX() + mockBatch.GetHeader().ServiceClassCode = DebitsOnly + err := mockBatch.Create() + if !base.Match(err, NewErrBatchServiceClassTranCode(DebitsOnly, 22)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCTXAddenda02 validates BatchCTX cannot have Addenda02 +func TestBatchCTXAddenda02(t *testing.T) { + mockBatch := mockBatchCTX() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02() + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} diff --git a/batchControl.go b/batchControl.go index 3fed720b1..0c14819bb 100644 --- a/batchControl.go +++ b/batchControl.go @@ -1,6 +1,19 @@ -// Copyright 2017 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. package ach @@ -8,60 +21,60 @@ import ( "fmt" "strconv" "strings" + "unicode/utf8" ) // BatchControl contains entry counts, dollar total and has totals for all // entries contained in the preceding batch type BatchControl struct { - // RecordType defines the type of record in the block. batchControlPos 8 - recordType string - // ServiceClassCode ACH Mixed Debits and Credits ‘200’ - // ACH Credits Only ‘220’ - // ACH Debits Only ‘225' + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // ServiceClassCode ACH Mixed Debits and Credits '200' + // ACH Credits Only '220' + // ACH Debits Only '225' + // Constants: MixedCreditsAnDebits (220), CReditsOnly 9220), DebitsOnly (225) // Same as 'ServiceClassCode' in BatchHeaderRecord - ServiceClassCode int + ServiceClassCode int `json:"serviceClassCode"` // EntryAddendaCount is a tally of each Entry Detail Record and each Addenda // Record processed, within either the batch or file as appropriate. - EntryAddendaCount int + EntryAddendaCount int `json:"entryAddendaCount"` // validate the Receiving DFI Identification in each Entry Detail Record is hashed // to provide a check against inadvertent alteration of data contents due - // to hardware failure or program erro + // to hardware failure or program error // // In this context the Entry Hash is the sum of the corresponding fields in the // Entry Detail Records on the file. - EntryHash int + EntryHash int `json:"entryHash"` // TotalDebitEntryDollarAmount Contains accumulated Entry debit totals within the batch. - TotalDebitEntryDollarAmount int + TotalDebitEntryDollarAmount int `json:"totalDebit"` // TotalCreditEntryDollarAmount Contains accumulated Entry credit totals within the batch. - TotalCreditEntryDollarAmount int - // CompanyIdentification is an alphameric code used to identify an Originato + TotalCreditEntryDollarAmount int `json:"totalCredit"` + // CompanyIdentification is an alphanumeric code used to identify an Originator // The Company Identification Field must be included on all - // prenotification records and on each entry initiated puruant to such + // prenotification records and on each entry initiated pursuant to such // prenotification. The Company ID may begin with the ANSI one-digit - // Identification Code Designators (ICD), followed by the identification - // numbe The ANSI Identification Numbers and related Identification Code - // Designators (ICD) are: + // Identification Code Designator (ICD), followed by the identification + // number The ANSI Identification Numbers and related Identification Code + // Designator (ICD) are: // // IRS Employer Identification Number (EIN) "1" // Data Universal Numbering Systems (DUNS) "3" // User Assigned Number "9" - CompanyIdentification string + CompanyIdentification string `json:"companyIdentification"` // MessageAuthenticationCode the MAC is an eight character code derived from a special key used in // conjunction with the DES algorithm. The purpose of the MAC is to // validate the authenticity of ACH entries. The DES algorithm and key // message standards must be in accordance with standards adopted by the // American National Standards Institute. The remaining eleven characters // of this field are blank. - MessageAuthenticationCode string - // Reserved for the future - Blank, 6 characters long - reserved string - // OdfiIdentification the routing number is used to identify the DFI originating entries within a given branch. - ODFIIdentification int + MessageAuthenticationCode string `json:"messageAuthentication,omitempty"` + // ODFIIdentification the routing number is used to identify the DFI originating entries within a given branch. + ODFIIdentification string `json:"ODFIIdentification"` // BatchNumber this number is assigned in ascending sequence to each batch by the ODFI // or its Sending Point in a given file of entries. Since the batch number // in the Batch Header Record and the Batch Control Record is the same, // the ascending sequence number should be assigned by batch and not by record. - BatchNumber int + BatchNumber int `json:"batchNumber"` // validator is composed for data validation validator // converters is composed for ACH to golang Converters @@ -69,9 +82,14 @@ type BatchControl struct { } // Parse takes the input record string and parses the EntryDetail values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. func (bc *BatchControl) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + // 1-1 Always "8" - bc.recordType = "8" // 2-4 This is the same as the "Service code" field in previous Batch Header Record bc.ServiceClassCode = bc.parseNumField(record[1:4]) // 5-10 Total number of Entry Detail Record in the batch @@ -88,9 +106,8 @@ func (bc *BatchControl) Parse(record string) { // 55-73 Seems to always be blank bc.MessageAuthenticationCode = strings.TrimSpace(record[54:73]) // 74-79 Always blank (just fill with spaces) - bc.reserved = " " // 80-87 This is the same as the "ODFI identification" field in previous Batch Header Record - bc.ODFIIdentification = bc.parseNumField(record[79:87]) + bc.ODFIIdentification = bc.parseStringField(record[79:87]) // 88-94 This is the same as the "Batch number" field in previous Batch Header Record bc.BatchNumber = bc.parseNumField(record[87:94]) } @@ -98,27 +115,28 @@ func (bc *BatchControl) Parse(record string) { // NewBatchControl returns a new BatchControl with default values for none exported fields func NewBatchControl() *BatchControl { return &BatchControl{ - recordType: "8", - EntryHash: 1, - BatchNumber: 1, + ServiceClassCode: MixedDebitsAndCredits, + EntryHash: 1, + BatchNumber: 1, } } // String writes the BatchControl struct to a 94 character string. func (bc *BatchControl) String() string { - return fmt.Sprintf("%v%v%v%v%v%v%v%v%v%v%v", - bc.recordType, - bc.ServiceClassCode, - bc.EntryAddendaCountField(), - bc.EntryHashField(), - bc.TotalDebitEntryDollarAmountField(), - bc.TotalCreditEntryDollarAmountField(), - bc.CompanyIdentificationField(), - bc.MessageAuthenticationCodeField(), - " ", - bc.ODFIIdentificationField(), - bc.BatchNumberField(), - ) + var buf strings.Builder + buf.Grow(94) + buf.WriteString(batchControlPos) + buf.WriteString(fmt.Sprintf("%v", bc.ServiceClassCode)) + buf.WriteString(bc.EntryAddendaCountField()) + buf.WriteString(bc.EntryHashField()) + buf.WriteString(bc.TotalDebitEntryDollarAmountField()) + buf.WriteString(bc.TotalCreditEntryDollarAmountField()) + buf.WriteString(bc.CompanyIdentificationField()) + buf.WriteString(bc.MessageAuthenticationCodeField()) + buf.WriteString(" ") + buf.WriteString(bc.ODFIIdentificationField()) + buf.WriteString(bc.BatchNumberField()) + return buf.String() } // Validate performs NACHA format rule checks on the record and returns an error if not Validated @@ -127,20 +145,16 @@ func (bc *BatchControl) Validate() error { if err := bc.fieldInclusion(); err != nil { return err } - if bc.recordType != "8" { - msg := fmt.Sprintf(msgRecordType, 7) - return &FieldError{FieldName: "recordType", Value: bc.recordType, Msg: msg} - } if err := bc.isServiceClass(bc.ServiceClassCode); err != nil { - return &FieldError{FieldName: "ServiceClassCode", Value: strconv.Itoa(bc.ServiceClassCode), Msg: err.Error()} + return fieldError("ServiceClassCode", err, strconv.Itoa(bc.ServiceClassCode)) } if err := bc.isAlphanumeric(bc.CompanyIdentification); err != nil { - return &FieldError{FieldName: "CompanyIdentification", Value: bc.CompanyIdentification, Msg: err.Error()} + return fieldError("CompanyIdentification", err, bc.CompanyIdentification) } if err := bc.isAlphanumeric(bc.MessageAuthenticationCode); err != nil { - return &FieldError{FieldName: "MessageAuthenticationCode", Value: bc.MessageAuthenticationCode, Msg: err.Error()} + return fieldError("MessageAuthenticationCode", err, bc.MessageAuthenticationCode) } return nil @@ -149,14 +163,11 @@ func (bc *BatchControl) Validate() error { // fieldInclusion validate mandatory fields are not default values. If fields are // invalid the ACH transfer will be returned. func (bc *BatchControl) fieldInclusion() error { - if bc.recordType == "" { - return &FieldError{FieldName: "recordType", Value: bc.recordType, Msg: msgFieldInclusion} - } if bc.ServiceClassCode == 0 { - return &FieldError{FieldName: "ServiceClassCode", Value: strconv.Itoa(bc.ServiceClassCode), Msg: msgFieldInclusion} + return fieldError("ServiceClassCode", ErrConstructor, strconv.Itoa(bc.ServiceClassCode)) } - if bc.ODFIIdentification == 0 { - return &FieldError{FieldName: "ODFIIdentification", Value: bc.ODFIIdentificationField(), Msg: msgFieldInclusion} + if bc.ODFIIdentification == "000000000" { + return fieldError("ODFIIdentification", ErrConstructor, bc.ODFIIdentificationField()) } return nil } @@ -171,7 +182,7 @@ func (bc *BatchControl) EntryHashField() string { return bc.numericField(bc.EntryHash, 10) } -//TotalDebitEntryDollarAmountField get a zero padded Debity Entry Amount +// TotalDebitEntryDollarAmountField get a zero padded Debit Entry Amount func (bc *BatchControl) TotalDebitEntryDollarAmountField() string { return bc.numericField(bc.TotalDebitEntryDollarAmount, 12) } @@ -181,7 +192,7 @@ func (bc *BatchControl) TotalCreditEntryDollarAmountField() string { return bc.numericField(bc.TotalCreditEntryDollarAmount, 12) } -// CompanyIdentificationField get the CompanyIdentification righ padded +// CompanyIdentificationField get the CompanyIdentification right padded func (bc *BatchControl) CompanyIdentificationField() string { return bc.alphaField(bc.CompanyIdentification, 10) } @@ -193,7 +204,7 @@ func (bc *BatchControl) MessageAuthenticationCodeField() string { // ODFIIdentificationField get the odfi number zero padded func (bc *BatchControl) ODFIIdentificationField() string { - return bc.numericField(bc.ODFIIdentification, 8) + return bc.stringField(bc.ODFIIdentification, 8) } // BatchNumberField gets a string of the batch number zero padded diff --git a/batchControl_internal_test.go b/batchControl_internal_test.go deleted file mode 100644 index 200e51693..000000000 --- a/batchControl_internal_test.go +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2017 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. - -package ach - -import ( - "strings" - "testing" -) - -func mockBatchControl() *BatchControl { - bc := NewBatchControl() - bc.ServiceClassCode = 220 - bc.CompanyIdentification = "123456789" - bc.ODFIIdentification = 6200001 - return bc -} - -func TestMockBatchControl(t *testing.T) { - bc := mockBatchControl() - if err := bc.Validate(); err != nil { - t.Error("mockBatchControl does not validate and will break other tests") - } - if bc.ServiceClassCode != 220 { - t.Error("ServiceClassCode depedendent default value has changed") - } - if bc.CompanyIdentification != "123456789" { - t.Error("CompanyIdentification depedendent default value has changed") - } - if bc.ODFIIdentification != 6200001 { - t.Error("ODFIIdentification depedendent default value has changed") - } -} - -// TestParseBatchControl parses a known Batch ControlRecord string. -func TestParseBatchControl(t *testing.T) { - var line = "82250000010005320001000000010500000000000000origid 076401250000001" - r := NewReader(strings.NewReader(line)) - r.line = line - r.addCurrentBatch(NewBatchPPD()) - bh := BatchHeader{BatchNumber: 1, - ServiceClassCode: 225, - CompanyIdentification: "origid", - ODFIIdentification: 7640125} - r.currentBatch.SetHeader(&bh) - - r.currentBatch.AddEntry(&EntryDetail{TransactionCode: 27, Amount: 10500, RDFIIdentification: 5320001, TraceNumber: 76401255655291}) - if err := r.parseBatchControl(); err != nil { - t.Errorf("%T: %s", err, err) - } - record := r.currentBatch.GetControl() - - if record.recordType != "8" { - t.Errorf("RecordType Expected '8' got: %v", record.recordType) - } - if record.ServiceClassCode != 225 { - t.Errorf("ServiceClassCode Expected '225' got: %v", record.ServiceClassCode) - } - if record.EntryAddendaCountField() != "000001" { - t.Errorf("EntryAddendaCount Expected '000001' got: %v", record.EntryAddendaCountField()) - } - if record.EntryHashField() != "0005320001" { - t.Errorf("EntryHash Expected '0005320001' got: %v", record.EntryHashField()) - } - if record.TotalDebitEntryDollarAmountField() != "000000010500" { - t.Errorf("TotalDebitEntryDollarAmount Expected '000000010500' got: %v", record.TotalDebitEntryDollarAmountField()) - } - if record.TotalCreditEntryDollarAmountField() != "000000000000" { - t.Errorf("TotalCreditEntryDollarAmount Expected '000000000000' got: %v", record.TotalCreditEntryDollarAmountField()) - } - if record.CompanyIdentificationField() != "origid " { - t.Errorf("CompanyIdentification Expected 'origid ' got: %v", record.CompanyIdentificationField()) - } - if record.MessageAuthenticationCodeField() != " " { - t.Errorf("MessageAuthenticationCode Expected ' ' got: %v", record.MessageAuthenticationCodeField()) - } - if record.reserved != " " { - t.Errorf("Reserved Expected ' ' got: %v", record.reserved) - } - if record.ODFIIdentificationField() != "07640125" { - t.Errorf("OdfiIdentification Expected '07640125' got: %v", record.ODFIIdentificationField()) - } - if record.BatchNumberField() != "0000001" { - t.Errorf("BatchNumber Expected '0000001' got: %v", record.BatchNumberField()) - } -} - -// TestBCString validats that a known parsed file can be return to a string of the same value -func TestBCString(t *testing.T) { - var line = "82250000010005320001000000010500000000000000origid 076401250000001" - r := NewReader(strings.NewReader(line)) - r.line = line - r.addCurrentBatch(NewBatchPPD()) - bh := BatchHeader{BatchNumber: 1, - ServiceClassCode: 225, - CompanyIdentification: "origid", - ODFIIdentification: 7640125} - r.currentBatch.SetHeader(&bh) - - r.currentBatch.AddEntry(&EntryDetail{TransactionCode: 27, Amount: 10500, RDFIIdentification: 5320001, TraceNumber: 76401255655291}) - if err := r.parseBatchControl(); err != nil { - t.Errorf("%T: %s", err, err) - } - record := r.currentBatch.GetControl() - - if record.String() != line { - t.Errorf("Strings do not match") - } -} - -// TestValidateBCRecordType ensure error if recordType is not 8 -func TestValidateBCRecordType(t *testing.T) { - bc := mockBatchControl() - bc.recordType = "2" - if err := bc.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "recordType" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestBCisServiceClassErr(t *testing.T) { - bc := mockBatchControl() - bc.ServiceClassCode = 123 - if err := bc.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "ServiceClassCode" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestBCBatchNumber(t *testing.T) { - bc := mockBatchControl() - bc.BatchNumber = 0 - if err := bc.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "BatchNumber" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestBCCompanyIdentificationAlphaNumeric(t *testing.T) { - bc := mockBatchControl() - bc.CompanyIdentification = "®" - if err := bc.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "CompanyIdentification" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestBCMessageAuthenticationCodeAlphaNumeric(t *testing.T) { - bc := mockBatchControl() - bc.MessageAuthenticationCode = "®" - if err := bc.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "MessageAuthenticationCode" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestBCFieldInclusionRecordType(t *testing.T) { - bc := mockBatchControl() - bc.recordType = "" - if err := bc.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestBCFieldInclusionServiceClassCode(t *testing.T) { - bc := mockBatchControl() - bc.ServiceClassCode = 0 - if err := bc.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestBCFieldInclusionODFIIdentification(t *testing.T) { - bc := mockBatchControl() - bc.ODFIIdentification = 0 - if err := bc.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} diff --git a/batchControl_test.go b/batchControl_test.go new file mode 100644 index 000000000..80df23ed0 --- /dev/null +++ b/batchControl_test.go @@ -0,0 +1,317 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "testing" + + "github.com/moov-io/base" +) + +func mockBatchControl() *BatchControl { + bc := NewBatchControl() + bc.ServiceClassCode = CreditsOnly + bc.CompanyIdentification = "121042882" + bc.ODFIIdentification = "12104288" + return bc +} + +// testMockBatchControl tests mock batch control +func testMockBatchControl(t testing.TB) { + bc := mockBatchControl() + if err := bc.Validate(); err != nil { + t.Error("mockBatchControl does not validate and will break other tests") + } + if bc.ServiceClassCode != CreditsOnly { + t.Error("ServiceClassCode depedendent default value has changed") + } + if bc.CompanyIdentification != "121042882" { + t.Error("CompanyIdentification depedendent default value has changed") + } + if bc.ODFIIdentification != "12104288" { + t.Error("ODFIIdentification depedendent default value has changed") + } +} + +// TestMockBatchControl test mock batch control +func TestMockBatchControl(t *testing.T) { + testMockBatchControl(t) +} + +// BenchmarkMockBatchControl benchmarks mock batch control +func BenchmarkMockBatchControl(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testMockBatchControl(b) + } +} + +// TestParseBatchControl parses a known Batch ControlRecord string. +func testParseBatchControl(t testing.TB) { + var line = "82250000010005320001000000010500000000000000origid 076401250000001" + r := NewReader(strings.NewReader(line)) + r.line = line + bh := BatchHeader{BatchNumber: 1, + ServiceClassCode: DebitsOnly, + CompanyIdentification: "origid", + ODFIIdentification: "7640125"} + r.addCurrentBatch(NewBatchPPD(&bh)) + + r.currentBatch.AddEntry(&EntryDetail{TransactionCode: 27, Amount: 10500, RDFIIdentification: "5320001", TraceNumber: "76401255655291"}) + if err := r.parseBatchControl(); err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.currentBatch.GetControl() + + if record.ServiceClassCode != DebitsOnly { + t.Errorf("ServiceClassCode Expected '225' got: %v", record.ServiceClassCode) + } + if record.EntryAddendaCountField() != "000001" { + t.Errorf("EntryAddendaCount Expected '000001' got: %v", record.EntryAddendaCountField()) + } + if record.EntryHashField() != "0005320001" { + t.Errorf("EntryHash Expected '0005320001' got: %v", record.EntryHashField()) + } + if record.TotalDebitEntryDollarAmountField() != "000000010500" { + t.Errorf("TotalDebitEntryDollarAmount Expected '000000010500' got: %v", record.TotalDebitEntryDollarAmountField()) + } + if record.TotalCreditEntryDollarAmountField() != "000000000000" { + t.Errorf("TotalCreditEntryDollarAmount Expected '000000000000' got: %v", record.TotalCreditEntryDollarAmountField()) + } + if record.CompanyIdentificationField() != "origid " { + t.Errorf("CompanyIdentification Expected 'origid ' got: %v", record.CompanyIdentificationField()) + } + if record.MessageAuthenticationCodeField() != " " { + t.Errorf("MessageAuthenticationCode Expected ' ' got: %v", record.MessageAuthenticationCodeField()) + } + if record.ODFIIdentificationField() != "07640125" { + t.Errorf("OdfiIdentification Expected '07640125' got: %v", record.ODFIIdentificationField()) + } + if record.BatchNumberField() != "0000001" { + t.Errorf("BatchNumber Expected '0000001' got: %v", record.BatchNumberField()) + } +} + +// TestParseBatchControl tests parsing a known Batch ControlRecord string. +func TestParseBatchControl(t *testing.T) { + testParseBatchControl(t) +} + +// BenchmarkParseBatchControl benchmarks parsing a known Batch ControlRecord string. +func BenchmarkParseBatchControl(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testParseBatchControl(b) + } +} + +// testBCString validates that a known parsed file can be return to a string of the same value +func testBCString(t testing.TB) { + var line = "82250000010005320001000000010500000000000000origid 076401250000001" + r := NewReader(strings.NewReader(line)) + r.line = line + bh := BatchHeader{BatchNumber: 1, + ServiceClassCode: DebitsOnly, + CompanyIdentification: "origid", + ODFIIdentification: "7640125"} + r.addCurrentBatch(NewBatchPPD(&bh)) + + r.currentBatch.AddEntry(&EntryDetail{TransactionCode: 27, Amount: 10500, RDFIIdentification: "5320001", TraceNumber: "76401255655291"}) + if err := r.parseBatchControl(); err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.currentBatch.GetControl() + + if record.String() != line { + t.Errorf("Strings do not match") + } +} + +// TestBCString tests validating that a known parsed file can be return to a string of the same value +func TestBCString(t *testing.T) { + testBCString(t) +} + +// BenchmarkBCString benchmarks validating that a known parsed file can be return to a string of the same value +func BenchmarkBCString(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBCString(b) + } +} + +// testBCisServiceClassErr verifies service class code +func testBCisServiceClassErr(t testing.TB) { + bc := mockBatchControl() + bc.ServiceClassCode = 123 + err := bc.Validate() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBCisServiceClassErr tests verifying service class code +func TestBCisServiceClassErr(t *testing.T) { + testBCisServiceClassErr(t) +} + +// BenchmarkBCisServiceClassErr benchmarks verifying service class code +func BenchmarkBCisServiceClassErr(b *testing.B) { + for i := 0; i < b.N; i++ { + testBCisServiceClassErr(b) + } +} + +// testBCBatchNumber verifies batch number +func testBCBatchNumber(t testing.TB) { + bc := mockBatchControl() + bc.BatchNumber = 0 + err := bc.Validate() + // TODO: are we expecting there to be no errors here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBCBatchNumber tests verifying batch number +func TestBCBatchNumber(t *testing.T) { + testBCBatchNumber(t) +} + +// BenchmarkBCBatchNumber benchmarks verifying batch number +func BenchmarkBCBatchNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBCBatchNumber(b) + } +} + +// testBCCompanyIdentificationAlphaNumeric verifies Company Identification is AlphaNumeric +func testBCCompanyIdentificationAlphaNumeric(t testing.TB) { + bc := mockBatchControl() + bc.CompanyIdentification = "®" + err := bc.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBCCompanyIdentificationAlphaNumeric tests verifying Company Identification is AlphaNumeric +func TestBCCompanyIdentificationAlphaNumeric(t *testing.T) { + testBCCompanyIdentificationAlphaNumeric(t) +} + +// BenchmarkBCCompanyIdentificationAlphaNumeric benchmarks verifying Company Identification is AlphaNumeric +func BenchmarkBCCompanyIdentificationAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBCCompanyIdentificationAlphaNumeric(b) + } +} + +// testBCMessageAuthenticationCodeAlphaNumeric verifies AuthenticationCode is AlphaNumeric +func testBCMessageAuthenticationCodeAlphaNumeric(t testing.TB) { + bc := mockBatchControl() + bc.MessageAuthenticationCode = "®" + err := bc.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBCMessageAuthenticationCodeAlphaNumeric tests verifying AuthenticationCode is AlphaNumeric +func TestBCMessageAuthenticationCodeAlphaNumeric(t *testing.T) { + testBCMessageAuthenticationCodeAlphaNumeric(t) +} + +// BenchmarkBCMessageAuthenticationCodeAlphaNumeric benchmarks verifying AuthenticationCode is AlphaNumeric +func BenchmarkBCMessageAuthenticationCodeAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBCMessageAuthenticationCodeAlphaNumeric(b) + } +} + +// testBCFieldInclusionServiceClassCode verifies Service Class Code is included +func testBCFieldInclusionServiceClassCode(t testing.TB) { + bc := mockBatchControl() + bc.ServiceClassCode = 0 + err := bc.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBCFieldInclusionServiceClassCode tests verifying Service Class Code is included +func TestBCFieldInclusionServiceClassCode(t *testing.T) { + testBCFieldInclusionServiceClassCode(t) +} + +// BenchmarkBCFieldInclusionServiceClassCod benchmarks verifying Service Class Code is included +func BenchmarkBCFieldInclusionServiceClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBCFieldInclusionServiceClassCode(b) + } +} + +// testBCFieldInclusionODFIIdentification verifies batch control ODFIIdentification +func testBCFieldInclusionODFIIdentification(t testing.TB) { + bc := mockBatchControl() + bc.ODFIIdentification = "000000000" + err := bc.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBCFieldInclusionODFIIdentification tests verifying batch control ODFIIdentification +func TestBCFieldInclusionODFIIdentification(t *testing.T) { + testBCFieldInclusionODFIIdentification(t) +} + +// BenchmarkBCFieldInclusionODFIIdentification benchmarks verifying batch control ODFIIdentification +func BenchmarkBCFieldInclusionODFIIdentification(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBCFieldInclusionODFIIdentification(b) + } +} + +// testBatchControlLength verifies batch control length +func testBatchControlLength(t testing.TB) { + bc := NewBatchControl() + recordLength := len(bc.String()) + if recordLength != 94 { + t.Errorf("Instantiated length of Batch Control string is not 94 but %v", recordLength) + } +} + +// TestBatchControlLength tests verifying batch control length +func TestBatchControlLength(t *testing.T) { + testBatchControlLength(t) +} + +// BenchmarkBatchControlLength benchmarks verifying batch control length +func BenchmarkBatchControlLength(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchControlLength(b) + } +} diff --git a/batchDNE.go b/batchDNE.go new file mode 100644 index 000000000..d8c55b6d4 --- /dev/null +++ b/batchDNE.go @@ -0,0 +1,129 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" +) + +// BatchDNE is a batch file that handles SEC code Death Notification Entry (DNE) +// United States Federal agencies (e.g. Social Security) use this to notify depository +// financial institutions that the recipient of government benefit payments has died. +// +// Notes: +// - Date of death always in positions 18-23 +// - SSN (positions 38-46) are zero if no SSN +// - Beneficiary payment starts at position 55 +type BatchDNE struct { + Batch +} + +// NewBatchDNE returns a *BatchDNE +func NewBatchDNE(bh *BatchHeader) *BatchDNE { + batch := new(BatchDNE) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} + +// Validate ensures the batch meets NACHA rules specific to this batch type. +func (batch *BatchDNE) Validate() error { + if err := batch.verify(); err != nil { + return err + } + + // SEC code + if batch.Header.StandardEntryClassCode != DNE { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, DNE) + } + + // Range over Entries + for _, entry := range batch.Entries { + if entry.Amount != 0 { + return batch.Error("Amount", ErrBatchAmountNonZero, entry.Amount) + } + + switch entry.TransactionCode { + case CheckingReturnNOCCredit, CheckingPrenoteCredit, SavingsReturnNOCCredit, SavingsPrenoteCredit: + default: + return batch.Error("TransactionCode", ErrBatchTransactionCode, entry.TransactionCode) + } + + // DNE must have one Addenda05 + if len(entry.Addenda05) != 1 { + return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 1)) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + } + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchDNE) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + return batch.Validate() +} + +// details returns the Date of Death (YYMMDD), Customer SSN (9 digits), and Amount ($$$$.cc) +// from the Addenda05 record. This method assumes the addenda05 PaymentRelatedInformation is valid. +func (batch *BatchDNE) details() (string, string, string) { + if batch == nil || len(batch.Entries) == 0 { + return "", "", "" + } + + addendas := batch.Entries[0].Addenda05 + if len(addendas) != 1 { + return "", "", "" + } + + line := addendas[0].PaymentRelatedInformation + return line[18:24], line[37:46], strings.TrimSuffix(line[54:], `\`) +} + +// DateOfDeath returns the YYMMDD string from Addenda05's PaymentRelatedInformation +func (batch *BatchDNE) DateOfDeath() string { + date, _, _ := batch.details() + return date +} + +// CustomerSSN returns the SSN string from Addenda05's PaymentRelatedInformation +func (batch *BatchDNE) CustomerSSN() string { + _, ssn, _ := batch.details() + return ssn +} + +// Amount returns the amount to be dispursed to the named beneficiary from Addenda05's PaymentRelatedInformation. +func (batch *BatchDNE) Amount() string { + _, _, amount := batch.details() + return amount +} diff --git a/batchDNE_test.go b/batchDNE_test.go new file mode 100644 index 000000000..91612ee61 --- /dev/null +++ b/batchDNE_test.go @@ -0,0 +1,298 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "log" + "testing" + "time" + + "github.com/moov-io/base" +) + +// mockBatchDNEHeader creates a DNE batch header +func mockBatchDNEHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = "231380104" + bh.StandardEntryClassCode = DNE + bh.CompanyEntryDescription = "Death" + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "23138010" + bh.OriginatorStatusCode = 2 + return bh +} + +// mockDNEEntryDetail creates a DNE entry detail +func mockDNEEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingReturnNOCCredit + entry.SetRDFI("031300012") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.SetOriginalTraceNumber("031300010000001") + entry.SetReceivingCompany("Best. #1") + entry.SetTraceNumber("23138010", 1) + entry.AddendaRecordIndicator = 1 + + addenda := NewAddenda05() + addenda.PaymentRelatedInformation = ` DATE OF DEATH*010218*CUSTOMERSSN*#########*AMOUNT*$$$$.cc\` + entry.AddAddenda05(addenda) + + return entry +} + +// mockBatchDNE creates a DNE batch +func mockBatchDNE() *BatchDNE { + batch := NewBatchDNE(mockBatchDNEHeader()) + batch.AddEntry(mockDNEEntryDetail()) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + return batch +} + +// testBatchDNEHeader creates a DNE batch header +func testBatchDNEHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchDNEHeader()) + _, ok := batch.(*BatchDNE) + if !ok { + t.Error("Expecting BatchDNE") + } +} + +// TestBatchDNEHeader tests creating a DNE batch header +func TestBatchDNEHeader(t *testing.T) { + testBatchDNEHeader(t) +} + +// BenchmarkBatchDNEHeader benchmark creating a DNE batch header +func BenchmarkBatchDNEHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchDNEHeader(b) + } +} + +// testBatchDNEAddendumCount batch control DNE can only have one addendum per entry detail +func testBatchDNEAddendumCount(t testing.TB) { + mockBatch := mockBatchDNE() + // Adding a second addenda to the mock entry + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchCalculatedControlEquality(3, 2)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchDNEAddendumCount tests batch control DNE can only have one addendum per entry detail +func TestBatchDNEAddendumCount(t *testing.T) { + testBatchDNEAddendumCount(t) +} + +// BenchmarkBatchDNEAddendumCount benchmarks batch control DNE can only have one addendum per entry detail +func BenchmarkBatchDNEAddendumCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchDNEAddendumCount(b) + } +} + +// TestBatchDNEAddendum98 validates Addenda05 returns an error +func TestBatchDNEAddendum98(t *testing.T) { + mockBatch := NewBatchDNE(mockBatchDNEHeader()) + mockBatch.AddEntry(mockDNEEntryDetail()) + err := mockBatch.Create() + // TODO: are we expecting there to be an error here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// testBatchDNEReceivingCompanyName validates Receiving company / Individual name is a mandatory field +func testBatchDNEReceivingCompanyName(t testing.TB) { + mockBatch := mockBatchDNE() + // modify the Individual name / receiving company to nothing + mockBatch.GetEntries()[0].SetReceivingCompany("") + err := mockBatch.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchDNEReceivingCompanyName tests validating receiving company / Individual name is a mandatory field +func TestBatchDNEReceivingCompanyName(t *testing.T) { + testBatchDNEReceivingCompanyName(t) +} + +// BenchmarkBatchDNEReceivingCompanyName benchmarks validating receiving company / Individual name is a mandatory field +func BenchmarkBatchDNEReceivingCompanyName(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchDNEReceivingCompanyName(b) + } +} + +// testBatchDNEAddendaTypeCode validates addenda type code is 05 +func testBatchDNEAddendaTypeCode(t testing.TB) { + mockBatch := mockBatchDNE() + mockBatch.GetEntries()[0].Addenda05[0].TypeCode = "05" + err := mockBatch.Validate() + // no error expected + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchDNEAddendaTypeCode tests validating addenda type code is 05 +func TestBatchDNEAddendaTypeCode(t *testing.T) { + testBatchDNEAddendaTypeCode(t) +} + +// BenchmarkBatchDNEAddendaTypeCod benchmarks validating addenda type code is 05 +func BenchmarkBatchDNEAddendaTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchDNEAddendaTypeCode(b) + } +} + +// testBatchDNESEC validates that the standard entry class code is DNE for batchDNE +func testBatchDNESEC(t testing.TB) { + mockBatch := mockBatchDNE() + mockBatch.Header.StandardEntryClassCode = ACK + err := mockBatch.Validate() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchDNESEC tests validating that the standard entry class code is DNE for batchDNE +func TestBatchDNESEC(t *testing.T) { + testBatchDNESEC(t) +} + +// BenchmarkBatchDNESEC benchmarks validating that the standard entry class code is DNE for batch DNE +func BenchmarkBatchDNESEC(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchDNESEC(b) + } +} + +// testBatchDNEAddendaCount validates batch DNE addenda count +func testBatchDNEAddendaCount(t testing.TB) { + mockBatch := mockBatchDNE() + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + err := mockBatch.Create() + if !base.Match(err, NewErrBatchAddendaCount(0, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchDNEAddendaCount tests validating batch DNE addenda count +func TestBatchDNEAddendaCount(t *testing.T) { + testBatchDNEAddendaCount(t) +} + +// BenchmarkBatchDNEAddendaCount benchmarks validating batch DNE addenda count +func BenchmarkBatchDNEAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchDNEAddendaCount(b) + } +} + +// testBatchDNEServiceClassCode validates ServiceClassCode +func testBatchDNEServiceClassCode(t testing.TB) { + mockBatch := mockBatchDNE() + // Batch Header information is required to Create a batch. + mockBatch.GetHeader().ServiceClassCode = 0 + err := mockBatch.Create() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchDNEServiceClassCode tests validating ServiceClassCode +func TestBatchDNEServiceClassCode(t *testing.T) { + testBatchDNEServiceClassCode(t) +} + +// BenchmarkBatchDNEServiceClassCode benchmarks validating ServiceClassCode +func BenchmarkBatchDNEServiceClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchDNEServiceClassCode(b) + } +} + +// TestBatchDNEAmount validates Amount +func TestBatchDNEAmount(t *testing.T) { + mockBatch := mockBatchDNE() + // Batch Header information is required to Create a batch. + mockBatch.GetEntries()[0].Amount = 25000 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAmountNonZero) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchDNETransactionCode validates TransactionCode +func TestBatchDNETransactionCode(t *testing.T) { + mockBatch := mockBatchDNE() + mockBatch.GetEntries()[0].TransactionCode = CheckingCredit + err := mockBatch.Create() + if !base.Match(err, ErrBatchTransactionCode) { + t.Errorf("%T: %s", err, err) + } +} + +func TestBatchDNE__Details(t *testing.T) { + mockBatch := mockBatchDNE() + date, ssn, amount := mockBatch.details() + if date != "010218" { + t.Errorf("Got %s", date) + } + if ssn != "#########" { + t.Errorf("Got %s", ssn) + } + if amount != "$$$$.cc" { + t.Errorf("Got %s", amount) + } + + // Check the helper methods too + if v := mockBatch.DateOfDeath(); v != date { + t.Errorf("got %s expected %s", v, date) + } + if v := mockBatch.CustomerSSN(); v != ssn { + t.Errorf("got %s expected %s", v, ssn) + } + if v := mockBatch.Amount(); v != amount { + t.Errorf("got %s expected %s", v, amount) + } +} + +func TestBatchDNE__nil(t *testing.T) { + var batch *BatchDNE = nil + date, ssn, amount := batch.details() + if date != "" || ssn != "" || amount != "" { + t.Errorf("got non-empty details from nil BatchDNE: date=%q ssn=%q amount=%q", date, ssn, amount) + } +} diff --git a/batchENR.go b/batchENR.go new file mode 100644 index 000000000..c51d9a62c --- /dev/null +++ b/batchENR.go @@ -0,0 +1,162 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "fmt" + "strconv" + "strings" +) + +// BatchENR is a non-monetary entry that enrolls a person with an agency of the US government +// for a depository financial institution. +// +// Allowed TransactionCode values: 22 Demand Credit, 27 Demand Debit, 32 Savings Credit, 37 Savings Debit +type BatchENR struct { + Batch +} + +// NewBatchENR returns a *BatchENR +func NewBatchENR(bh *BatchHeader) *BatchENR { + batch := new(BatchENR) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} + +// Validate ensures the batch meets NACHA rules specific to this batch type. +func (batch *BatchENR) Validate() error { + if err := batch.verify(); err != nil { + return err + } + + // Batch Header checks + if batch.Header.StandardEntryClassCode != ENR { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, ENR) + } + if batch.Header.CompanyEntryDescription != "AUTOENROLL" { + return batch.Error("CompanyEntryDescription", ErrBatchCompanyEntryDescriptionAutoenroll, batch.Header.CompanyEntryDescription) + } + + // Range over Entries + for _, entry := range batch.Entries { + if err := entry.Validate(); err != nil { + return err + } + + if entry.Amount != 0 { + return batch.Error("Amount", ErrBatchAmountNonZero, entry.Amount) + } + + switch entry.TransactionCode { + case CheckingCredit, CheckingDebit, SavingsCredit, SavingsDebit: + default: + return batch.Error("TransactionCode", ErrBatchTransactionCode, entry.TransactionCode) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // ENR must have one Addenda05 + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + } + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchENR) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + return batch.Validate() +} + +// ENRPaymentInformation structure +type ENRPaymentInformation struct { + // TransactionCode is the Transaction Code of the holder's account + // Values: 22 (Demand Credit), 27 (Demand Debit), 32 (Savings Credit), 37 (Savings Debit) + TransactionCode int + + // RDFIIdentification is the Receiving Depository Identification Number. Typically the first 8 of their ABA routing number. + RDFIIdentification string + + // CheckDigit is the last digit from an ABA routing number. + CheckDigit string + + // DFIAccountNumber contains the holder's account number. + DFIAccountNumber string + + // IndividualIdentification contains the customer's Social Security Number (SSN) for automated enrollments and the + // taxpayer ID for companies. + IndividualIdentification string + + // IndividualName is the account holders full name. + IndividualName string + + // EnrolleeClassificationCode (also called Representative Payee Indicator) returns a code from a specific Addenda05 record. + // These codes represent: + // 0: (no) - Initiated by beneficiary + // 1: (yes) - Initiated by someone other than named beneficiary + // A: Enrollee is a consumer + // b: Enrollee is a company + EnrolleeClassificationCode int +} + +func (info *ENRPaymentInformation) String() string { + line := "TransactionCode: %d, RDFIIdentification: %s, CheckDigit: %s, DFIAccountNumber: %s, IndividualIdentification: %v, IndividualName: %s, EnrolleeClassificationCode: %d" + return fmt.Sprintf(line, info.TransactionCode, info.RDFIIdentification, info.CheckDigit, info.DFIAccountNumber, info.IndividualIdentification != "", info.IndividualName, info.EnrolleeClassificationCode) +} + +// ParsePaymentInformation returns an ENRPaymentInformation for a given Addenda05 record. The information is parsed from the addenda's +// PaymentRelatedInformation field. +// +// The returned information is not validated for correctness. +func (batch *BatchENR) ParsePaymentInformation(addenda05 *Addenda05) (*ENRPaymentInformation, error) { + parts := strings.Split(strings.TrimSuffix(addenda05.PaymentRelatedInformation, `\`), "*") // PaymentRelatedInformation is terminated by '\' + if len(parts) != 8 { + return nil, fmt.Errorf("ENR: unable to parse Addenda05 (%s) PaymentRelatedInformation", addenda05.ID) + } + + txCode, err := strconv.Atoi(parts[0]) + if err != nil { + return nil, fmt.Errorf("ENR: unable to parse TransactionCode (%s) from Addenda05.ID=%s", parts[0], addenda05.ID) + } + enrolleeCode, err := strconv.Atoi(parts[7]) + if err != nil { + return nil, fmt.Errorf("ENR: unable to parse EnrolleeClassificationCode (%s) from Addenda05.ID=%s", parts[7], addenda05.ID) + } + + return &ENRPaymentInformation{ + TransactionCode: txCode, + RDFIIdentification: parts[1], + CheckDigit: parts[2], + DFIAccountNumber: parts[3], + IndividualIdentification: parts[4], + IndividualName: fmt.Sprintf("%s %s", parts[6], parts[5]), + EnrolleeClassificationCode: enrolleeCode, + }, nil +} diff --git a/batchENR_test.go b/batchENR_test.go new file mode 100644 index 000000000..52a11e33e --- /dev/null +++ b/batchENR_test.go @@ -0,0 +1,316 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "log" + "testing" + + "github.com/moov-io/base" +) + +// mockBatchENRHeader creates a ENR batch header +func mockBatchENRHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = "231380104" + bh.StandardEntryClassCode = ENR + bh.CompanyEntryDescription = "AUTOENROLL" + bh.ODFIIdentification = "23138010" + return bh +} + +// mockENREntryDetail creates a ENR entry detail +func mockENREntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("031300012") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.SetOriginalTraceNumber("031300010000001") + entry.SetReceivingCompany("Best. #1") + entry.SetTraceNumber("23138010", 1) + + addenda := NewAddenda05() + addenda.PaymentRelatedInformation = `21*12200004*3*123987654321*777777777*DOE*JOHN*1\` + entry.AddAddenda05(addenda) + entry.AddendaRecordIndicator = 1 + + return entry +} + +// mockBatchENR creates a ENR batch +func mockBatchENR() *BatchENR { + batch := NewBatchENR(mockBatchENRHeader()) + batch.AddEntry(mockENREntryDetail()) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + return batch +} + +// testBatchENRHeader creates a ENR batch header +func testBatchENRHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchENRHeader()) + _, ok := batch.(*BatchENR) + if !ok { + t.Error("Expecting BatchENR") + } +} + +// TestBatchENRHeader tests creating a ENR batch header +func TestBatchENRHeader(t *testing.T) { + testBatchENRHeader(t) +} + +// BenchmarkBatchENRHeader benchmark creating a ENR batch header +func BenchmarkBatchENRHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchENRHeader(b) + } +} + +// testBatchENRAddendumCount batch control ENR must have 1-9999 Addenda05 records +func testBatchENRAddendumCount(t testing.TB) { + mockBatch := mockBatchENR() + // Adding a second addenda to the mock entry + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + if err := mockBatch.Create(); err != nil { + t.Errorf("Adding addenda is allowed: %v", err) + } + if err := mockBatch.Validate(); err != nil { + t.Errorf("Adding addendas is allowed: %v", err) + } +} + +// TestBatchENRAddendumCount tests batch control ENR can only have one addendum per entry detail +func TestBatchENRAddendumCount(t *testing.T) { + testBatchENRAddendumCount(t) +} + +// BenchmarkBatchENRAddendumCount benchmarks batch control ENR can only have one addendum per entry detail +func BenchmarkBatchENRAddendumCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchENRAddendumCount(b) + } +} + +// TestBatchENRAddendum98 validates Addenda05 returns an error +func TestBatchENRAddendum98(t *testing.T) { + mockBatch := NewBatchENR(mockBatchENRHeader()) + mockBatch.AddEntry(mockENREntryDetail()) + err := mockBatch.Create() + // TODO: are we expecting there to be an error here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// testBatchENRCompanyEntryDescription validates CompanyEntryDescription +func testBatchENRCompanyEntryDescription(t testing.TB) { + mockBatch := mockBatchENR() + mockBatch.Header.CompanyEntryDescription = "bad" + err := mockBatch.Create() + if !base.Match(err, ErrBatchCompanyEntryDescriptionAutoenroll) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchENRCompanyEntryDescription tests validating receiving company / Individual name is a mandatory field +func TestBatchENRCompanyEntryDescription(t *testing.T) { + testBatchENRCompanyEntryDescription(t) +} + +// BenchmarkBatchENRCompanyEntryDescription benchmarks validating receiving company / Individual name is a mandatory field +func BenchmarkBatchENRCompanyEntryDescription(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchENRCompanyEntryDescription(b) + } +} + +// testBatchENRAddendaTypeCode validates addenda type code is 05 +func testBatchENRAddendaTypeCode(t testing.TB) { + mockBatch := mockBatchENR() + mockBatch.GetEntries()[0].Addenda05[0].TypeCode = "98" + err := mockBatch.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchENRAddendaTypeCode tests validating addenda type code is 05 +func TestBatchENRAddendaTypeCode(t *testing.T) { + testBatchENRAddendaTypeCode(t) +} + +// BenchmarkBatchENRAddendaTypeCod benchmarks validating addenda type code is 05 +func BenchmarkBatchENRAddendaTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchENRAddendaTypeCode(b) + } +} + +// testBatchENRSEC validates that the standard entry class code is ENR for batchENR +func testBatchENRSEC(t testing.TB) { + mockBatch := mockBatchENR() + mockBatch.Header.StandardEntryClassCode = ACK + err := mockBatch.Validate() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchENRSEC tests validating that the standard entry class code is ENR for batchENR +func TestBatchENRSEC(t *testing.T) { + testBatchENRSEC(t) +} + +// BenchmarkBatchENRSEC benchmarks validating that the standard entry class code is ENR for batch ENR +func BenchmarkBatchENRSEC(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchENRSEC(b) + } +} + +// testBatchENRAddendaCount validates batch ENR addenda count +func testBatchENRAddendaCount(t testing.TB) { + mockBatch := mockBatchENR() + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + err := mockBatch.Create() + // TODO: are we expecting there to be no errors here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchENRAddendaCount tests validating batch ENR addenda count +func TestBatchENRAddendaCount(t *testing.T) { + testBatchENRAddendaCount(t) +} + +// BenchmarkBatchENRAddendaCount benchmarks validating batch ENR addenda count +func BenchmarkBatchENRAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchENRAddendaCount(b) + } +} + +// testBatchENRServiceClassCode validates ServiceClassCode +func testBatchENRServiceClassCode(t testing.TB) { + mockBatch := mockBatchENR() + mockBatch.GetHeader().ServiceClassCode = 0 + err := mockBatch.Create() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchENRServiceClassCode tests validating ServiceClassCode +func TestBatchENRServiceClassCode(t *testing.T) { + testBatchENRServiceClassCode(t) +} + +// BenchmarkBatchENRServiceClassCode benchmarks validating ServiceClassCode +func BenchmarkBatchENRServiceClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchENRServiceClassCode(b) + } +} + +// TestBatchENRAmount validates Amount +func TestBatchENRAmount(t *testing.T) { + mockBatch := mockBatchENR() + mockBatch.GetEntries()[0].Amount = 25000 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAmountNonZero) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchENRTransactionCode validates TransactionCode +func TestBatchENRTransactionCode(t *testing.T) { + mockBatch := mockBatchENR() + mockBatch.GetEntries()[0].TransactionCode = CheckingReturnNOCCredit + err := mockBatch.Create() + if !base.Match(err, ErrBatchTransactionCode) { + t.Errorf("%T: %s", err, err) + } +} + +func TestBatchENR__PaymentInformation(t *testing.T) { + batch := mockBatchENR() + if err := batch.Validate(); err != nil { + t.Fatal(err) + } + addenda05 := batch.GetEntries()[0].Addenda05[0] + info, err := batch.ParsePaymentInformation(addenda05) + if err != nil { + t.Fatal(err) + } + + if v := info.TransactionCode; v != CheckingReturnNOCCredit { + t.Errorf("TransactionCode: %d", v) + } + if v := info.RDFIIdentification; v != "12200004" { + t.Errorf("RDFIIdentification: %s", v) + } + if v := info.CheckDigit; v != "3" { + t.Errorf("CheckDigit: %s", v) + } + if v := info.DFIAccountNumber; v != "123987654321" { + t.Errorf("DFIAccountNumber: %s", v) + } + if v := info.IndividualIdentification; v != "777777777" { + t.Errorf("IndividualIdentification: %s", v) + } + if v := info.IndividualName; v != "JOHN DOE" { + t.Errorf("IndividualName: %s", v) + } + if v := info.EnrolleeClassificationCode; v != 1 { + t.Errorf("EnrolleeClassificationCode: %d", v) + } +} + +// TestBatchENRValidTranCodeForServiceClassCode validates a transactionCode based on ServiceClassCode +func TestBatchENRValidTranCodeForServiceClassCode(t *testing.T) { + mockBatch := mockBatchENR() + mockBatch.GetHeader().ServiceClassCode = DebitsOnly + err := mockBatch.Create() + if !base.Match(err, NewErrBatchServiceClassTranCode(DebitsOnly, 22)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchENRAddenda02 validates BatchENR cannot have Addenda02 +func TestBatchENRAddenda02(t *testing.T) { + mockBatch := mockBatchENR() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02() + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} diff --git a/batchErrors.go b/batchErrors.go new file mode 100644 index 000000000..577850a98 --- /dev/null +++ b/batchErrors.go @@ -0,0 +1,364 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "errors" + "fmt" +) + +var ( + // ErrBatchNoEntries is the error given when a batch doesn't have any entries + ErrBatchNoEntries = errors.New("must have Entry Record(s) to be built") + // ErrBatchADVCount is the error given when an ADV batch has too many entries + ErrBatchADVCount = errors.New("there can be a maximum of 9999 ADV Sequence Numbers (ADV Entry Detail Records)") + // ErrBatchAddendaIndicator is the error given when the addenda indicator is incorrectly set + ErrBatchAddendaIndicator = errors.New("is 0 but found addenda record(s)") + // ErrBatchOriginatorDNE is the error given when a non-government agency tries to originate a DNE + ErrBatchOriginatorDNE = errors.New("only government agencies (originator status code 2) can originate a DNE") + // ErrBatchInvalidCardTransactionType is the error given when a card transaction type is invalid + ErrBatchInvalidCardTransactionType = errors.New("invalid card transaction type") + // ErrBatchDebitOnly is the error given when a batch which can only have debits has a credit + ErrBatchDebitOnly = errors.New("this batch type does not allow credit transaction codes") + // ErrBatchCheckSerialNumber is the error given when a batch requires check serial numbers, but it is missing + ErrBatchCheckSerialNumber = errors.New("this batch type requires entries to have Check Serial Numbers") + // ErrBatchSECType is the error given when the batch's header has the wrong SEC for its type + ErrBatchSECType = errors.New("header SEC does not match this batch's type") + // ErrBatchServiceClassCode is the error given when the batch's header has the wrong SCC for its type + ErrBatchServiceClassCode = errors.New("header SCC is not valid for this batch's type") + // ErrBatchTransactionCode is the error given when a batch has an invalid transaction code + ErrBatchTransactionCode = errors.New("transaction code is not valid for this batch's type") + // ErrBatchTransactionCodeAddenda is the error given when a batch has an addenda on a transaction code which doesn't allow it + ErrBatchTransactionCodeAddenda = errors.New("this batch type does not allow an addenda for this transaction code") + // ErrBatchAmountNonZero is the error given when an entry for a non-zero amount is in a batch that requires zero amount entries + ErrBatchAmountNonZero = errors.New("this batch type requires that the amount is zero") + // ErrBatchAmountZero is the error given when an entry for zero amount is in a batch that requires non-zero amount entries + ErrBatchAmountZero = errors.New("this batch type requires that the amount is non-zero") + // ErrBatchCompanyEntryDescriptionAutoenroll is the error given when the Company Entry Description is invalid (needs to be 'Autoenroll') + ErrBatchCompanyEntryDescriptionAutoenroll = errors.New("this batch type requires that the Company Entry Description is AUTOENROLL") + // ErrBatchCompanyEntryDescriptionREDEPCHECK is the error given when the Company Entry Description is invalid (needs to be 'REDEPCHECK') + ErrBatchCompanyEntryDescriptionREDEPCHECK = errors.New("this batch type requires that the Company Entry Description is REDEPCHECK") + // ErrBatchAddendaCategory is the error given when the addenda isn't allowed for the batch's type and category + ErrBatchAddendaCategory = errors.New("this batch type does not allow this addenda for category") +) + +// BatchError is an Error that describes batch validation issues +type BatchError struct { + BatchNumber int + BatchType string + FieldName string + FieldValue interface{} + Err error +} + +func (e *BatchError) Error() string { + if e.FieldValue == nil { + return fmt.Sprintf("batch #%d (%v) %s %v", e.BatchNumber, e.BatchType, e.FieldName, e.Err) + } + return fmt.Sprintf("batch #%d (%v) %s %v: %v", e.BatchNumber, e.BatchType, e.FieldName, e.Err, e.FieldValue) +} + +// Unwrap implements the base.UnwrappableError interface for BatchError +func (e *BatchError) Unwrap() error { + return e.Err +} + +// error returns a new BatchError based on err +func (b *Batch) Error(field string, err error, values ...interface{}) error { + if err == nil { + return nil + } + if _, ok := err.(*BatchError); ok { + return err + } + be := BatchError{ + BatchNumber: b.Header.BatchNumber, + BatchType: b.Header.StandardEntryClassCode, + FieldName: field, + Err: err, + } + // only the first value counts + if len(values) > 0 { + be.FieldValue = values[0] + } + return &be +} + +// error returns a new BatchError based on err +func (iatBatch *IATBatch) Error(field string, err error, values ...interface{}) error { + if err == nil { + return nil + } + if _, ok := err.(*BatchError); ok { + return err + } + be := BatchError{ + BatchNumber: iatBatch.Header.BatchNumber, + BatchType: iatBatch.Header.StandardEntryClassCode, + FieldName: field, + Err: err, + } + // only the first value counts + if len(values) > 0 { + be.FieldValue = values[0] + } + return &be +} + +// ErrBatchHeaderControlEquality is the error given when the control record does not match the calculated value +type ErrBatchHeaderControlEquality struct { + Message string + HeaderValue interface{} + ControlValue interface{} +} + +// NewErrBatchHeaderControlEquality creates a new error of the ErrBatchHeaderControlEquality type +func NewErrBatchHeaderControlEquality(header, control interface{}) ErrBatchHeaderControlEquality { + return ErrBatchHeaderControlEquality{ + Message: fmt.Sprintf("header %v is not equal to control %v", header, control), + HeaderValue: header, + ControlValue: control, + } +} + +func (e ErrBatchHeaderControlEquality) Error() string { + return e.Message +} + +// ErrBatchCalculatedControlEquality is the error given when the control record does not match the calculated value +type ErrBatchCalculatedControlEquality struct { + Message string + CalculatedValue interface{} + ControlValue interface{} +} + +// NewErrBatchCalculatedControlEquality creates a new error of the ErrBatchCalculatedControlEquality type +func NewErrBatchCalculatedControlEquality(calculated, control interface{}) ErrBatchCalculatedControlEquality { + return ErrBatchCalculatedControlEquality{ + Message: fmt.Sprintf("calculated %v is out-of-balance with batch control %v", calculated, control), + CalculatedValue: calculated, + ControlValue: control, + } +} + +func (e ErrBatchCalculatedControlEquality) Error() string { + return e.Message +} + +// ErrBatchAscending is the error given when the trace numbers in a batch are not in ascending order +type ErrBatchAscending struct { + Message string + PreviousTrace interface{} + CurrentTrace interface{} +} + +// NewErrBatchAscending creates a new error of the ErrBatchAscending type +func NewErrBatchAscending(previous, current interface{}) ErrBatchAscending { + return ErrBatchAscending{ + Message: fmt.Sprintf("must be in ascending order, %v is less than or equal to last number %v", current, previous), + PreviousTrace: previous, + CurrentTrace: current, + } +} + +func (e ErrBatchAscending) Error() string { + return e.Message +} + +// ErrBatchCategory is the error given when a batch has entires with two different categories +type ErrBatchCategory struct { + Message string + CategoryA string + CategoryB string +} + +// NewErrBatchCategory creates a new error of the ErrBatchCategory type +func NewErrBatchCategory(categoryA, categoryB string) ErrBatchCategory { + return ErrBatchCategory{ + Message: fmt.Sprintf("%v category found in batch with category %v", categoryA, categoryB), + CategoryA: categoryA, + CategoryB: categoryB, + } +} + +func (e ErrBatchCategory) Error() string { + return e.Message +} + +// ErrBatchTraceNumberNotODFI is the error given when a batch's ODFI does not match an entry's trace number +type ErrBatchTraceNumberNotODFI struct { + Message string + ODFI string + TraceNumber string +} + +// NewErrBatchTraceNumberNotODFI creates a new error of the ErrBatchTraceNumberNotODFI type +func NewErrBatchTraceNumberNotODFI(odfi, trace string) ErrBatchTraceNumberNotODFI { + return ErrBatchTraceNumberNotODFI{ + Message: fmt.Sprintf("%v in header does not match entry trace number %v", odfi, trace), + ODFI: odfi, + TraceNumber: trace, + } +} + +func (e ErrBatchTraceNumberNotODFI) Error() string { + return e.Message +} + +// ErrBatchAddendaTraceNumber is the error given when the entry detail sequence number doesn't match the trace number +type ErrBatchAddendaTraceNumber struct { + Message string + EntryDetailNumber string + TraceNumber string +} + +// NewErrBatchAddendaTraceNumber creates a new error of the ErrBatchAddendaTraceNumber type +func NewErrBatchAddendaTraceNumber(entryDetail, trace string) ErrBatchAddendaTraceNumber { + return ErrBatchAddendaTraceNumber{ + Message: fmt.Sprintf("%v does not match proceeding entry detail trace number %v", entryDetail, trace), + EntryDetailNumber: entryDetail, + TraceNumber: trace, + } +} + +func (e ErrBatchAddendaTraceNumber) Error() string { + return e.Message +} + +// ErrBatchAddendaCount is the error given when there are too many addenda than allowed for the batch type +type ErrBatchAddendaCount struct { + Message string + FoundCount int + AllowedCount int +} + +// NewErrBatchAddendaCount creates a new error of the ErrBatchAddendaCount type +func NewErrBatchAddendaCount(found, allowed int) ErrBatchAddendaCount { + return ErrBatchAddendaCount{ + Message: fmt.Sprintf("%v addendum found where %v is allowed for this batch type", found, allowed), + FoundCount: found, + AllowedCount: allowed, + } +} + +func (e ErrBatchAddendaCount) Error() string { + return e.Message +} + +// ErrBatchRequiredAddendaCount is the error given when the batch type requires a certain number of addenda, which is not met +type ErrBatchRequiredAddendaCount struct { + Message string + FoundCount int + RequiredCount int +} + +// NewErrBatchRequiredAddendaCount creates a new error of the ErrBatchRequiredAddendaCount type +func NewErrBatchRequiredAddendaCount(found, required int) ErrBatchRequiredAddendaCount { + return ErrBatchRequiredAddendaCount{ + Message: fmt.Sprintf("%v addendum found where %v are required for this batch type", found, required), + FoundCount: found, + RequiredCount: required, + } +} + +func (e ErrBatchRequiredAddendaCount) Error() string { + return e.Message +} + +// ErrBatchExpectedAddendaCount is the error given when the batch type has entries with a field +// for the number of addenda, and a different number of addenda are foound +type ErrBatchExpectedAddendaCount struct { + Message string + FoundCount int + ExpectedCount int +} + +// NewErrBatchExpectedAddendaCount creates a new error of the ErrBatchExpectedAddendaCount type +func NewErrBatchExpectedAddendaCount(found, expected int) ErrBatchExpectedAddendaCount { + return ErrBatchExpectedAddendaCount{ + Message: fmt.Sprintf("%v addendum found where %v are Expected for this batch type", found, expected), + FoundCount: found, + ExpectedCount: expected, + } +} + +func (e ErrBatchExpectedAddendaCount) Error() string { + return e.Message +} + +// ErrBatchServiceClassTranCode is the error given when the transaction code is not valid for the batch's service class +type ErrBatchServiceClassTranCode struct { + Message string + ServiceClassCode int + TransactionCode int +} + +// NewErrBatchServiceClassTranCode creates a new error of the ErrBatchServiceClassTranCode type +func NewErrBatchServiceClassTranCode(serviceClassCode, transactionCode int) ErrBatchServiceClassTranCode { + return ErrBatchServiceClassTranCode{ + Message: fmt.Sprintf("service class code %v does not support transaction code %v", serviceClassCode, transactionCode), + ServiceClassCode: serviceClassCode, + TransactionCode: transactionCode, + } +} + +func (e ErrBatchServiceClassTranCode) Error() string { + return e.Message +} + +// ErrBatchAmount is the error given when the amount exceeds the batch type's limit +type ErrBatchAmount struct { + Message string + Amount int + Limit int +} + +// NewErrBatchAmount creates a new error of the ErrBatchAmount type +func NewErrBatchAmount(amount, limit int) ErrBatchAmount { + // TODO: pretty format the amounts to make it more readable + return ErrBatchAmount{ + Message: fmt.Sprintf("amounts in this batch type are limited to %v, found amount of %v", limit, amount), + Amount: amount, + Limit: limit, + } +} + +func (e ErrBatchAmount) Error() string { + return e.Message +} + +// ErrBatchIATNOC is the error given when an IAT batch has an NOC, and there are invalid values +type ErrBatchIATNOC struct { + Message string + Found interface{} + Expected interface{} +} + +// NewErrBatchIATNOC creates a new error of the ErrBatchIATNOC type +func NewErrBatchIATNOC(found, expected interface{}) ErrBatchIATNOC { + // TODO: pretty format the amounts to make it more readable + return ErrBatchIATNOC{ + Message: fmt.Sprintf("%v invalid for IAT NOC, should be %v", found, expected), + Found: found, + Expected: expected, + } +} + +func (e ErrBatchIATNOC) Error() string { + return e.Message +} diff --git a/batchHeader.go b/batchHeader.go index 8d14d54be..429b66c23 100644 --- a/batchHeader.go +++ b/batchHeader.go @@ -1,6 +1,19 @@ -// Copyright 2017 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. package ach @@ -9,40 +22,45 @@ import ( "strconv" "strings" "time" + "unicode/utf8" ) -// msgServiceClass - // BatchHeader identifies the originating entity and the type of transactions // contained in the batch (i.e., the standard entry class, PPD for consumer, CCD // or CTX for corporate). This record also contains the effective date, or desired // settlement date, for all entries contained in this batch. The settlement date // field is not entered as it is determined by the ACH operator type BatchHeader struct { - // RecordType defines the type of record in the block. 5 - recordType string + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` - // ServiceClassCode ACH Mixed Debits and Credits ‘200’ - // ACH Credits Only ‘220’ - // ACH Debits Only ‘225' - ServiceClassCode int + // ServiceClassCode ACH Mixed Debits and Credits '200' + // ACH Credits Only '220' + // ACH Debits Only '225' + ServiceClassCode int `json:"serviceClassCode"` // CompanyName the company originating the entries in the batch - CompanyName string + CompanyName string `json:"companyName"` // CompanyDiscretionaryData allows Originators and/or ODFIs to include codes (one or more), // of significance only to them, to enable specialized handling of all // subsequent entries in that batch. There will be no standardized // interpretation for the value of the field. This field must be returned // intact on any return entry. - CompanyDiscretionaryData string + CompanyDiscretionaryData string `json:"companyDiscretionaryData,omitempty"` // CompanyIdentification The 9 digit FEIN number (proceeded by a predetermined // alpha or numeric character) of the entity in the company name field - CompanyIdentification string + CompanyIdentification string `json:"companyIdentification"` - // StandardEntryClassCode PPD’ for consumer transactions, ‘CCD’ or ‘CTX’ for corporate - StandardEntryClassCode string + // StandardEntryClassCode + // Identifies the payment type (product) found within an ACH batch-using a 3-character code. + // The SEC Code pertains to all items within batch. + // Determines format of the detail records. + // Determines addenda records (required or optional PLUS one or up to 9,999 records). + // Determines rules to follow (return time frames). + // Some SEC codes require specific data in predetermined fields within the ACH record + StandardEntryClassCode string `json:"standardEntryClassCode"` // CompanyEntryDescription A description of the entries contained in the batch // @@ -59,96 +77,112 @@ type BatchHeader struct { // // This field must contain the word "NONSETTLED" (left justified) when the // batch contains entries which could not settle. - CompanyEntryDescription string - - // CompanyDescriptiveDate except as otherwise noted below, the Originator establishes this field - // as the date it would like to see displayed to the receiver for - // descriptive purposes. This field is never used to control timing of any - // computer or manual operation. It is solely for descriptive purposes. - // The RDFI should not assume any specific format. Examples of possible - // entries in this field are "011392,", "01 92," "JAN 13," "JAN 92," etc. - CompanyDescriptiveDate string + CompanyEntryDescription string `json:"companyEntryDescription,omitempty"` + // CompanyDescriptiveDate currently, the Rules provide that the “Originator establishes this field as the date it + // would like to see displayed to the Receiver for descriptive purposes.” NACHA recommends that, as desired, + // the content of this field be formatted using the convention “SDHHMM”, where the “SD” in positions 64- 65 denotes + // the intent for same-day settlement, and the hours and minutes in positions 66-69 denote the desired settlement + // time using a 24-hour clock. When electing to use this convention, the ODFI would validate that the field + // contains either. + // + // ODFIs at their discretion may require their Originators to further show intent for + // same-day settlement using an optional, yet standardized, same-day indicator in the Company Descriptive Date + // field. The Company Descriptive Date field (5 record, field 8) is an optional field with 6 positions available + // (positions 64-69). + CompanyDescriptiveDate string `json:"companyDescriptiveDate,omitempty"` - // EffectiveEntryDate the date on which the entries are to settle - EffectiveEntryDate time.Time + // EffectiveEntryDate the date on which the entries are to settle. Format: YYMMDD (Y=Year, M=Month, D=Day) + EffectiveEntryDate string `json:"effectiveEntryDate,omitempty"` // SettlementDate Leave blank, this field is inserted by the ACH operator - settlementDate string + SettlementDate string `json:"settlementDate,omitempty"` - // OriginatorStatusCode '1' - OriginatorStatusCode int + // OriginatorStatusCode refers to the ODFI initiating the Entry. + // 0 ADV File prepared by an ACH Operator. + // 1 This code identifies the Originator as a depository financial institution. + // 2 This code identifies the Originator as a Federal Government entity or agency. + OriginatorStatusCode int `json:"originatorStatusCode,omitempty"` //ODFIIdentification First 8 digits of the originating DFI transit routing number - ODFIIdentification int + ODFIIdentification string `json:"ODFIIdentification"` // BatchNumber is assigned in ascending sequence to each batch by the ODFI // or its Sending Point in a given file of entries. Since the batch number // in the Batch Header Record and the Batch Control Record is the same, // the ascending sequence number should be assigned by batch and not by // record. - BatchNumber int + BatchNumber int `json:"batchNumber"` // validator is composed for data validation validator // converters is composed for ACH to golang Converters converters + + validateOpts *ValidateOpts } -// NewBatchHeader returns a new BatchHeader with default values for none exported fields -func NewBatchHeader(params ...BatchParam) *BatchHeader { +const ( + // BatchHeader.ServiceClassCode and BatchControl.ServiceClassCode + + // MixedDebitsAndCredits indicates a batch can have debit and credit ACH entries + MixedDebitsAndCredits = 200 + // CreditsOnly indicates a batch can only have credit ACH entries + CreditsOnly = 220 + // DebitsOnly indicates a batch can only have debit ACH entries + DebitsOnly = 225 + // AutomatedAccountingAdvices indicates a batch can only have Automated Accounting Advices (debit and credit) + AutomatedAccountingAdvices = 280 +) + +// NewBatchHeader returns a new BatchHeader with default values for non exported fields +func NewBatchHeader() *BatchHeader { bh := &BatchHeader{ - recordType: "5", - OriginatorStatusCode: 1, + OriginatorStatusCode: 1, // Prepared by a financial institution BatchNumber: 1, } - if len(params) > 0 { - bh.ServiceClassCode = bh.parseNumField(params[0].ServiceClassCode) - bh.CompanyName = params[0].CompanyName - bh.CompanyIdentification = params[0].CompanyIdentification - bh.StandardEntryClassCode = params[0].StandardEntryClass - bh.CompanyEntryDescription = params[0].CompanyEntryDescription - bh.CompanyDescriptiveDate = params[0].CompanyDescriptiveDate - bh.EffectiveEntryDate = bh.parseSimpleDate(params[0].EffectiveEntryDate) - bh.ODFIIdentification = bh.parseNumField(params[0].ODFIIdentification) - return bh - } return bh } // Parse takes the input record string and parses the BatchHeader values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. func (bh *BatchHeader) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + // 1-1 Always "5" - bh.recordType = "5" - // 2-4 If the entries are credits, always "220". If the entries are debits, always "225" + // 2-4 MixedCreditsAnDebits (200), CreditsOnly (220), DebitsOnly (225) bh.ServiceClassCode = bh.parseNumField(record[1:4]) - // 5-20 Your company's name. This name may appear on the receivers’ statements prepared by the RDFI. + // 5-20 Your company's name. This name may appear on the receivers' statements prepared by the RDFI. bh.CompanyName = strings.TrimSpace(record[4:20]) // 21-40 Optional field you may use to describe the batch for internal accounting purposes bh.CompanyDiscretionaryData = strings.TrimSpace(record[20:40]) // 41-50 A 10-digit number assigned to you by the ODFI once they approve you to // originate ACH files through them. This is the same as the "Immediate origin" field in File Header Record bh.CompanyIdentification = strings.TrimSpace(record[40:50]) - // 51-53 If the entries are PPD (credits/debits towards consumer account), use "PPD". - // If the entries are CCD (credits/debits towards corporate account), use "CCD". - // The difference between the 2 class codes are outside of the scope of this post, but generally most ACH transfers to consumer bank accounts should use "PPD" + // 51-53 If the entries are PPD (credits/debits towards consumer account), use PPD. + // If the entries are CCD (credits/debits towards corporate account), use CCD. + // The difference between the 2 SEC codes are outside of the scope of this post. bh.StandardEntryClassCode = record[50:53] - // 54-63 Your description of the transaction. This text will appear on the receivers’ bank statement. + // 54-63 Your description of the transaction. This text will appear on the receivers' bank statement. // For example: "Payroll " bh.CompanyEntryDescription = strings.TrimSpace(record[53:63]) // 64-69 The date you choose to identify the transactions in YYMMDD format. - // This date may be printed on the receivers’ bank statement by the RDFI + // This date may be printed on the receivers' bank statement by the RDFI bh.CompanyDescriptiveDate = strings.TrimSpace(record[63:69]) - // 70-75 Date transactions are to be posted to the receivers’ account. + // 70-75 Date transactions are to be posted to the receivers' account. // You almost always want the transaction to post as soon as possible, so put tomorrow's date in YYMMDD format - bh.EffectiveEntryDate = bh.parseSimpleDate(record[69:75]) - // 76-79 Always blank (just fill with spaces) - bh.settlementDate = " " + bh.EffectiveEntryDate = bh.validateSimpleDate(record[69:75]) + // 76-78 Always blank if creating batches (just fill with spaces). + // Set to file value when parsing. Julian day format. + bh.SettlementDate = bh.validateSettlementDate(record[75:78]) // 79-79 Always 1 bh.OriginatorStatusCode = bh.parseNumField(record[78:79]) // 80-87 Your ODFI's routing number without the last digit. The last digit is simply a // checksum digit, which is why it is not necessary - bh.ODFIIdentification = bh.parseNumField(record[79:87]) + bh.ODFIIdentification = bh.parseStringField(record[79:87]) // 88-94 Sequential number of this Batch Header Record // For example, put "1" if this is the first Batch Header Record in the file bh.BatchNumber = bh.parseNumField(record[87:94]) @@ -156,21 +190,55 @@ func (bh *BatchHeader) Parse(record string) { // String writes the BatchHeader struct to a 94 character string. func (bh *BatchHeader) String() string { - return fmt.Sprintf("%v%v%v%v%v%v%v%v%v%v%v%v%v", - bh.recordType, - bh.ServiceClassCode, - bh.CompanyNameField(), - bh.CompanyDiscretionaryDataField(), - bh.CompanyIdentificationField(), - bh.StandardEntryClassCode, - bh.CompanyEntryDescriptionField(), - bh.CompanyDescriptiveDateField(), - bh.EffectiveEntryDateField(), - bh.settlementDateField(), - bh.OriginatorStatusCode, - bh.ODFIIdentificationField(), - bh.BatchNumberField(), - ) + var buf strings.Builder + buf.Grow(94) + buf.WriteString(batchHeaderPos) + buf.WriteString(fmt.Sprintf("%v", bh.ServiceClassCode)) + buf.WriteString(bh.CompanyNameField()) + buf.WriteString(bh.CompanyDiscretionaryDataField()) + buf.WriteString(bh.CompanyIdentificationField()) + buf.WriteString(bh.StandardEntryClassCode) + buf.WriteString(bh.CompanyEntryDescriptionField()) + buf.WriteString(bh.CompanyDescriptiveDateField()) + buf.WriteString(bh.EffectiveEntryDateField()) + buf.WriteString(bh.SettlementDateField()) + buf.WriteString(fmt.Sprintf("%v", bh.OriginatorStatusCode)) + buf.WriteString(bh.ODFIIdentificationField()) + buf.WriteString(bh.BatchNumberField()) + return buf.String() +} + +// Equal returns true only if two BatchHeaders are equal. +// Equality is determined by the Nacha defined fields of each record. +func (bh *BatchHeader) Equal(other *BatchHeader) bool { + if bh.ServiceClassCode != other.ServiceClassCode { + return false + } + if !strings.EqualFold(bh.CompanyName, other.CompanyName) { + return false + } + if bh.CompanyIdentification != other.CompanyIdentification { + return false + } + if bh.StandardEntryClassCode != other.StandardEntryClassCode { + return false + } + if bh.EffectiveEntryDate != other.EffectiveEntryDate { + return false + } + if bh.ODFIIdentification != other.ODFIIdentification { + return false + } + return true +} + +// SetValidation stores ValidateOpts on the BatchHeader which are to be used to override +// the default NACHA validation rules. +func (bh *BatchHeader) SetValidation(opts *ValidateOpts) { + if bh == nil { + return + } + bh.validateOpts = opts } // Validate performs NACHA format rule checks on the record and returns an error if not Validated @@ -179,63 +247,61 @@ func (bh *BatchHeader) Validate() error { if err := bh.fieldInclusion(); err != nil { return err } - if bh.recordType != "5" { - msg := fmt.Sprintf(msgRecordType, 5) - return &FieldError{FieldName: "recordType", Value: bh.recordType, Msg: msg} - } - if err := bh.isServiceClass(bh.ServiceClassCode); err != nil { - return &FieldError{FieldName: "ServiceClassCode", Value: strconv.Itoa(bh.ServiceClassCode), Msg: err.Error()} + if bh.validateOpts == nil || bh.validateOpts.CheckTransactionCode == nil { + // Ensure the ServiceClassCode follows NACHA standards if we have no TransactionCode + // validation overrides. Custom TransactionCode's don't allow for standard validation. + if err := bh.isServiceClass(bh.ServiceClassCode); err != nil { + return fieldError("ServiceClassCode", err, bh.ServiceClassCode) + } } if err := bh.isSECCode(bh.StandardEntryClassCode); err != nil { - return &FieldError{FieldName: "StandardEntryClassCode", Value: bh.StandardEntryClassCode, Msg: err.Error()} + return fieldError("StandardEntryClassCode", err, bh.StandardEntryClassCode) } if err := bh.isOriginatorStatusCode(bh.OriginatorStatusCode); err != nil { - return &FieldError{FieldName: "OriginatorStatusCode", Value: strconv.Itoa(bh.OriginatorStatusCode), Msg: err.Error()} + return fieldError("OriginatorStatusCode", err, bh.OriginatorStatusCode) + } + + // Originator status code 0 is used for ADV batches only + if bh.StandardEntryClassCode != ADV && bh.OriginatorStatusCode == 0 { + return fieldError("OriginatorStatusCode", ErrOrigStatusCode, bh.OriginatorStatusCode) } + if err := bh.isAlphanumeric(bh.CompanyName); err != nil { - return &FieldError{FieldName: "CompanyName", Value: bh.CompanyName, Msg: err.Error()} + return fieldError("CompanyName", err, bh.CompanyName) } if err := bh.isAlphanumeric(bh.CompanyDiscretionaryData); err != nil { - return &FieldError{FieldName: "CompanyDiscretionaryData", Value: bh.CompanyDiscretionaryData, Msg: err.Error()} + return fieldError("CompanyDiscretionaryData", err, bh.CompanyDiscretionaryData) } if err := bh.isAlphanumeric(bh.CompanyIdentification); err != nil { - return &FieldError{FieldName: "CompanyIdentification", Value: bh.CompanyIdentification, Msg: err.Error()} + return fieldError("CompanyIdentification", err, bh.CompanyIdentification) } if err := bh.isAlphanumeric(bh.CompanyEntryDescription); err != nil { - return &FieldError{FieldName: "CompanyEntryDescription", Value: bh.CompanyEntryDescription, Msg: err.Error()} + return fieldError("CompanyEntryDescription", err, bh.CompanyEntryDescription) } - return nil } // fieldInclusion validate mandatory fields are not default values. If fields are // invalid the ACH transfer will be returned. func (bh *BatchHeader) fieldInclusion() error { - if bh.recordType == "" { - return &FieldError{FieldName: "recordType", Value: bh.recordType, Msg: msgFieldInclusion} - } if bh.ServiceClassCode == 0 { - return &FieldError{FieldName: "ServiceClassCode", Value: strconv.Itoa(bh.ServiceClassCode), Msg: msgFieldInclusion} + return fieldError("ServiceClassCode", ErrConstructor, strconv.Itoa(bh.ServiceClassCode)) } if bh.CompanyName == "" { - return &FieldError{FieldName: "CompanyName", Value: bh.CompanyName, Msg: msgFieldInclusion} + return fieldError("CompanyName", ErrConstructor, bh.CompanyName) } if bh.CompanyIdentification == "" { - return &FieldError{FieldName: "CompanyIdentification", Value: bh.CompanyIdentification, Msg: msgFieldInclusion} + return fieldError("CompanyIdentification", ErrConstructor, bh.CompanyIdentification) } if bh.StandardEntryClassCode == "" { - return &FieldError{FieldName: "StandardEntryClassCode", Value: bh.StandardEntryClassCode, Msg: msgFieldInclusion} + return fieldError("StandardEntryClassCode", ErrConstructor, bh.StandardEntryClassCode) } if bh.CompanyEntryDescription == "" { - return &FieldError{FieldName: "CompanyEntryDescription", Value: bh.CompanyEntryDescription, Msg: msgFieldInclusion} + return fieldError("CompanyEntryDescription", ErrConstructor, bh.CompanyEntryDescription) } - if bh.OriginatorStatusCode == 0 { - return &FieldError{FieldName: "OriginatorStatusCode", Value: strconv.Itoa(bh.OriginatorStatusCode), Msg: msgFieldInclusion} + if bh.ODFIIdentification == "" { + return fieldError("ODFIIdentification", ErrConstructor, bh.ODFIIdentificationField()) } - if bh.ODFIIdentification == 0 { - return &FieldError{FieldName: "ODFIIdentification", Value: bh.ODFIIdentificationField(), Msg: msgFieldInclusion} - } - return nil } @@ -266,12 +332,16 @@ func (bh *BatchHeader) CompanyDescriptiveDateField() string { // EffectiveEntryDateField get the EffectiveEntryDate in YYMMDD format func (bh *BatchHeader) EffectiveEntryDateField() string { - return bh.formatSimpleDate(bh.EffectiveEntryDate) + // ENR records require EffectiveEntryDate to be space filled. NACHA Page OR108 + if bh.CompanyEntryDescription == "AUTOENROLL" { + return bh.alphaField("", 6) + } + return bh.stringField(bh.EffectiveEntryDate, 6) // YYMMDD } // ODFIIdentificationField get the odfi number zero padded func (bh *BatchHeader) ODFIIdentificationField() string { - return bh.numericField(bh.ODFIIdentification, 8) + return bh.stringField(bh.ODFIIdentification, 8) } // BatchNumberField get the batch number zero padded @@ -279,6 +349,10 @@ func (bh *BatchHeader) BatchNumberField() string { return bh.numericField(bh.BatchNumber, 7) } -func (bh *BatchHeader) settlementDateField() string { - return bh.alphaField(bh.settlementDate, 3) +func (bh *BatchHeader) SettlementDateField() string { + return bh.alphaField(bh.SettlementDate, 3) +} + +func (bh *BatchHeader) LiftEffectiveEntryDate() (time.Time, error) { + return time.Parse("060102", bh.EffectiveEntryDate) // YYMMDD } diff --git a/batchHeader_internal_test.go b/batchHeader_internal_test.go deleted file mode 100644 index cab77c8a9..000000000 --- a/batchHeader_internal_test.go +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright 2017 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. - -package ach - -import ( - "strings" - "testing" -) - -func mockBatchHeader() *BatchHeader { - bh := NewBatchHeader() - bh.ServiceClassCode = 220 - bh.StandardEntryClassCode = "PPD" - bh.CompanyName = "ACME Corporation" - bh.CompanyIdentification = "123456789" - bh.CompanyEntryDescription = "PAYROLL" - bh.ODFIIdentification = 6200001 - return bh -} - -func TestMockBatchHeader(t *testing.T) { - bh := mockBatchHeader() - if err := bh.Validate(); err != nil { - t.Error("mockBatchHeader does not validate and will break other tests") - } - if bh.ServiceClassCode != 220 { - t.Error("ServiceClassCode dependent default value has changed") - } - if bh.StandardEntryClassCode != "PPD" { - t.Error("StandardEntryClassCode dependent default value has changed") - } - if bh.CompanyName != "ACME Corporation" { - t.Error("CompanyName dependent default value has changed") - } - if bh.CompanyIdentification != "123456789" { - t.Error("CompanyIdentification dependent default value has changed") - } - if bh.CompanyEntryDescription != "PAYROLL" { - t.Error("CompanyEntryDescription dependent default value has changed") - } - if bh.ODFIIdentification != 6200001 { - t.Error("ODFIIdentification dependent default value has changed") - } -} - -// TestParseBatchHeader parses a known Batch Header Record string. -func TestParseBatchHeader(t *testing.T) { - var line = "5225companyname origid PPDCHECKPAYMT000002080730 1076401250000001" - r := NewReader(strings.NewReader(line)) - r.line = line - if err := r.parseBatchHeader(); err != nil { - t.Errorf("%T: %s", err, err) - } - record := r.currentBatch.GetHeader() - - if record.recordType != "5" { - t.Errorf("RecordType Expected '5' got: %v", record.recordType) - } - if record.ServiceClassCode != 225 { - t.Errorf("ServiceClassCode Expected '225' got: %v", record.ServiceClassCode) - } - if record.CompanyNameField() != "companyname " { - t.Errorf("CompanyName Expected 'companyname ' got: '%v'", record.CompanyNameField()) - } - if record.CompanyDiscretionaryDataField() != " " { - t.Errorf("CompanyDiscretionaryData Expected ' ' got: %v", record.CompanyDiscretionaryDataField()) - } - if record.CompanyIdentificationField() != "origid " { - t.Errorf("CompanyIdentification Expected 'origid ' got: %v", record.CompanyIdentificationField()) - } - if record.StandardEntryClassCode != "PPD" { - t.Errorf("StandardEntryClassCode Expected 'PPD' got: %v", record.StandardEntryClassCode) - } - if record.CompanyEntryDescriptionField() != "CHECKPAYMT" { - t.Errorf("CompanyEntryDescription Expected 'CHECKPAYMT' got: %v", record.CompanyEntryDescriptionField()) - } - if record.CompanyDescriptiveDate != "000002" { - t.Errorf("CompanyDescriptiveDate Expected '000002' got: %v", record.CompanyDescriptiveDate) - } - if record.EffectiveEntryDateField() != "080730" { - t.Errorf("EffectiveEntryDate Expected '080730' got: %v", record.EffectiveEntryDateField()) - } - if record.settlementDate != " " { - t.Errorf("SettlementDate Expected ' ' got: %v", record.settlementDate) - } - if record.OriginatorStatusCode != 1 { - t.Errorf("OriginatorStatusCode Expected 1 got: %v", record.OriginatorStatusCode) - } - if record.ODFIIdentificationField() != "07640125" { - t.Errorf("OdfiIdentification Expected '07640125' got: %v", record.ODFIIdentificationField()) - } - if record.BatchNumberField() != "0000001" { - t.Errorf("BatchNumber Expected '0000001' got: %v", record.BatchNumberField()) - } -} - -// TestBHString validats that a known parsed file can be return to a string of the same value -func TestBHString(t *testing.T) { - var line = "5225companyname origid PPDCHECKPAYMT000002080730 1076401250000001" - r := NewReader(strings.NewReader(line)) - r.line = line - if err := r.parseBatchHeader(); err != nil { - t.Errorf("%T: %s", err, err) - } - record := r.currentBatch.GetHeader() - - if record.String() != line { - t.Errorf("Strings do not match") - } -} - -// TestValidateBHRecordType ensure error if recordType is not 5 -func TestValidateBHRecordType(t *testing.T) { - bh := mockBatchHeader() - bh.recordType = "2" - if err := bh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "recordType" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -// TestInvalidServiceCode ensure error if service class is not valid -func TestInvalidServiceCode(t *testing.T) { - bh := mockBatchHeader() - bh.ServiceClassCode = 123 - if err := bh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "ServiceClassCode" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -// TestValidateInvalidServiceCode ensure error if service class is not valid -func TestInvalidSECCode(t *testing.T) { - bh := mockBatchHeader() - bh.StandardEntryClassCode = "123" - if err := bh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "StandardEntryClassCode" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -// TestInvalidOrigStatusCode ensure error if originator status code is not valid -func TestInvalidOrigStatusCode(t *testing.T) { - bh := mockBatchHeader() - bh.OriginatorStatusCode = 3 - if err := bh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "OriginatorStatusCode" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestBatchHeaderFieldInclusion(t *testing.T) { - bh := mockBatchHeader() - bh.BatchNumber = 0 - if err := bh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "BatchNumber" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestBatchHeaderCompanyNameAlphaNumeric(t *testing.T) { - bh := mockBatchHeader() - bh.CompanyName = "AT&T®" - if err := bh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "CompanyName" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestBatchCompanyDiscretionaryDataAlphaNumeric(t *testing.T) { - bh := mockBatchHeader() - bh.CompanyDiscretionaryData = "®" - if err := bh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "CompanyDiscretionaryData" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestBatchCompanyIdentificationAlphaNumeric(t *testing.T) { - bh := mockBatchHeader() - bh.CompanyIdentification = "®" - if err := bh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "CompanyIdentification" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestBatchCompanyEntryDescriptionAlphaNumeric(t *testing.T) { - bh := mockBatchHeader() - bh.CompanyEntryDescription = "P®YROLL" - if err := bh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "CompanyEntryDescription" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestBHFieldInclusionRecordType(t *testing.T) { - bh := mockBatchHeader() - bh.recordType = "" - if err := bh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "recordType" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestBHFieldInclusionCompanyName(t *testing.T) { - bh := mockBatchHeader() - bh.CompanyName = "" - if err := bh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "CompanyName" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestBHFieldInclusionCompanyIdentification(t *testing.T) { - bh := mockBatchHeader() - bh.CompanyIdentification = "" - if err := bh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "CompanyIdentification" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestBHFieldInclusionStandardEntryClassCode(t *testing.T) { - bh := mockBatchHeader() - bh.StandardEntryClassCode = "" - if err := bh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "StandardEntryClassCode" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestBHFieldInclusionCompanyEntryDescription(t *testing.T) { - bh := mockBatchHeader() - bh.CompanyEntryDescription = "" - if err := bh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "CompanyEntryDescription" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestBHFieldInclusionOriginatorStatusCode(t *testing.T) { - bh := mockBatchHeader() - bh.OriginatorStatusCode = 0 - if err := bh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "OriginatorStatusCode" { - t.Errorf("%T: %s", err, err) - } - } - } -} diff --git a/batchHeader_test.go b/batchHeader_test.go new file mode 100644 index 000000000..97a3395f5 --- /dev/null +++ b/batchHeader_test.go @@ -0,0 +1,525 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "testing" + + "github.com/moov-io/base" +) + +// mockBatchHeader creates a batch header +func mockBatchHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = PPD + bh.CompanyName = "ACME Corporation" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "PAYROLL" + bh.ODFIIdentification = "12104288" + return bh +} + +// testMockBatchHeader creates a batch header +func testMockBatchHeader(t testing.TB) { + bh := mockBatchHeader() + if err := bh.Validate(); err != nil { + t.Error("mockBatchHeader does not validate and will break other tests") + } + if bh.ServiceClassCode != CreditsOnly { + t.Error("ServiceClassCode dependent default value has changed") + } + if bh.StandardEntryClassCode != PPD { + t.Error("StandardEntryClassCode dependent default value has changed") + } + if bh.CompanyName != "ACME Corporation" { + t.Error("CompanyName dependent default value has changed") + } + if bh.CompanyIdentification != "121042882" { + t.Error("CompanyIdentification dependent default value has changed") + } + if bh.CompanyEntryDescription != "PAYROLL" { + t.Error("CompanyEntryDescription dependent default value has changed") + } + if bh.ODFIIdentification != "12104288" { + t.Error("ODFIIdentification dependent default value has changed") + } +} + +// TestMockBatchHeader tests creating a batch header +func TestMockBatchHeader(t *testing.T) { + testMockBatchHeader(t) +} + +// BenchmarkMockBatchHeader benchmarks creating a batch header +func BenchmarkMockBatchHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testMockBatchHeader(b) + } +} + +// testParseBatchHeader parses a known batch header record string +func testParseBatchHeader(t testing.TB) { + var line = "5225companyname origid PPDCHECKPAYMT000002190730 1076401250000001" + r := NewReader(strings.NewReader(line)) + r.line = line + if err := r.parseBatchHeader(); err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.currentBatch.GetHeader() + + if record.ServiceClassCode != DebitsOnly { + t.Errorf("ServiceClassCode Expected '225' got: %v", record.ServiceClassCode) + } + if record.CompanyNameField() != "companyname " { + t.Errorf("CompanyName Expected 'companyname ' got: '%v'", record.CompanyNameField()) + } + if record.CompanyDiscretionaryDataField() != " " { + t.Errorf("CompanyDiscretionaryData Expected ' ' got: %v", record.CompanyDiscretionaryDataField()) + } + if record.CompanyIdentificationField() != "origid " { + t.Errorf("CompanyIdentification Expected 'origid ' got: %v", record.CompanyIdentificationField()) + } + if record.StandardEntryClassCode != PPD { + t.Errorf("StandardEntryClassCode Expected 'PPD' got: %v", record.StandardEntryClassCode) + } + if record.CompanyEntryDescriptionField() != "CHECKPAYMT" { + t.Errorf("CompanyEntryDescription Expected 'CHECKPAYMT' got: %v", record.CompanyEntryDescriptionField()) + } + if record.CompanyDescriptiveDate != "000002" { + t.Errorf("CompanyDescriptiveDate Expected '000002' got: %v", record.CompanyDescriptiveDate) + } + if record.EffectiveEntryDateField() != "190730" { + t.Errorf("EffectiveEntryDate Expected '190730' got: %v", record.EffectiveEntryDateField()) + } + if record.SettlementDateField() != " " { + t.Errorf("SettlementDate Expected ' ' got: %v", record.SettlementDateField()) + } + if record.OriginatorStatusCode != 1 { + t.Errorf("OriginatorStatusCode Expected 1 got: %v", record.OriginatorStatusCode) + } + if record.ODFIIdentificationField() != "07640125" { + t.Errorf("OdfiIdentification Expected '07640125' got: %v", record.ODFIIdentificationField()) + } + if record.BatchNumberField() != "0000001" { + t.Errorf("BatchNumber Expected '0000001' got: %v", record.BatchNumberField()) + } +} + +// TestParseBatchHeader tests parsing a known batch header record string +func TestParseBatchHeader(t *testing.T) { + testParseBatchHeader(t) +} + +// BenchmarkParseBatchHeader benchmarks parsing a known batch header record string +func BenchmarkParseBatchHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testParseBatchHeader(b) + } +} + +// testBHString validates that a known parsed file can be return to a string of the same value +func testBHString(t testing.TB) { + var line = "5225companyname origid PPDCHECKPAYMT000002180730 1076401250000001" + r := NewReader(strings.NewReader(line)) + r.line = line + if err := r.parseBatchHeader(); err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.currentBatch.GetHeader() + + if v := record.String(); v != line { + t.Errorf("Strings do not match:\n v=%q\nline=%q", v, line) // these are aligned with spaces + } +} + +// TestBHString tests validating that a known parsed file can be return to a string of the same value +func TestBHString(t *testing.T) { + testBHString(t) +} + +// BenchmarkBHString benchmarks validating that a known parsed file can be return to a string of the same value +func BenchmarkBHString(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBHString(b) + } +} + +// testInvalidServiceCode validates error if service code is not valid +func testInvalidServiceCode(t testing.TB) { + bh := mockBatchHeader() + bh.ServiceClassCode = 123 + err := bh.Validate() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestInvalidServiceCode tests validating error if service code is not valid +func TestInvalidServiceCode(t *testing.T) { + testInvalidServiceCode(t) +} + +// BenchmarkInvalidServiceCode benchmarks validating error if service code is not valid +func BenchmarkInvalidServiceCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testInvalidServiceCode(b) + } +} + +// testValidateInvalidSECCode validates error if service class is not valid +func testInvalidSECCode(t testing.TB) { + bh := mockBatchHeader() + bh.StandardEntryClassCode = "123" + err := bh.Validate() + if !base.Match(err, ErrSECCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestInvalidSECCode tests validating error if service class is not valid +func TestInvalidSECCode(t *testing.T) { + testInvalidSECCode(t) +} + +// BenchmarkInvalidSECCode benchmarks validating error if service class is not valid +func BenchmarkInvalidSECCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testInvalidSECCode(b) + } +} + +// testInvalidOrigStatusCode validates error if originator status code is not valid +func testInvalidOrigStatusCode(t testing.TB) { + bh := mockBatchHeader() + bh.OriginatorStatusCode = 3 + err := bh.Validate() + if !base.Match(err, ErrOrigStatusCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestInvalidOrigStatusCode tests validating error if originator status code is not valid +func TestInvalidOrigStatusCode(t *testing.T) { + testInvalidOrigStatusCode(t) +} + +// BenchmarkInvalidOrigStatusCode benchmarks validating error if originator status code is not valid +func BenchmarkInvalidOrigStatusCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testInvalidOrigStatusCode(b) + } +} + +// testBatchHeaderFieldInclusion validates batch header field inclusion +func testBatchHeaderFieldInclusion(t testing.TB) { + bh := mockBatchHeader() + bh.BatchNumber = 0 + err := bh.Validate() + // TODO: are we expecting there to be no errors here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchHeaderFieldInclusion tests validating batch header field inclusion +func TestBatchHeaderFieldInclusion(t *testing.T) { + testBatchHeaderFieldInclusion(t) +} + +// BenchmarkBatchHeaderFieldInclusion benchmarks validating batch header field inclusion +func BenchmarkBatchHeaderFieldInclusion(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchHeaderFieldInclusion(b) + } +} + +// testBatchHeaderCompanyNameAlphaNumeric validates batch header company name is alphanumeric +func testBatchHeaderCompanyNameAlphaNumeric(t testing.TB) { + bh := mockBatchHeader() + bh.CompanyName = "AT&T®" + err := bh.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchHeaderCompanyNameAlphaNumeric tests validating batch header company name is alphanumeric +func TestBatchHeaderCompanyNameAlphaNumeric(t *testing.T) { + testBatchHeaderCompanyNameAlphaNumeric(t) +} + +// BenchmarkBatchHeaderCompanyNameAlphaNumeric benchmarks validating batch header company name is alphanumeric +func BenchmarkBatchHeaderCompanyNameAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchHeaderCompanyNameAlphaNumeric(b) + } +} + +// testBatchCompanyDiscretionaryDataAlphaNumeric validates company discretionary data is alphanumeric +func testBatchCompanyDiscretionaryDataAlphaNumeric(t testing.TB) { + bh := mockBatchHeader() + bh.CompanyDiscretionaryData = "®" + err := bh.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCompanyDiscretionaryDataAlphaNumeric tests validating company discretionary data is alphanumeric +func TestBatchCompanyDiscretionaryDataAlphaNumeric(t *testing.T) { + testBatchCompanyDiscretionaryDataAlphaNumeric(t) +} + +// BenchmarkBatchCompanyDiscretionaryDataAlphaNumeric benchmarks validating company discretionary data is alphanumeric +func BenchmarkBatchCompanyDiscretionaryDataAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCompanyDiscretionaryDataAlphaNumeric(b) + } +} + +// testBatchCompanyIdentificationAlphaNumeric validates company identification is alphanumeric +func testBatchCompanyIdentificationAlphaNumeric(t testing.TB) { + bh := mockBatchHeader() + bh.CompanyIdentification = "®" + err := bh.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCompanyIdentificationAlphaNumeric tests validating company identification is alphanumeric +func TestBatchCompanyIdentificationAlphaNumeric(t *testing.T) { + testBatchCompanyIdentificationAlphaNumeric(t) +} + +// BenchmarkBatchCompanyIdentificationAlphaNumeric benchmarks validating company identification is alphanumeric +func BenchmarkBatchCompanyIdentificationAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCompanyIdentificationAlphaNumeric(b) + } +} + +// testBatchCompanyEntryDescriptionAlphaNumeric validates company entry description is alphanumeric +func testBatchCompanyEntryDescriptionAlphaNumeric(t testing.TB) { + bh := mockBatchHeader() + bh.CompanyEntryDescription = "P®YROLL" + err := bh.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCompanyEntryDescriptionAlphaNumeric tests validating company entry description is alphanumeric +func TestBatchCompanyEntryDescriptionAlphaNumeric(t *testing.T) { + testBatchCompanyEntryDescriptionAlphaNumeric(t) +} + +// BenchmarkBatchCompanyEntryDescriptionAlphaNumeric benchmarks validating company entry description is alphanumeric +func BenchmarkBatchCompanyEntryDescriptionAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCompanyEntryDescriptionAlphaNumeric(b) + } +} + +// testBHFieldInclusionCompanyName validates company name field inclusion +func testBHFieldInclusionCompanyName(t testing.TB) { + bh := mockBatchHeader() + bh.CompanyName = "" + err := bh.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBHFieldInclusionCompanyName tests validating company name field inclusion +func TestBHFieldInclusionCompanyName(t *testing.T) { + testBHFieldInclusionCompanyName(t) +} + +// BenchmarkBHFieldInclusionCompanyName benchmarks validating company name field inclusion +func BenchmarkBHFieldInclusionCompanyName(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBHFieldInclusionCompanyName(b) + } +} + +// testBHFieldInclusionCompanyIdentification validates company identification field inclusion +func testBHFieldInclusionCompanyIdentification(t testing.TB) { + bh := mockBatchHeader() + bh.CompanyIdentification = "" + err := bh.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBHFieldInclusionCompanyIdentification tests validating company identification field inclusion +func TestBHFieldInclusionCompanyIdentification(t *testing.T) { + testBHFieldInclusionCompanyIdentification(t) +} + +// BenchmarkBHFieldInclusionCompanyIdentification benchmarks validating company identification field inclusion +func BenchmarkBHFieldInclusionCompanyIdentification(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBHFieldInclusionCompanyIdentification(b) + } +} + +// testBHFieldInclusionStandardEntryClassCode validates SEC Code field inclusion +func testBHFieldInclusionStandardEntryClassCode(t testing.TB) { + bh := mockBatchHeader() + bh.StandardEntryClassCode = "" + err := bh.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBHFieldInclusionStandardEntryClassCode tests validating SEC Code field inclusion +func TestBHFieldInclusionStandardEntryClassCode(t *testing.T) { + testBHFieldInclusionStandardEntryClassCode(t) +} + +// BenchmarkBHFieldInclusionStandardEntryClassCode benchmarks validating SEC Code field inclusion +func BenchmarkBHFieldInclusionStandardEntryClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBHFieldInclusionStandardEntryClassCode(b) + } +} + +// testBHFieldInclusionCompanyEntryDescription validates Company Entry Description field inclusion +func testBHFieldInclusionCompanyEntryDescription(t testing.TB) { + bh := mockBatchHeader() + bh.CompanyEntryDescription = "" + err := bh.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBHFieldInclusionCompanyEntryDescription tests validating Company Entry Description field inclusion +func Test(t *testing.T) { + testBHFieldInclusionCompanyEntryDescription(t) +} + +// BenchmarkBHFieldInclusionCompanyEntryDescription benchmarks validating Company Entry Description field inclusion +func BenchmarkBHFieldInclusionCompanyEntryDescription(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBHFieldInclusionCompanyEntryDescription(b) + } +} + +// testBHFieldInclusionOriginatorStatusCode validates Originator Status Code field inclusion +func testBHFieldInclusionOriginatorStatusCode(t testing.TB) { + bh := mockBatchHeader() + bh.OriginatorStatusCode = 0 + err := bh.Validate() + if !base.Match(err, ErrOrigStatusCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBHFieldInclusionOriginatorStatusCode tests validating Originator Status Code field inclusion +func TestBHFieldInclusionOriginatorStatusCode(t *testing.T) { + testBHFieldInclusionOriginatorStatusCode(t) +} + +// BenchmarkBHFieldInclusionOriginatorStatusCode benchmarks validating Originator Status Code field inclusion +func BenchmarkBHFieldInclusionOriginatorStatusCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBHFieldInclusionOriginatorStatusCode(b) + } +} + +// testBHFieldInclusionODFIIdentification validates ODFIIdentification field inclusion +func testBHFieldInclusionODFIIdentification(t testing.TB) { + bh := mockBatchHeader() + bh.ODFIIdentification = "" + err := bh.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBHFieldInclusionODFIIdentification tests validating ODFIIdentification field inclusion +func TestBHFieldInclusionODFIIdentification(t *testing.T) { + testBHFieldInclusionODFIIdentification(t) +} + +// BenchmarkBHFieldInclusionODFIIdentification benchmarks validating ODFIIdentification field inclusion +func BenchmarkBHFieldInclusionODFIIdentification(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBHFieldInclusionODFIIdentification(b) + } +} + +func TestBatchHeaderENR__EffectiveEntryDateField(t *testing.T) { + bh := mockBatchHeader() + + // ENR batches require EffectiveEntryDate to be space filled + bh.CompanyEntryDescription = "AUTOENROLL" + if v, ans := bh.EffectiveEntryDateField(), " "; v != ans { + t.Errorf("got %q (len=%d), expected space filled (len=6)", v, len(ans)) + } +} + +func TestBatchHeader__LiftEffectiveEntryDate(t *testing.T) { + bh := mockBatchHeader() + bh.EffectiveEntryDate = "190730" + + if tt, err := bh.LiftEffectiveEntryDate(); err != nil { + t.Fatal(err) + } else { + if tt.String() != "2019-07-30 00:00:00 +0000 UTC" { + t.Errorf("tt=%v", tt) + } + } + + bh.EffectiveEntryDate = "aaaaaaaa" + if _, err := bh.LiftEffectiveEntryDate(); err == nil { + t.Error("expected error") + } +} + +func TestBatchHeader__SetValidation(t *testing.T) { + bh := NewBatchHeader() + bh.SetValidation(&ValidateOpts{}) + + // nil out + bh = nil + bh.SetValidation(&ValidateOpts{}) +} diff --git a/batchMTE.go b/batchMTE.go new file mode 100644 index 000000000..77b14a116 --- /dev/null +++ b/batchMTE.go @@ -0,0 +1,94 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + + "github.com/moov-io/ach/internal/usabbrev" +) + +// BatchMTE holds the BatchHeader, BatchControl, and EntryDetail for Machine Transfer Entry (MTE) entries. +// +// A MTE transaction is created when a consumer uses their debit card at an Automated Teller Machine (ATM) to withdraw cash. +// MTE transactions cannot be aggregated together under a single Entry. +type BatchMTE struct { + Batch +} + +// NewBatchMTE returns a *BatchMTE +func NewBatchMTE(bh *BatchHeader) *BatchMTE { + batch := new(BatchMTE) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} + +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the batch. +func (batch *BatchMTE) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + + if batch.Header.StandardEntryClassCode != MTE { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, MTE) + } + + for _, entry := range batch.Entries { + if entry.Amount <= 0 { + return batch.Error("Amount", ErrBatchAmountZero, entry.Amount) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + if entry.Category == CategoryForward { + if !usabbrev.Valid(entry.Addenda02.TerminalState) { + return batch.Error("TerminalState", ErrValidState, entry.Addenda02.TerminalState) + } + } + + // MTE entries cannot have an identification number that is all spaces or all zeros + if strings.Trim(entry.IdentificationNumber, " 0") == "" { + return batch.Error("IdentificationNumber", ErrIdentificationNumber, entry.IdentificationNumber) + } + } + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchMTE) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + return batch.Validate() +} diff --git a/batchMTE_test.go b/batchMTE_test.go new file mode 100644 index 000000000..f6c361616 --- /dev/null +++ b/batchMTE_test.go @@ -0,0 +1,272 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "log" + "testing" + "time" + + "github.com/moov-io/base" +) + +// mockBatchMTEHeader creates a MTE batch header +func mockBatchMTEHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.CompanyName = "Merchant with ATM" + bh.CompanyIdentification = "231380104" + bh.StandardEntryClassCode = MTE + bh.CompanyEntryDescription = "CASH WITHDRAW" + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "23138010" + return bh +} + +// mockMTEEntryDetail creates a MTE entry detail +func mockMTEEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingDebit + entry.SetRDFI("031300012") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 10000 + entry.SetOriginalTraceNumber("031300010000001") + entry.SetReceivingCompany("JANE DOE") + entry.SetTraceNumber("23138010", 1) + entry.AddendaRecordIndicator = 1 + + addenda02 := NewAddenda02() + + // NACHA rules example: 200509*321 East Market Street*Anytown*VA\ + addenda02.TerminalIdentificationCode = "200509" + addenda02.TerminalLocation = "321 East Market Street" + addenda02.TerminalCity = "ANYTOWN" + addenda02.TerminalState = "VA" + + addenda02.TransactionSerialNumber = "123456" // Generated by Terminal, used for audits + addenda02.TransactionDate = "1224" + addenda02.TraceNumber = entry.TraceNumber + entry.Addenda02 = addenda02 + + return entry +} + +// mockBatchMTE creates a MTE batch +func mockBatchMTE() *BatchMTE { + batch := NewBatchMTE(mockBatchMTEHeader()) + batch.AddEntry(mockMTEEntryDetail()) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + return batch +} + +// testBatchMTEHeader creates a MTE batch header +func testBatchMTEHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchMTEHeader()) + _, ok := batch.(*BatchMTE) + if !ok { + t.Error("Expecting BatchMTE") + } +} + +// TestBatchMTEHeader tests creating a MTE batch header +func TestBatchMTEHeader(t *testing.T) { + testBatchMTEHeader(t) +} + +// BenchmarkBatchMTEHeader benchmark creating a MTE batch header +func BenchmarkBatchMTEHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchMTEHeader(b) + } +} + +// TestBatchMTEAddendum02 validates Addenda02 returns an error +func TestBatchMTEAddendum02(t *testing.T) { + mockBatch := NewBatchMTE(mockBatchMTEHeader()) + mockBatch.AddEntry(mockMTEEntryDetail()) + err := mockBatch.Create() + // TODO: are we expecting there to be an error here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// testBatchMTEReceivingCompanyName validates Receiving company / Individual name is a mandatory field +func testBatchMTEReceivingCompanyName(t testing.TB) { + mockBatch := mockBatchMTE() + // modify the Individual name / receiving company to nothing + mockBatch.GetEntries()[0].SetReceivingCompany("") + err := mockBatch.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchMTEReceivingCompanyName tests validating receiving company / Individual name is a mandatory field +func TestBatchMTEReceivingCompanyName(t *testing.T) { + testBatchMTEReceivingCompanyName(t) +} + +// BenchmarkBatchMTEReceivingCompanyName benchmarks validating receiving company / Individual name is a mandatory field +func BenchmarkBatchMTEReceivingCompanyName(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchMTEReceivingCompanyName(b) + } +} + +// testBatchMTEAddendaTypeCode validates addenda type code is 05 +func testBatchMTEAddendaTypeCode(t testing.TB) { + mockBatch := mockBatchMTE() + mockBatch.GetEntries()[0].Addenda02.TypeCode = "05" + err := mockBatch.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchMTEAddendaTypeCode tests validating addenda type code is 05 +func TestBatchMTEAddendaTypeCode(t *testing.T) { + testBatchMTEAddendaTypeCode(t) +} + +// BenchmarkBatchMTEAddendaTypeCod benchmarks validating addenda type code is 05 +func BenchmarkBatchMTEAddendaTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchMTEAddendaTypeCode(b) + } +} + +// testBatchMTESEC validates that the standard entry class code is MTE for batchMTE +func testBatchMTESEC(t testing.TB) { + mockBatch := mockBatchMTE() + mockBatch.Header.StandardEntryClassCode = ACK + err := mockBatch.Validate() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchMTESEC tests validating that the standard entry class code is MTE for batchMTE +func TestBatchMTESEC(t *testing.T) { + testBatchMTESEC(t) +} + +// BenchmarkBatchMTESEC benchmarks validating that the standard entry class code is MTE for batch MTE +func BenchmarkBatchMTESEC(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchMTESEC(b) + } +} + +// testBatchMTEServiceClassCode validates ServiceClassCode +func testBatchMTEServiceClassCode(t testing.TB) { + mockBatch := mockBatchMTE() + // Batch Header information is required to Create a batch. + mockBatch.GetHeader().ServiceClassCode = 0 + err := mockBatch.Create() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchMTEServiceClassCode tests validating ServiceClassCode +func TestBatchMTEServiceClassCode(t *testing.T) { + testBatchMTEServiceClassCode(t) +} + +// BenchmarkBatchMTEServiceClassCode benchmarks validating ServiceClassCode +func BenchmarkBatchMTEServiceClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchMTEServiceClassCode(b) + } +} + +// TestBatchMTEAmount validates Amount +func TestBatchMTEAmount(t *testing.T) { + mockBatch := mockBatchMTE() + mockBatch.GetEntries()[0].Amount = 0 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAmountZero) { + t.Errorf("%T: %s", err, err) + } +} + +func TestBatchMTETerminalState(t *testing.T) { + mockBatch := mockBatchMTE() + mockBatch.GetEntries()[0].Addenda02.TerminalState = "XX" + err := mockBatch.Create() + if !base.Match(err, ErrValidState) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchMTEIndividualName validates IndividualName +func TestBatchMTEIndividualName(t *testing.T) { + mockBatch := mockBatchMTE() + mockBatch.GetEntries()[0].IndividualName = "" + err := mockBatch.Create() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchMTEIdentificationNumber validates IdentificationNumber +func TestBatchMTEIdentificationNumber(t *testing.T) { + mockBatch := mockBatchMTE() + + // NACHA rules state MTE records can't be all spaces or all zeros + mockBatch.GetEntries()[0].IdentificationNumber = " " + err := mockBatch.Validate() + if !base.Match(err, ErrIdentificationNumber) { + t.Errorf("%T: %s", err, err) + } + + mockBatch.GetEntries()[0].IdentificationNumber = "000000" + err = mockBatch.Validate() + if !base.Match(err, ErrIdentificationNumber) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchMTEValidTranCodeForServiceClassCode validates a transactionCode based on ServiceClassCode +func TestBatchMTEValidTranCodeForServiceClassCode(t *testing.T) { + mockBatch := mockBatchMTE() + mockBatch.GetHeader().ServiceClassCode = CreditsOnly + err := mockBatch.Create() + if !base.Match(err, NewErrBatchServiceClassTranCode(CreditsOnly, 27)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchMTEAddenda05 validates BatchMTE cannot have Addenda05 +func TestBatchMTEAddenda05(t *testing.T) { + mockBatch := mockBatchMTE() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} diff --git a/batchPOP.go b/batchPOP.go new file mode 100644 index 000000000..a007a339d --- /dev/null +++ b/batchPOP.go @@ -0,0 +1,108 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +// BatchPOP holds the BatchHeader and BatchControl and all EntryDetail for POP Entries. +// +// Point-of-Purchase. A check presented in-person to a merchant for purchase is presented +// as an ACH entry instead of a physical check. +// +// This ACH debit application is used by originators as a method of payment for the +// in-person purchase of goods or services by consumers. These Single Entry debit +// entries are initiated by the originator based on a written authorization and +// account information drawn from the source document (a check) obtained from the +// consumer at the point-of-purchase. The source document, which is voided by the +// merchant and returned to the consumer at the point-of-purchase, is used to +// collect the consumer's routing number, account number and check serial number that +// will be used to generate the debit entry to the consumer's account. +// +// The difference between POP and ARC is that ARC can result from a check mailed in whereas POP is in-person. +type BatchPOP struct { + Batch +} + +// NewBatchPOP returns a *BatchPOP +func NewBatchPOP(bh *BatchHeader) *BatchPOP { + batch := new(BatchPOP) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} + +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the batch. +func (batch *BatchPOP) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + + // Add configuration and type specific validation for this type. + if batch.Header.StandardEntryClassCode != POP { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, POP) + } + + // POP detail entries can only be a debit, ServiceClassCode must allow debits + switch batch.Header.ServiceClassCode { + case CreditsOnly: + return batch.Error("ServiceClassCode", ErrBatchServiceClassCode, batch.Header.ServiceClassCode) + } + + for _, entry := range batch.Entries { + // POP detail entries must be a debit + if entry.CreditOrDebit() != "D" { + return batch.Error("TransactionCode", ErrBatchDebitOnly, entry.TransactionCode) + } + // Amount must be 25,000 or less + if entry.Amount > 2500000 { + return batch.Error("Amount", NewErrBatchAmount(entry.Amount, 2500000)) + } + // CheckSerialNumber, Terminal City, Terminal State underlying IdentificationNumber, must be defined + if entry.IdentificationNumber == "" { + return batch.Error("CheckSerialNumber", ErrBatchCheckSerialNumber) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + } + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchPOP) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + // Additional steps specific to batch type + // ... + + return batch.Validate() +} diff --git a/batchPOP_test.go b/batchPOP_test.go new file mode 100644 index 000000000..9cc2c75b2 --- /dev/null +++ b/batchPOP_test.go @@ -0,0 +1,491 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockBatchPOPHeader creates a BatchPOP BatchHeader +func mockBatchPOPHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = POP + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "Point of Purchase" + bh.ODFIIdentification = "12104288" + return bh +} + +// mockPOPEntryDetail creates a BatchPOP EntryDetail +func mockPOPEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.SetPOPCheckSerialNumber("123456789") + entry.SetPOPTerminalCity("PHIL") + entry.SetPOPTerminalState("PA") + entry.SetReceivingCompany("ABC Company") + entry.SetTraceNumber(mockBatchPOPHeader().ODFIIdentification, 1) + entry.Category = CategoryForward + return entry +} + +// mockBatchPOP creates a BatchPOP +func mockBatchPOP() *BatchPOP { + mockBatch := NewBatchPOP(mockBatchPOPHeader()) + mockBatch.AddEntry(mockPOPEntryDetail()) + if err := mockBatch.Create(); err != nil { + panic(err) + } + return mockBatch +} + +// mockBatchPOPHeaderCredit creates a BatchPOP BatchHeader +func mockBatchPOPHeaderCredit() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = POP + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = POP + bh.ODFIIdentification = "12104288" + return bh +} + +// mockPOPEntryDetailCredit creates a POP EntryDetail with a credit entry +func mockPOPEntryDetailCredit() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.SetPOPCheckSerialNumber("123456789") + entry.SetPOPTerminalCity("PHIL") + entry.SetPOPTerminalState("PA") + entry.SetReceivingCompany("ABC Company") + entry.SetTraceNumber(mockBatchPOPHeader().ODFIIdentification, 123) + entry.Category = CategoryForward + return entry +} + +// mockBatchPOPCredit creates a BatchPOP with a Credit entry +func mockBatchPOPCredit() *BatchPOP { + mockBatch := NewBatchPOP(mockBatchPOPHeaderCredit()) + mockBatch.AddEntry(mockPOPEntryDetailCredit()) + return mockBatch +} + +// testBatchPOPHeader creates a BatchPOP BatchHeader +func testBatchPOPHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchPOPHeader()) + err, ok := batch.(*BatchPOP) + if !ok { + t.Errorf("Expecting BatchPOP got %T", err) + } +} + +// TestBatchPOPHeader tests validating BatchPOP BatchHeader +func TestBatchPOPHeader(t *testing.T) { + testBatchPOPHeader(t) +} + +// BenchmarkBatchPOPHeader benchmarks validating BatchPOP BatchHeader +func BenchmarkBatchPOPHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOPHeader(b) + } +} + +// testBatchPOPCreate validates BatchPOP create +func testBatchPOPCreate(t testing.TB) { + mockBatch := mockBatchPOP() + if err := mockBatch.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOPCreate tests validating BatchPOP create +func TestBatchPOPCreate(t *testing.T) { + testBatchPOPCreate(t) +} + +// BenchmarkBatchPOPCreate benchmarks validating BatchPOP create +func BenchmarkBatchPOPCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOPCreate(b) + } +} + +// testBatchPOPStandardEntryClassCode validates BatchPOP create for an invalid StandardEntryClassCode +func testBatchPOPStandardEntryClassCode(t testing.TB) { + mockBatch := mockBatchPOP() + mockBatch.Header.StandardEntryClassCode = WEB + err := mockBatch.Create() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOPStandardEntryClassCode tests validating BatchPOP create for an invalid StandardEntryClassCode +func TestBatchPOPStandardEntryClassCode(t *testing.T) { + testBatchPOPStandardEntryClassCode(t) +} + +// BenchmarkBatchPOPStandardEntryClassCode benchmarks validating BatchPOP create for an invalid StandardEntryClassCode +func BenchmarkBatchPOPStandardEntryClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOPStandardEntryClassCode(b) + } +} + +// testBatchPOPServiceClassCodeEquality validates service class code equality +func testBatchPOPServiceClassCodeEquality(t testing.TB) { + mockBatch := mockBatchPOP() + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(220, MixedDebitsAndCredits)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOPServiceClassCodeEquality tests validating service class code equality +func TestBatchPOPServiceClassCodeEquality(t *testing.T) { + testBatchPOPServiceClassCodeEquality(t) +} + +// BenchmarkBatchPOPServiceClassCodeEquality benchmarks validating service class code equality +func BenchmarkBatchPOPServiceClassCodeEquality(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOPServiceClassCodeEquality(b) + } +} + +// testBatchPOPMixedCreditsAndDebits validates BatchPOP create for an invalid MixedCreditsAndDebits +func testBatchPOPMixedCreditsAndDebits(t testing.TB) { + mockBatch := mockBatchPOP() + mockBatch.Header.ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(MixedDebitsAndCredits, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOPMixedCreditsAndDebits tests validating BatchPOP create for an invalid MixedCreditsAndDebits +func TestBatchPOPMixedCreditsAndDebits(t *testing.T) { + testBatchPOPMixedCreditsAndDebits(t) +} + +// BenchmarkBatchPOPMixedCreditsAndDebits benchmarks validating BatchPOP create for an invalid MixedCreditsAndDebits +func BenchmarkBatchPOPMixedCreditsAndDebits(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOPMixedCreditsAndDebits(b) + } +} + +// testBatchPOPCreditsOnly validates BatchPOP create for an invalid CreditsOnly +func testBatchPOPCreditsOnly(t testing.TB) { + mockBatch := mockBatchPOP() + mockBatch.Header.ServiceClassCode = CreditsOnly + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(CreditsOnly, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOPCreditsOnly tests validating BatchPOP create for an invalid CreditsOnly +func TestBatchPOPCreditsOnly(t *testing.T) { + testBatchPOPCreditsOnly(t) +} + +// BenchmarkBatchPOPCreditsOnly benchmarks validating BatchPOP create for an invalid CreditsOnly +func BenchmarkBatchPOPCreditsOnly(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOPCreditsOnly(b) + } +} + +// testBatchPOPAutomatedAccountingAdvices validates BatchPOP create for an invalid AutomatedAccountingAdvices +func testBatchPOPAutomatedAccountingAdvices(t testing.TB) { + mockBatch := mockBatchPOP() + mockBatch.Header.ServiceClassCode = AutomatedAccountingAdvices + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(MixedDebitsAndCredits, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOPAutomatedAccountingAdvices tests validating BatchPOP create for an invalid AutomatedAccountingAdvices +func TestBatchPOPAutomatedAccountingAdvices(t *testing.T) { + testBatchPOPAutomatedAccountingAdvices(t) +} + +// BenchmarkBatchPOPAutomatedAccountingAdvices benchmarks validating BatchPOP create for an invalid AutomatedAccountingAdvices +func BenchmarkBatchPOPAutomatedAccountingAdvices(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOPAutomatedAccountingAdvices(b) + } +} + +// testBatchPOPAmount validates BatchPOP create for an invalid Amount +func testBatchPOPAmount(t testing.TB) { + mockBatch := mockBatchPOP() + mockBatch.Entries[0].Amount = 2600000 + err := mockBatch.Create() + if !base.Match(err, NewErrBatchAmount(2600000, 2500000)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOPAmount validates BatchPOP create for an invalid Amount +func TestBatchPOPAmount(t *testing.T) { + testBatchPOPAmount(t) +} + +// BenchmarkBatchPOPAmount validates BatchPOP create for an invalid Amount +func BenchmarkBatchPOPAmount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOPAmount(b) + } +} + +// testBatchPOPCheckSerialNumber validates BatchPOP CheckSerialNumber / IdentificationNumber is a mandatory field +func testBatchPOPCheckSerialNumber(t testing.TB) { + mockBatch := mockBatchPOP() + // modify CheckSerialNumber / IdentificationNumber to nothing + mockBatch.GetEntries()[0].SetCheckSerialNumber("") + err := mockBatch.Validate() + if !base.Match(err, ErrBatchCheckSerialNumber) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOPCheckSerialNumber tests validating BatchPOP +// CheckSerialNumber / IdentificationNumber is a mandatory field +func TestBatchPOPCheckSerialNumber(t *testing.T) { + testBatchPOPCheckSerialNumber(t) +} + +// BenchmarkBatchPOPCheckSerialNumber benchmarks validating BatchPOP +// CheckSerialNumber / IdentificationNumber is a mandatory field +func BenchmarkBatchPOPCheckSerialNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOPCheckSerialNumber(b) + } +} + +// testBatchPOPCheckSerialNumberField validates POPCheckSerialNumberField characters 1-9 of underlying BatchPOP +// CheckSerialNumber / IdentificationNumber +func testBatchPOPCheckSerialNumberField(t testing.TB) { + mockBatch := mockBatchPOP() + tc := mockBatch.Entries[0].POPCheckSerialNumberField() + if tc != "123456789" { + t.Error("CheckSerialNumber is invalid") + } +} + +// TestBatchPPOPCheckSerialNumberField tests validating POPCheckSerialNumberField characters 1-9 of underlying BatchPOP +// CheckSerialNumber / IdentificationNumber +func TestBatchPOPCheckSerialNumberField(t *testing.T) { + testBatchPOPCheckSerialNumberField(t) +} + +// BenchmarkBatchPOPCheckSerialNumberField benchmarks validating POPCheckSerialNumberField characters 1-9 of underlying +// BatchPOP CheckSerialNumber / IdentificationNumber +func BenchmarkBatchPOPCheckSerialNumberField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOPTerminalCityField(b) + } +} + +// testBatchPOPTerminalCityField validates POPTerminalCity characters 10-13 of underlying BatchPOP +// CheckSerialNumber / IdentificationNumber +func testBatchPOPTerminalCityField(t testing.TB) { + mockBatch := mockBatchPOP() + tc := mockBatch.Entries[0].POPTerminalCityField() + if tc != "PHIL" { + t.Error("TerminalCity is invalid") + } +} + +// TestBatchPOPTerminalCityField tests validating POPTerminalCity characters 10-13 of underlying BatchPOP +// CheckSerialNumber / IdentificationNumber +func TestBatchPOPTerminalCityField(t *testing.T) { + testBatchPOPTerminalCityField(t) +} + +// BenchmarkBatchPOPTerminalCityField benchmarks validating POPTerminalCity characters 10-13 of underlying +// BatchPOP CheckSerialNumber / IdentificationNumber +func BenchmarkBatchPOPTerminalCityField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOPTerminalCityField(b) + } +} + +// testBatchPOPTerminalStateField validates POPTerminalState characters 14-15 of underlying BatchPOP +// CheckSerialNumber / IdentificationNumber +func testBatchPOPTerminalStateField(t testing.TB) { + mockBatch := mockBatchPOP() + ts := mockBatch.Entries[0].POPTerminalStateField() + if ts != "PA" { + t.Error("TerminalState is invalid") + } +} + +// TestBatchPOPTerminalStateField tests validating POPTerminalState characters 14-15 of underlying BatchPOP +// CheckSerialNumber / IdentificationNumber +func TestBatchPOPTerminalStateField(t *testing.T) { + testBatchPOPTerminalStateField(t) +} + +// BenchmarkBatchPOPTerminalStateField benchmarks validating POPTerminalState characters 14-15 of underlying +// BatchPOP CheckSerialNumber / IdentificationNumber +func BenchmarkBatchPOPTerminalStateField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOPTerminalStateField(b) + } +} + +// testBatchPOPTransactionCode validates BatchPOP TransactionCode is not a credit +func testBatchPOPTransactionCode(t testing.TB) { + mockBatch := mockBatchPOPCredit() + err := mockBatch.Create() + if !base.Match(err, ErrBatchDebitOnly) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOPTransactionCode tests validating BatchPOP TransactionCode is not a credit +func TestBatchPOPTransactionCode(t *testing.T) { + testBatchPOPTransactionCode(t) +} + +// BenchmarkBatchPOPTransactionCode benchmarks validating BatchPOP TransactionCode is not a credit +func BenchmarkBatchPOPTransactionCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOPTransactionCode(b) + } +} + +// testBatchPOPAddendaCount validates BatchPOP Addenda count +func testBatchPOPAddendaCount(t testing.TB) { + mockBatch := mockBatchPOP() + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOPAddendaCount tests validating BatchPOP Addenda count +func TestBatchPOPAddendaCount(t *testing.T) { + testBatchPOPAddendaCount(t) +} + +// BenchmarkBatchPOPAddendaCount benchmarks validating BatchPOP Addenda count +func BenchmarkBatchPOPAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchARCAddendaCount(b) + } +} + +// testBatchPOPInvalidBuild validates an invalid batch build +func testBatchPOPInvalidBuild(t testing.TB) { + mockBatch := mockBatchPOP() + mockBatch.GetHeader().ServiceClassCode = 3 + err := mockBatch.Create() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOPInvalidBuild tests validating an invalid batch build +func TestBatchPOPInvalidBuild(t *testing.T) { + testBatchPOPInvalidBuild(t) +} + +// BenchmarkBatchPOPInvalidBuild benchmarks validating an invalid batch build +func BenchmarkBatchPOPInvalidBuild(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOPInvalidBuild(b) + } +} + +// TestBatchPOPAddendum98 validates Addenda98 returns an error +func TestBatchPOPAddendum98(t *testing.T) { + mockBatch := NewBatchPOP(mockBatchPOPHeader()) + mockBatch.AddEntry(mockPOPEntryDetail()) + mockAddenda98 := mockAddenda98() + mockAddenda98.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOPAddendum99 validates Addenda99 returns an error +func TestBatchPOPAddendum99(t *testing.T) { + mockBatch := NewBatchPOP(mockBatchPOPHeader()) + mockBatch.AddEntry(mockPOPEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +func testBatchPOPMixedDebitsAndCredits(t testing.TB) { + mockBatch := mockBatchPOP() + mockBatch.Header.ServiceClassCode = MixedDebitsAndCredits + mockBatch.Batch.Control.ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOPMixedDebitsAndCredits tests validating BatchPOP create for MixedDebitsAndCredits with debit transaction code +func TestBatchPOPMixedDebitsAndCredits(t *testing.T) { + testBatchPOPMixedDebitsAndCredits(t) +} diff --git a/batchPOS.go b/batchPOS.go new file mode 100644 index 000000000..20d0ec5cc --- /dev/null +++ b/batchPOS.go @@ -0,0 +1,101 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "github.com/moov-io/ach/internal/usabbrev" +) + +// BatchPOS holds the BatchHeader and BatchControl and all EntryDetail for POS Entries. +// +// A POS Entry is a debit Entry initiated at an “electronic terminal” to a consumer +// account of the receiver to pay an obligation incurred in a point- of-sale +// transaction, or to effect a point-of-sale terminal cash withdrawal. +// +// Point-of-Sale Entries (POS) are ACH debit entries typically initiated by the use +// of a merchant-issued plastic card to pay an obligation at the point-of-sale. Much +// like a financial institution issued debit card, the merchant- issued debit card is +// swiped at the point-of-sale and approved for use; however, the authorization only +// verifies the card is open, active and within the card's limits—it does not verify +// the Receiver's account balance or debit the account at the time of the purchase. +// Settlement of the transaction moves from the card network to the ACH Network through +// the creation of a POS entry by the card issuer to debit the Receiver's account. +type BatchPOS struct { + Batch +} + +// NewBatchPOS returns a *BatchPOS +func NewBatchPOS(bh *BatchHeader) *BatchPOS { + batch := new(BatchPOS) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} + +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the batch. +func (batch *BatchPOS) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + + // Add configuration and type specific validation for this type. + + if batch.Header.StandardEntryClassCode != POS { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, POS) + } + + for _, entry := range batch.Entries { + if err := entry.isCardTransactionType(entry.DiscretionaryData); err != nil { + return batch.Error("CardTransactionType", ErrBatchInvalidCardTransactionType, entry.DiscretionaryData) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + if entry.Category == CategoryForward { + if !usabbrev.Valid(entry.Addenda02.TerminalState) { + return batch.Error("TerminalState", ErrValidState, entry.Addenda02.TerminalState) + } + } + } + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchPOS) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + // Additional steps specific to batch type + // ... + return batch.Validate() +} diff --git a/batchPOS_test.go b/batchPOS_test.go new file mode 100644 index 000000000..e25177115 --- /dev/null +++ b/batchPOS_test.go @@ -0,0 +1,519 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockBatchPOSHeader creates a BatchPOS BatchHeader +func mockBatchPOSHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = POS + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH POS" + bh.ODFIIdentification = "12104288" + return bh +} + +// mockPOSEntryDetail creates a BatchPOS EntryDetail +func mockPOSEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.IdentificationNumber = "45689033" + entry.IndividualName = "Wade Arnold" + entry.SetTraceNumber(mockBatchPOSHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + return entry +} + +// mockPOSEntryDetailCredit creates a BatchPOS EntryDetail with a credit entry +func mockPOSEntryDetailCredit() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.IdentificationNumber = "45689033" + entry.IndividualName = "Wade Arnold" + entry.SetTraceNumber(mockBatchPOSHeader().ODFIIdentification, 2) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + return entry +} + +// mockBatchPOS creates a BatchPOS +func mockBatchPOS() *BatchPOS { + mockBatch := NewBatchPOS(mockBatchPOSHeader()) + mockBatch.AddEntry(mockPOSEntryDetail()) + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + if err := mockBatch.Create(); err != nil { + panic(err) + } + return mockBatch +} + +// mockBatchPOSMixedDebitsAndCredits creates a BatchPOS with mixed debits and credits +func mockBatchPOSMixedDebitsAndCredits() *BatchPOS { + bh := mockBatchPOSHeader() + bh.ServiceClassCode = MixedDebitsAndCredits + + mockBatch := NewBatchPOS(bh) + mockBatch.AddEntry(mockPOSEntryDetail()) + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + + mockBatch.AddEntry(mockPOSEntryDetailCredit()) + mockBatch.GetEntries()[1].Addenda02 = mockAddenda02() + mockBatch.Entries[1].AddendaRecordIndicator = 1 + + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + + if err := mockBatch.Create(); err != nil { + panic(err) + } + return mockBatch +} + +// testBatchPOSHeader creates a BatchPOS BatchHeader +func testBatchPOSHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchPOSHeader()) + err, ok := batch.(*BatchPOS) + if !ok { + t.Errorf("Expecting BatchPOS got %T", err) + } +} + +// TestBatchPOSHeader tests validating BatchPOS BatchHeader +func TestBatchPOSHeader(t *testing.T) { + testBatchPOSHeader(t) +} + +// BenchmarkBatchPOSHeader benchmarks validating BatchPOS BatchHeader +func BenchmarkBatchPOSHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOSHeader(b) + } +} + +// testBatchPOSCreate validates BatchPOS create +func testBatchPOSCreate(t testing.TB) { + mockBatch := mockBatchPOS() + if err := mockBatch.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOSCreate tests validating BatchPOS create +func TestBatchPOSCreate(t *testing.T) { + testBatchPOSCreate(t) +} + +// BenchmarkBatchPOSCreate benchmarks validating BatchPOS create +func BenchmarkBatchPOSCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOSCreate(b) + } +} + +// testBatchPOSStandardEntryClassCode validates BatchPOS create for an invalid StandardEntryClassCode +func testBatchPOSStandardEntryClassCode(t testing.TB) { + mockBatch := mockBatchPOS() + mockBatch.Header.StandardEntryClassCode = WEB + err := mockBatch.Create() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOSStandardEntryClassCode tests validating BatchPOS create for an invalid StandardEntryClassCode +func TestBatchPOSStandardEntryClassCode(t *testing.T) { + testBatchPOSStandardEntryClassCode(t) +} + +// BenchmarkBatchPOSStandardEntryClassCode benchmarks validating BatchPOS create for an invalid StandardEntryClassCode +func BenchmarkBatchPOSStandardEntryClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOSStandardEntryClassCode(b) + } +} + +// testBatchPOSServiceClassCodeEquality validates service class code equality +func testBatchPOSServiceClassCodeEquality(t testing.TB) { + mockBatch := mockBatchPOS() + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(220, MixedDebitsAndCredits)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOSServiceClassCodeEquality tests validating service class code equality +func TestBatchPOSServiceClassCodeEquality(t *testing.T) { + testBatchPOSServiceClassCodeEquality(t) +} + +// BenchmarkBatchPOSServiceClassCodeEquality benchmarks validating service class code equality +func BenchmarkBatchPOSServiceClassCodeEquality(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOSServiceClassCodeEquality(b) + } +} + +// testBatchPOSMixedCreditsAndDebits validates BatchPOS create for an invalid MixedCreditsAndDebits +func testBatchPOSMixedCreditsAndDebits(t testing.TB) { + mockBatch := mockBatchPOS() + mockBatch.Header.ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(MixedDebitsAndCredits, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOSMixedCreditsAndDebits tests validating BatchPOS create for an invalid MixedCreditsAndDebits +func TestBatchPOSMixedCreditsAndDebits(t *testing.T) { + testBatchPOSMixedCreditsAndDebits(t) +} + +// BenchmarkBatchPOSMixedCreditsAndDebits benchmarks validating BatchPOS create for an invalid MixedCreditsAndDebits +func BenchmarkBatchPOSMixedCreditsAndDebits(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOSMixedCreditsAndDebits(b) + } +} + +// testBatchPOSCreditsOnly validates BatchPOS create for an invalid CreditsOnly +func testBatchPOSCreditsOnly(t testing.TB) { + mockBatch := mockBatchPOS() + mockBatch.Header.ServiceClassCode = CreditsOnly + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(CreditsOnly, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOSCreditsOnly tests validating BatchPOS create for an invalid CreditsOnly +func TestBatchPOSCreditsOnly(t *testing.T) { + testBatchPOSCreditsOnly(t) +} + +// BenchmarkBatchPOSCreditsOnly benchmarks validating BatchPOS create for an invalid CreditsOnly +func BenchmarkBatchPOSCreditsOnly(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOSCreditsOnly(b) + } +} + +// testBatchPOSAutomatedAccountingAdvices validates BatchPOS create for an invalid AutomatedAccountingAdvices +func testBatchPOSAutomatedAccountingAdvices(t testing.TB) { + mockBatch := mockBatchPOS() + mockBatch.Header.ServiceClassCode = AutomatedAccountingAdvices + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(MixedDebitsAndCredits, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOSAutomatedAccountingAdvices tests validating BatchPOS create for an invalid AutomatedAccountingAdvices +func TestBatchPOSAutomatedAccountingAdvices(t *testing.T) { + testBatchPOSAutomatedAccountingAdvices(t) +} + +// BenchmarkBatchPOSAutomatedAccountingAdvices benchmarks validating BatchPOS create for an invalid AutomatedAccountingAdvices +func BenchmarkBatchPOSAutomatedAccountingAdvices(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOSAutomatedAccountingAdvices(b) + } +} + +// testBatchPOSAddendaCount validates BatchPOS Addendum count of 2 +func testBatchPOSAddendaCount(t testing.TB) { + mockBatch := mockBatchPOS() + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02() + err := mockBatch.Create() + // TODO: are we expecting there to be an error here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOSAddendaCount tests validating BatchPOS Addendum count of 2 +func TestBatchPOSAddendaCount(t *testing.T) { + testBatchPOSAddendaCount(t) +} + +// BenchmarkBatchPOSAddendaCount benchmarks validating BatchPOS Addendum count of 2 +func BenchmarkBatchPOSAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOSAddendaCount(b) + } +} + +// testBatchPOSAddendaCountZero validates Addendum count of 0 +func testBatchPOSAddendaCountZero(t testing.TB) { + mockBatch := NewBatchPOS(mockBatchPOSHeader()) + mockBatch.AddEntry(mockPOSEntryDetail()) + mockAddenda02 := mockAddenda02() + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02 + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + // TODO: are we expecting there to be no errors here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOSAddendaCountZero tests validating Addendum count of 0 +func TestBatchPOSAddendaCountZero(t *testing.T) { + testBatchPOSAddendaCountZero(t) +} + +// BenchmarkBatchPOSAddendaCountZero benchmarks validating Addendum count of 0 +func BenchmarkBatchPOSAddendaCountZero(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOSAddendaCountZero(b) + } +} + +// testBatchPOSInvalidAddendum validates Addendum must be Addenda02 +func testBatchPOSInvalidAddendum(t testing.TB) { + mockBatch := NewBatchPOS(mockBatchPOSHeader()) + mockBatch.AddEntry(mockPOSEntryDetail()) + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02() + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOSInvalidAddendum tests validating Addendum must be Addenda02 +func TestBatchPOSInvalidAddendum(t *testing.T) { + testBatchPOSInvalidAddendum(t) +} + +// BenchmarkBatchPOSInvalidAddendum benchmarks validating Addendum must be Addenda02 +func BenchmarkBatchPOSInvalidAddendum(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOSInvalidAddendum(b) + } +} + +// TestBatchPOSAddendum98 validates Addenda98 returns an error +func TestBatchPOSAddendum98(t *testing.T) { + mockBatch := NewBatchPOS(mockBatchPOSHeader()) + mockBatch.AddEntry(mockPOSEntryDetail()) + mockAddenda98 := mockAddenda98() + mockBatch.GetEntries()[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98 + err := mockBatch.Create() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOSAddendum99 validates Addenda99 returns an error +func TestBatchPOSAddendum99(t *testing.T) { + mockBatch := NewBatchPOS(mockBatchPOSHeader()) + mockBatch.AddEntry(mockPOSEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// testBatchPOSInvalidAddenda validates Addendum must be Addenda02 +func testBatchPOSInvalidAddenda(t testing.TB) { + mockBatch := NewBatchPOS(mockBatchPOSHeader()) + mockBatch.AddEntry(mockPOSEntryDetail()) + addenda02 := mockAddenda02() + addenda02.TypeCode = "63" + mockBatch.GetEntries()[0].Addenda02 = addenda02 + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOSInvalidAddenda tests validating Addendum must be Addenda02 +func TestBatchPOSInvalidAddenda(t *testing.T) { + testBatchPOSInvalidAddenda(t) +} + +// BenchmarkBatchPOSInvalidAddenda benchmarks validating Addendum must be Addenda02 +func BenchmarkBatchPOSInvalidAddenda(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOSInvalidAddenda(b) + } +} + +// testBatchPOSInvalidBuild validates an invalid batch build +func testBatchPOSInvalidBuild(t testing.TB) { + mockBatch := mockBatchPOS() + mockBatch.GetHeader().ServiceClassCode = 3 + err := mockBatch.Create() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOSInvalidBuild tests validating an invalid batch build +func TestBatchPOSInvalidBuild(t *testing.T) { + testBatchPOSInvalidBuild(t) +} + +// BenchmarkBatchPOSInvalidBuild benchmarks validating an invalid batch build +func BenchmarkBatchPOSInvalidBuild(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOSInvalidBuild(b) + } +} + +// testBatchPOSCardTransactionType validates BatchPOS create for an invalid CardTransactionType +func testBatchPOSCardTransactionType(t testing.TB) { + mockBatch := mockBatchPOS() + mockBatch.GetEntries()[0].DiscretionaryData = "555" + err := mockBatch.Validate() + if !base.Match(err, ErrBatchInvalidCardTransactionType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOSCardTransactionType tests validating BatchPOS create for an invalid CardTransactionType +func TestBatchPOSCardTransactionType(t *testing.T) { + testBatchPOSCardTransactionType(t) +} + +// BenchmarkBatchPOSCardTransactionType benchmarks validating BatchPOS create for an invalid CardTransactionType +func BenchmarkBatchPOSCardTransactionType(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOSCardTransactionType(b) + } +} + +// TestBatchPOSAddendum99Category validates Addenda99 returns an error +func TestBatchPOSAddendum99Category(t *testing.T) { + mockBatch := NewBatchPOS(mockBatchPOSHeader()) + mockBatch.AddEntry(mockPOSEntryDetail()) + mockAddenda99 := mockAddenda99() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.Entries[0].Category = CategoryNOC + mockBatch.Entries[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOSCategoryReturn validates CategoryReturn returns an error if valid Addenda02 is defined +func TestBatchPOSCategoryReturn(t *testing.T) { + mockBatch := NewBatchPOS(mockBatchPOSHeader()) + mockBatch.AddEntry(mockPOSEntryDetail()) + mockAddenda02 := mockAddenda02() + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.GetEntries()[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOSCategoryReturnAddenda99 validates CategoryReturn returns an error if Addenda99 is not defined +func TestBatchPOSCategoryReturnAddenda99(t *testing.T) { + mockBatch := NewBatchPOS(mockBatchPOSHeader()) + mockBatch.AddEntry(mockPOSEntryDetail()) + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.GetEntries()[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOSTerminalState validates TerminalState returns an error if invalid from usabbrev +func TestBatchPOSTerminalState(t *testing.T) { + mockBatch := NewBatchPOS(mockBatchPOSHeader()) + mockBatch.AddEntry(mockPOSEntryDetail()) + mockAddenda02 := mockAddenda02() + mockAddenda02.TerminalState = "YY" + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02 + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrValidState) { + t.Errorf("%T: %s", err, err) + } +} + +// testBatchPOSMixedDebitsAndCredits validates BatchPOS with mixed debits and credits +func testBatchPOSMixedDebitsAndCredits(t testing.TB) { + mockBatch := mockBatchPOSMixedDebitsAndCredits() + err := mockBatch.Create() + + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } + + err = mockBatch.Validate() + + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchPOSMixedDebitsAndCredits tests validating BatchPOS with mixed debits and credits +func TestBatchPOSMixedDebitsAndCredits(t *testing.T) { + testBatchPOSMixedDebitsAndCredits(t) +} + +// BenchmarkBatchPOSMixedDebitsAndCredits benchmarks validating BatchPOS with mixed debits and credits +func BenchmarkBatchPOSMixedDebitsAndCredits(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPOSMixedDebitsAndCredits(b) + } +} diff --git a/batchPPD.go b/batchPPD.go index 852fa6261..9fca6c365 100644 --- a/batchPPD.go +++ b/batchPPD.go @@ -1,53 +1,73 @@ -// Copyright 2017 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. package ach // BatchPPD holds the Batch Header and Batch Control and all Entry Records for PPD Entries type BatchPPD struct { - batch + Batch } // NewBatchPPD returns a *BatchPPD -func NewBatchPPD(params ...BatchParam) *BatchPPD { +func NewBatchPPD(bh *BatchHeader) *BatchPPD { batch := new(BatchPPD) batch.SetControl(NewBatchControl()) - - if len(params) > 0 { - bh := NewBatchHeader(params[0]) - bh.StandardEntryClassCode = ppd - batch.SetHeader(bh) - return batch - } - bh := NewBatchHeader() - bh.StandardEntryClassCode = ppd batch.SetHeader(bh) + batch.SetID(bh.ID) return batch } -// Validate checks valid NACHA batch rules. Assumes properly parsed records. +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the batch. func (batch *BatchPPD) Validate() error { // basic verification of the batch before we validate specific rules. if err := batch.verify(); err != nil { return err } - // Add configuration based validation for this type. + // Add configuration and type specific validation for this type. - // Batch can have one addenda per entry record - if err := batch.isAddendaCount(1); err != nil { - return err - } - if err := batch.isTypeCode("05"); err != nil { - return err + if batch.Header.StandardEntryClassCode != PPD { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, PPD) } - // Add type specific validation. - // ... + for _, entry := range batch.Entries { + // PPD can have up to one Addenda05 record + if len(entry.Addenda05) > 1 { + return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 1)) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + } return nil } -// Create takes Batch Header and Entries and builds a valid batch +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. func (batch *BatchPPD) Create() error { // generates sequence numbers and batch control if err := batch.build(); err != nil { @@ -56,8 +76,5 @@ func (batch *BatchPPD) Create() error { // Additional steps specific to batch type // ... - if err := batch.Validate(); err != nil { - return err - } - return nil + return batch.Validate() } diff --git a/batchPPD_test.go b/batchPPD_test.go index 20a741cf1..bc6897687 100644 --- a/batchPPD_test.go +++ b/batchPPD_test.go @@ -1,347 +1,352 @@ -// Copyright 2017 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. package ach import ( "testing" "time" + + "github.com/moov-io/base" ) +// mockBatchPPDHeader creates a PPD batch header +func mockBatchPPDHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = PPD + bh.CompanyName = "ACME Corporation" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "PAYROLL" + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "12104288" + return bh +} + +// mockPPDEntryDetail creates a PPD Entry Detail +func mockPPDEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "123456789" + entry.Amount = 100000000 + entry.IndividualName = "Wade Arnold" + entry.SetTraceNumber(mockBatchPPDHeader().ODFIIdentification, 1) + entry.Category = CategoryForward + return entry +} + +// mockBatchPPDHeader2 creates a 2nd PPD batch header +func mockBatchPPDHeader2() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = MixedDebitsAndCredits + bh.CompanyName = "MY BEST COMP." + bh.CompanyDiscretionaryData = "INCLUDES OVERTIME" + bh.CompanyIdentification = "121042882" + bh.StandardEntryClassCode = PPD + bh.CompanyEntryDescription = "PAYROLL" + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "12104288" + return bh +} + +// mockPPDEntryDetail2 creates a 2nd PPD entry detail +func mockPPDEntryDetail2() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit // ACH Credit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "62292250" // account number + entry.Amount = 100000 // 1k dollars + entry.IdentificationNumber = "658-888-2468" // Unique ID for payment + entry.IndividualName = "Wade Arnold" + entry.SetTraceNumber(mockBatchPPDHeader2().ODFIIdentification, 1) + entry.Category = CategoryForward + return entry +} + +// mockPPDEntryDetailNOC creates a PPD Entry Detail +func mockPPDEntryDetailNOC() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "123456789" + entry.Amount = 100000000 + entry.IndividualName = "Wade Arnold" + entry.SetTraceNumber(mockBatchPPDHeader().ODFIIdentification, 1) + entry.Category = CategoryNOC + return entry +} + +// mockBatchPPD creates a PPD batch func mockBatchPPD() *BatchPPD { - mockBatch := NewBatchPPD() - mockBatch.SetHeader(mockBatchHeader()) - mockBatch.AddEntry(mockEntryDetail()) + mockBatch := NewBatchPPD(mockBatchPPDHeader()) + mockBatch.AddEntry(mockPPDEntryDetail()) if err := mockBatch.Create(); err != nil { panic(err) } return mockBatch } +// testBatchError validates batch error handling +func testBatchError(t testing.TB) { + err := &BatchError{BatchNumber: 1, FieldName: "mock", Err: ErrBatchNoEntries} + if err.Error() != "batch #1 () mock must have Entry Record(s) to be built" { + t.Errorf("BatchError Error has changed formatting: %v", err) + } +} + +// TestBatchError tests validating batch error handling func TestBatchError(t *testing.T) { - err := &BatchError{BatchNumber: 1, FieldName: "mock", Msg: "test message"} - if err.Error() != "BatchNumber 1 mock test message" { - t.Error("BatchError Error has changed formatting") + testBatchError(t) +} + +// BenchmarkBatchError benchmarks validating batch error handling +func BenchmarkBatchError(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchError(b) } } -func TestBatchServiceClassCodeEquality(t *testing.T) { +// testBatchServiceClassCodeEquality validates service class code equality +func testBatchServiceClassCodeEquality(t testing.TB) { mockBatch := mockBatchPPD() - mockBatch.GetControl().ServiceClassCode = 225 - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "ServiceClassCode" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + mockBatch.GetControl().ServiceClassCode = DebitsOnly + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(220, MixedDebitsAndCredits)) { + t.Errorf("%T: %s", err, err) } } -func TestBatchPPDCreate(t *testing.T) { +// TestBatchServiceClassCodeEquality tests validating service class code equality +func TestBatchServiceClassCodeEquality(t *testing.T) { + testBatchServiceClassCodeEquality(t) +} + +// BenchmarkBatchServiceClassCodeEquality benchmarks validating service class code equality +func BenchmarkBatchServiceClassCodeEquality(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchServiceClassCodeEquality(b) + } +} + +// BatchPPDCreate validates batch create for an invalid service code +func testBatchPPDCreate(t testing.TB) { mockBatch := mockBatchPPD() // can not have default values in Batch Header to build batch mockBatch.GetHeader().ServiceClassCode = 0 - mockBatch.Create() - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "ServiceClassCode" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + err := mockBatch.Create() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) } } -func TestBatchPPDTypeCode(t *testing.T) { +// TestBatchPPDCreate tests validating batch create for an invalid service code +func TestBatchPPDCreate(t *testing.T) { + testBatchPPDCreate(t) +} + +// BenchmarkBatchPPDCreate benchmarks validating batch create for an invalid service code +func BenchmarkBatchPPDCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPPDCreate(b) + } +} + +// testBatchPPDTypeCode validates batch PPD type code +func testBatchPPDTypeCode(t testing.TB) { mockBatch := mockBatchPPD() // change an addendum to an invalid type code - a := mockAddenda() + a := mockAddenda05() a.TypeCode = "63" - mockBatch.GetEntries()[0].AddAddenda(a) - mockBatch.Create() - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "TypeCode" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + mockBatch.GetEntries()[0].AddAddenda05(a) + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) } } -func TestBatchCompanyIdentification(t *testing.T) { - mockBatch := mockBatchPPD() - mockBatch.GetControl().CompanyIdentification = "XYZ Inc" - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "CompanyIdentification" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } +// TestBatchPPDTypeCode tests validating batch PPD type code +func TestBatchPPDTypeCode(t *testing.T) { + testBatchPPDTypeCode(t) +} + +// BenchmarkBatchPPDTypeCode benchmarks validating batch PPD type code +func BenchmarkBatchPPDTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPPDTypeCode(b) } } -func TestBatchODFIIDMismatch(t *testing.T) { +// testBatchCompanyIdentification validates batch PPD company identification +func testBatchCompanyIdentification(t testing.TB) { mockBatch := mockBatchPPD() - mockBatch.GetControl().ODFIIdentification = 987654321 - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "ODFIIdentification" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + mockBatch.GetControl().CompanyIdentification = "XYZ Inc" + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality("121042882", "XYZ Inc")) { + t.Errorf("%T: %s", err, err) } } -func TestBatchNumberMismatch(t *testing.T) { - mockBatch := mockBatchPPD() - mockBatch.GetControl().BatchNumber = 2 - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "BatchNumber" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } +// TestBatchCompanyIdentification tests validating batch PPD company identification +func TestBatchCompanyIdentification(t *testing.T) { + testBatchCompanyIdentification(t) +} + +// BenchmarkBatchCompanyIdentification benchmarks validating batch PPD company identification +func BenchmarkBatchCompanyIdentification(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCompanyIdentification(b) } } -func TestCreditBatchisBatchAmount(t *testing.T) { - mockBatch := NewBatchPPD() - mockBatch.SetHeader(mockBatchHeader()) - e1 := mockEntryDetail() - e1.TransactionCode = 22 - e1.Amount = 100 - e2 := mockEntryDetail() - e2.TransactionCode = 22 - e2.Amount = 100 - mockBatch.AddEntry(e1) - mockBatch.AddEntry(e2) - if err := mockBatch.Create(); err != nil { +// testBatchODFIIDMismatch validates ODFIIdentification mismatch +func testBatchODFIIDMismatch(t testing.TB) { + mockBatch := mockBatchPPD() + mockBatch.GetControl().ODFIIdentification = "987654321" + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality("12104288", "987654321")) { t.Errorf("%T: %s", err, err) } +} + +// TestBatchODFIIDMismatch tests validating ODFIIdentification mismatch +func TestBatchODFIIDMismatch(t *testing.T) { + testBatchODFIIDMismatch(t) +} - mockBatch.GetControl().TotalCreditEntryDollarAmount = 1 - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "TotalCreditEntryDollarAmount" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } +// BenchmarkBatchODFIIDMismatch benchmarks validating ODFIIdentification mismatch +func BenchmarkBatchODFIIDMismatch(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchODFIIDMismatch(b) } } -func TestSavingsBatchisBatchAmount(t *testing.T) { - mockBatch := NewBatchPPD() - mockBatch.SetHeader(mockBatchHeader()) - e1 := mockEntryDetail() - e1.TransactionCode = 32 - e1.Amount = 100 - e2 := mockEntryDetail() - e2.TransactionCode = 37 - e2.Amount = 100 - mockBatch.AddEntry(e1) - mockBatch.AddEntry(e2) +// testBatchBuild builds a PPD batch +func testBatchBuild(t testing.TB) { + mockBatch := NewBatchPPD(mockBatchPPDHeader2()) + entry := mockPPDEntryDetail2() + addenda05 := NewAddenda05() + entry.AddendaRecordIndicator = 1 + entry.AddAddenda05(addenda05) + mockBatch.AddEntry(entry) if err := mockBatch.Create(); err != nil { t.Errorf("%T: %s", err, err) } - - mockBatch.GetControl().TotalDebitEntryDollarAmount = 1 - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "TotalDebitEntryDollarAmount" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } - } } -func TestBatchisEntryHash(t *testing.T) { - mockBatch := mockBatchPPD() - mockBatch.GetControl().EntryHash = 1 - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "EntryHash" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } - } +// TestBatchBuild tests building a PPD batch +func TestBatchBuild(t *testing.T) { + testBatchBuild(t) } -func TestBatchDNEMismatch(t *testing.T) { - mockBatch := NewBatchPPD() - mockBatch.SetHeader(mockBatchHeader()) - ed := mockEntryDetail() - ed.AddAddenda(mockAddenda()) - ed.AddAddenda(mockAddenda()) - mockBatch.AddEntry(ed) - mockBatch.Create() - - mockBatch.GetHeader().OriginatorStatusCode = 1 - mockBatch.GetEntries()[0].TransactionCode = 23 - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "OriginatorStatusCode" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } +// BenchmarkBatchBuild benchmarks building a PPD batch +func BenchmarkBatchBuild(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchBuild(b) } } -func TestBatchTraceNumberNotODFI(t *testing.T) { +// testBatchPPDAddendaCount validates BatchPPD Addendum count of 2 +func testBatchPPDAddendaCount(t testing.TB) { mockBatch := mockBatchPPD() - mockBatch.GetEntries()[0].setTraceNumber(12345678, 1) - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "ODFIIdentificationField" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchCalculatedControlEquality(3, 1)) { + t.Errorf("%T: %s", err, err) } } -func TestBatchEntryCountEquality(t *testing.T) { - mockBatch := NewBatchPPD() - mockBatch.SetHeader(mockBatchHeader()) - e := mockEntryDetail() - a := mockAddenda() - e.AddAddenda(a) - mockBatch.AddEntry(e) - if err := mockBatch.Create(); err != nil { - t.Errorf("%T: %s", err, err) - } +// TestBatchPPDAddendaCount tests validating BatchPPD Addendum count of 2 +func TestBatchPPDAddendaCount(t *testing.T) { + testBatchPPDAddendaCount(t) +} - mockBatch.GetControl().EntryAddendaCount = 1 - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "EntryAddendaCount" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } +// BenchmarkBatchPPDAddendaCount benchmarks validating BatchPPD Addendum count of 2 +func BenchmarkBatchPPDAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchPPDAddendaCount(b) } } -func TestBatchAddendaIndicator(t *testing.T) { - mockBatch := mockBatchPPD() - mockBatch.GetEntries()[0].AddAddenda(mockAddenda()) - mockBatch.GetEntries()[0].AddendaRecordIndicator = 0 - mockBatch.GetControl().EntryAddendaCount = 2 - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "AddendaRecordIndicator" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } +// TestBatchPPDAddendum98 validates Addenda98 returns an error +func TestBatchPPDAddendum98(t *testing.T) { + mockBatch := NewBatchPPD(mockBatchPPDHeader()) + mockBatch.AddEntry(mockPPDEntryDetail()) + mockAddenda98 := mockAddenda98() + mockAddenda98.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) } } -func TestBatchIsAddendaSeqAscending(t *testing.T) { - mockBatch := NewBatchPPD() - mockBatch.SetHeader(mockBatchHeader()) - ed := mockEntryDetail() - ed.AddAddenda(mockAddenda()) - ed.AddAddenda(mockAddenda()) - mockBatch.AddEntry(ed) - mockBatch.Create() - - mockBatch.GetEntries()[0].Addendum[0].SequenceNumber = 2 - mockBatch.GetEntries()[0].Addendum[1].SequenceNumber = 1 - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "SequenceNumber" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } +// TestBatchPPDAddendum99 validates Addenda99 returns an error +func TestBatchPPDAddendum99(t *testing.T) { + mockBatch := NewBatchPPD(mockBatchPPDHeader()) + mockBatch.AddEntry(mockPPDEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) } } -func TestBatchIsSequenceAscending(t *testing.T) { +// TestBatchPPDSEC validates that the standard entry class code is PPD for batch PPD +func TestBatchPPDSEC(t *testing.T) { mockBatch := mockBatchPPD() - e3 := mockEntryDetail() - e3.TraceNumber = 1 - mockBatch.AddEntry(e3) - mockBatch.GetControl().EntryAddendaCount = 2 - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "TraceNumber" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + mockBatch.Header.StandardEntryClassCode = RCK + err := mockBatch.Validate() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) } } -func TestBatchAddendaTraceNumber(t *testing.T) { +// TestBatchPPDValidTranCodeForServiceClassCode validates a transactionCode based on ServiceClassCode +func TestBatchPPDValidTranCodeForServiceClassCode(t *testing.T) { mockBatch := mockBatchPPD() - mockBatch.GetEntries()[0].AddAddenda(mockAddenda()) - if err := mockBatch.Create(); err != nil { + mockBatch.GetHeader().ServiceClassCode = DebitsOnly + err := mockBatch.Create() + if !base.Match(err, NewErrBatchServiceClassTranCode(DebitsOnly, 22)) { t.Errorf("%T: %s", err, err) } - - mockBatch.GetEntries()[0].Addendum[0].EntryDetailSequenceNumber = 99 - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "TraceNumber" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } - } } -func TestBatchBuild(t *testing.T) { - mockBatch := NewBatchPPD() - header := NewBatchHeader() - header.ServiceClassCode = 200 - header.CompanyName = "MY BEST COMP." - header.CompanyDiscretionaryData = "INCLUDES OVERTIME" - header.CompanyIdentification = "1419871234" - header.StandardEntryClassCode = "PPD" - header.CompanyEntryDescription = "PAYROLL" - header.EffectiveEntryDate = time.Now() - header.ODFIIdentification = 109991234 - mockBatch.SetHeader(header) - - entry := NewEntryDetail() - entry.TransactionCode = 22 // ACH Credit - entry.SetRDFI(81086674) // scottrade bank routing number - entry.DFIAccountNumber = "62292250" // scottrade account number - entry.Amount = 1000000 // 1k dollars - entry.IdentificationNumber = "658-888-2468" // Unique ID for payment - entry.IndividualName = "Wade Arnold" - entry.setTraceNumber(header.ODFIIdentification, 1) - a1 := NewAddenda() - entry.AddAddenda(a1) - mockBatch.AddEntry(entry) - if err := mockBatch.Create(); err != nil { +// TestBatchPPDAddenda02 validates BatchPPD cannot have Addenda02 +func TestBatchPPDAddenda02(t *testing.T) { + mockBatch := mockBatchPPD() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02() + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { t.Errorf("%T: %s", err, err) } } diff --git a/batchRCK.go b/batchRCK.go new file mode 100644 index 000000000..6df02895e --- /dev/null +++ b/batchRCK.go @@ -0,0 +1,102 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +// BatchRCK holds the BatchHeader and BatchControl and all EntryDetail for RCK Entries. +// +// Represented Check Entries (RCK). A physical check that was presented but returned because of +// insufficient funds may be represented as an ACH entry. +type BatchRCK struct { + Batch +} + +// NewBatchRCK returns a *BatchRCK +func NewBatchRCK(bh *BatchHeader) *BatchRCK { + batch := new(BatchRCK) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} + +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the batch. +func (batch *BatchRCK) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + + // Add configuration and type specific validation for this type. + if batch.Header.StandardEntryClassCode != RCK { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, RCK) + } + + // RCK detail entries can only be a debit, ServiceClassCode must allow debits + switch batch.Header.ServiceClassCode { + case CreditsOnly: + return batch.Error("ServiceClassCode", ErrBatchServiceClassCode, batch.Header.ServiceClassCode) + } + + // CompanyEntryDescription is required to be REDEPCHECK + if batch.Header.CompanyEntryDescription != "REDEPCHECK" { + return batch.Error("CompanyEntryDescription", ErrBatchCompanyEntryDescriptionREDEPCHECK, batch.Header.CompanyEntryDescription) + } + + for _, entry := range batch.Entries { + // RCK detail entries must be a debit + if entry.CreditOrDebit() != "D" { + return batch.Error("TransactionCode", ErrBatchDebitOnly, entry.TransactionCode) + } + // // Amount must be 2,500 or less + if entry.Amount > 250000 { + return batch.Error("Amount", NewErrBatchAmount(entry.Amount, 250000)) + } + // CheckSerialNumber underlying IdentificationNumber, must be defined + if entry.IdentificationNumber == "" { + return batch.Error("CheckSerialNumber", ErrBatchCheckSerialNumber) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + } + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchRCK) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + // Additional steps specific to batch type + // ... + + return batch.Validate() +} diff --git a/batchRCK_test.go b/batchRCK_test.go new file mode 100644 index 000000000..1ec60587c --- /dev/null +++ b/batchRCK_test.go @@ -0,0 +1,462 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockBatchRCKHeader creates a BatchRCK BatchHeader +func mockBatchRCKHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = RCK + bh.CompanyName = "Company Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "REDEPCHECK" + bh.ODFIIdentification = "12104288" + return bh +} + +// mockRCKEntryDetail creates a BatchRCK EntryDetail +func mockRCKEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 2400 + entry.SetCheckSerialNumber("123456789") + entry.IndividualName = "Wade Arnold" + entry.SetTraceNumber(mockBatchRCKHeader().ODFIIdentification, 1) + entry.Category = CategoryForward + return entry +} + +// mockBatchRCK creates a BatchRCK +func mockBatchRCK() *BatchRCK { + mockBatch := NewBatchRCK(mockBatchRCKHeader()) + mockBatch.AddEntry(mockRCKEntryDetail()) + if err := mockBatch.Create(); err != nil { + panic(err) + } + return mockBatch +} + +// mockBatchRCKHeaderCredit creates a BatchRCK BatchHeader +func mockBatchRCKHeaderCredit() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = RCK + bh.CompanyName = "Company Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "REDEPCHECK" + bh.ODFIIdentification = "12104288" + return bh +} + +// mockRCKEntryDetailCredit creates a BatchRCK EntryDetail with a credit entry +func mockRCKEntryDetailCredit() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 2400 + entry.SetCheckSerialNumber("123456789") + entry.IndividualName = "Wade Arnold" + entry.SetTraceNumber(mockBatchRCKHeader().ODFIIdentification, 1) + entry.Category = CategoryForward + return entry +} + +// mockBatchRCKCredit creates a BatchRCK with a credit entry +func mockBatchRCKCredit() *BatchRCK { + mockBatch := NewBatchRCK(mockBatchRCKHeaderCredit()) + mockBatch.AddEntry(mockRCKEntryDetailCredit()) + return mockBatch +} + +// testBatchRCKHeader creates a BatchRCK BatchHeader +func testBatchRCKHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchRCKHeader()) + err, ok := batch.(*BatchRCK) + if !ok { + t.Errorf("Expecting BatchRCK got %T", err) + } +} + +// TestBatchRCKHeader tests validating BatchRCK BatchHeader +func TestBatchRCKHeader(t *testing.T) { + testBatchRCKHeader(t) +} + +// BenchmarkBatchRCKHeader benchmarks validating BatchRCK BatchHeader +func BenchmarkBatchRCKHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchRCKHeader(b) + } +} + +// testBatchRCKCreate validates BatchRCK create +func testBatchRCKCreate(t testing.TB) { + mockBatch := mockBatchRCK() + if err := mockBatch.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchRCKCreate tests validating BatchRCK create +func TestBatchRCKCreate(t *testing.T) { + testBatchRCKCreate(t) +} + +// BenchmarkBatchRCKCreate benchmarks validating BatchRCK create +func BenchmarkBatchRCKCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchRCKCreate(b) + } +} + +// testBatchRCKStandardEntryClassCode validates BatchRCK create for an invalid StandardEntryClassCode +func testBatchRCKStandardEntryClassCode(t testing.TB) { + mockBatch := mockBatchRCK() + mockBatch.Header.StandardEntryClassCode = WEB + err := mockBatch.Create() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchRCKStandardEntryClassCode tests validating BatchRCK create for an invalid StandardEntryClassCode +func TestBatchRCKStandardEntryClassCode(t *testing.T) { + testBatchRCKStandardEntryClassCode(t) +} + +// BenchmarkBatchRCKStandardEntryClassCode benchmarks validating BatchRCK create for an invalid StandardEntryClassCode +func BenchmarkBatchRCKStandardEntryClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchRCKStandardEntryClassCode(b) + } +} + +// testBatchRCKServiceClassCodeEquality validates service class code equality +func testBatchRCKServiceClassCodeEquality(t testing.TB) { + mockBatch := mockBatchRCK() + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(220, MixedDebitsAndCredits)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchRCKServiceClassCodeEquality tests validating service class code equality +func TestBatchRCKServiceClassCodeEquality(t *testing.T) { + testBatchRCKServiceClassCodeEquality(t) +} + +// BenchmarkBatchRCKServiceClassCodeEquality benchmarks validating service class code equality +func BenchmarkBatchRCKServiceClassCodeEquality(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchRCKServiceClassCodeEquality(b) + } +} + +// testBatchRCKMixedCreditsAndDebits validates BatchRCK create for an invalid MixedCreditsAndDebits +func testBatchRCKMixedCreditsAndDebits(t testing.TB) { + mockBatch := mockBatchRCK() + mockBatch.Header.ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(MixedDebitsAndCredits, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchRCKMixedCreditsAndDebits tests validating BatchRCK create for an invalid MixedCreditsAndDebits +func TestBatchRCKMixedCreditsAndDebits(t *testing.T) { + testBatchRCKMixedCreditsAndDebits(t) +} + +// BenchmarkBatchRCKMixedCreditsAndDebits benchmarks validating BatchRCK create for an invalid MixedCreditsAndDebits +func BenchmarkBatchRCKMixedCreditsAndDebits(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchRCKMixedCreditsAndDebits(b) + } +} + +// testBatchRCKCreditsOnly validates BatchRCK create for an invalid CreditsOnly +func testBatchRCKCreditsOnly(t testing.TB) { + mockBatch := mockBatchRCK() + mockBatch.Header.ServiceClassCode = CreditsOnly + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(CreditsOnly, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchRCKCreditsOnly tests validating BatchRCK create for an invalid CreditsOnly +func TestBatchRCKCreditsOnly(t *testing.T) { + testBatchRCKCreditsOnly(t) +} + +// BenchmarkBatchRCKCreditsOnly benchmarks validating BatchRCK create for an invalid CreditsOnly +func BenchmarkBatchRCKCreditsOnly(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchRCKCreditsOnly(b) + } +} + +// testBatchRCKAutomatedAccountingAdvices validates BatchRCK create for an invalid AutomatedAccountingAdvices +func testBatchRCKAutomatedAccountingAdvices(t testing.TB) { + mockBatch := mockBatchRCK() + mockBatch.Header.ServiceClassCode = AutomatedAccountingAdvices + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(AutomatedAccountingAdvices, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchRCKAutomatedAccountingAdvices tests validating BatchRCK create for an invalid AutomatedAccountingAdvices +func TestBatchRCKAutomatedAccountingAdvices(t *testing.T) { + testBatchRCKAutomatedAccountingAdvices(t) +} + +// BenchmarkBatchRCKAutomatedAccountingAdvices benchmarks validating BatchRCK create for an invalid AutomatedAccountingAdvices +func BenchmarkBatchRCKAutomatedAccountingAdvices(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchRCKAutomatedAccountingAdvices(b) + } +} + +// testBatchRCKCompanyEntryDescription validates BatchRCK create for an invalid CompanyEntryDescription +func testBatchRCKCompanyEntryDescription(t testing.TB) { + mockBatch := mockBatchRCK() + mockBatch.Header.CompanyEntryDescription = "XYZ975" + err := mockBatch.Create() + if !base.Match(err, ErrBatchCompanyEntryDescriptionREDEPCHECK) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchRCKCompanyEntryDescription validates BatchRCK create for an invalid CompanyEntryDescription +func TestBatchRCKCompanyEntryDescription(t *testing.T) { + testBatchRCKCompanyEntryDescription(t) +} + +// BenchmarkBatchRCKCompanyEntryDescription validates BatchRCK create for an invalid CompanyEntryDescription +func BenchmarkBatchRCKCompanyEntryDescription(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchRCKCompanyEntryDescription(b) + } +} + +// testBatchRCKAmount validates BatchRCK create for an invalid Amount +func testBatchRCKAmount(t testing.TB) { + mockBatch := mockBatchRCK() + mockBatch.Entries[0].Amount = 250001 + err := mockBatch.Create() + if !base.Match(err, NewErrBatchAmount(250001, 250000)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchRCKAmount validates BatchRCK create for an invalid Amount +func TestBatchRCKAmount(t *testing.T) { + testBatchRCKAmount(t) +} + +// BenchmarkBatchRCKAmount validates BatchRCK create for an invalid Amount +func BenchmarkBatchRCKAmount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchRCKAmount(b) + } +} + +// testBatchRCKCheckSerialNumber validates BatchRCK CheckSerialNumber / IdentificationNumber is a mandatory field +func testBatchRCKCheckSerialNumber(t testing.TB) { + mockBatch := mockBatchRCK() + // modify CheckSerialNumber / IdentificationNumber to empty string + mockBatch.GetEntries()[0].SetCheckSerialNumber("") + err := mockBatch.Validate() + if !base.Match(err, ErrBatchCheckSerialNumber) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchRCKCheckSerialNumber tests validating BatchRCK +// CheckSerialNumber / IdentificationNumber is a mandatory field +func TestBatchRCKCheckSerialNumber(t *testing.T) { + testBatchRCKCheckSerialNumber(t) +} + +// BenchmarkBatchRCKCheckSerialNumber benchmarks validating BatchRCK +// CheckSerialNumber / IdentificationNumber is a mandatory field +func BenchmarkBatchRCKCheckSerialNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchRCKCheckSerialNumber(b) + } +} + +// testBatchRCKTransactionCode validates BatchRCK TransactionCode is not a credit +func testBatchRCKTransactionCode(t testing.TB) { + mockBatch := mockBatchRCKCredit() + err := mockBatch.Create() + if !base.Match(err, ErrBatchDebitOnly) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchRCKTransactionCode tests validating BatchRCK TransactionCode is not a credit +func TestBatchRCKTransactionCode(t *testing.T) { + testBatchRCKTransactionCode(t) +} + +// BenchmarkBatchRCKTransactionCode benchmarks validating BatchRCK TransactionCode is not a credit +func BenchmarkBatchRCKTransactionCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchRCKTransactionCode(b) + } +} + +// testBatchRCKAddendaCount validates BatchRCK addenda count +func testBatchRCKAddendaCount(t testing.TB) { + mockBatch := mockBatchRCK() + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchRCKAddendaCount tests validating BatchRCK addenda count +func TestBatchRCKAddendaCount(t *testing.T) { + testBatchRCKAddendaCount(t) +} + +// BenchmarkBatchRCKAddendaCount benchmarks validating BatchRCK addenda count +func BenchmarkBatchRCKAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchRCKAddendaCount(b) + } +} + +// testBatchRCKParseCheckSerialNumber validates BatchRCK create +func testBatchRCKParseCheckSerialNumber(t testing.TB) { + mockBatch := mockBatchRCK() + if err := mockBatch.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + + checkSerialNumber := "123456789 " + if checkSerialNumber != mockBatch.GetEntries()[0].CheckSerialNumberField() { + t.Errorf("RecordType Expected '123456789' got: %v", mockBatch.GetEntries()[0].CheckSerialNumberField()) + } +} + +// TestBatchRCKParseCheckSerialNumber tests validating BatchRCK create +func TestBatchRCKParseCheckSerialNumber(t *testing.T) { + testBatchRCKParseCheckSerialNumber(t) +} + +// BenchmarkBatchRCKParseCheckSerialNumber benchmarks validating BatchRCK create +func BenchmarkBatchRCKParseCheckSerialNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchRCKParseCheckSerialNumber(b) + } +} + +// testBatchRCKInvalidBuild validates an invalid batch build +func testBatchRCKInvalidBuild(t testing.TB) { + mockBatch := mockBatchRCK() + mockBatch.GetHeader().ServiceClassCode = 3 + err := mockBatch.Create() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchRCKInvalidBuild tests validating an invalid batch build +func TestBatchRCKInvalidBuild(t *testing.T) { + testBatchRCKInvalidBuild(t) +} + +// BenchmarkBatchRCKInvalidBuild benchmarks validating an invalid batch build +func BenchmarkRCKBatchInvalidBuild(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchRCKInvalidBuild(b) + } +} + +// TestBatchRCKAddendum98 validates Addenda98 returns an error +func TestBatchRCKAddendum98(t *testing.T) { + mockBatch := NewBatchRCK(mockBatchRCKHeader()) + mockBatch.AddEntry(mockRCKEntryDetail()) + mockAddenda98 := mockAddenda98() + mockAddenda98.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchRCKAddendum99 validates Addenda99 returns an error +func TestBatchRCKAddendum99(t *testing.T) { + mockBatch := NewBatchRCK(mockBatchRCKHeader()) + mockBatch.AddEntry(mockRCKEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// testBatchRCKServiceClassCodeEquality validates MixedDebitsAndCredits service class code +func testBatchRCKMixedDebitsAndCreditsServiceClassCode(t testing.TB) { + mockBatch := mockBatchRCK() + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + mockBatch.Header.ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchRCKServiceClassCodeEquality tests validating MixedDebitsAndCredits service class code +func TestBatchRCKMixedDebitsAndCreditsServiceClassCode(t *testing.T) { + testBatchRCKMixedDebitsAndCreditsServiceClassCode(t) +} diff --git a/batchSHR.go b/batchSHR.go new file mode 100644 index 000000000..63660d0c3 --- /dev/null +++ b/batchSHR.go @@ -0,0 +1,115 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "github.com/moov-io/ach/internal/usabbrev" +) + +// BatchSHR holds the BatchHeader and BatchControl and all EntryDetail for SHR Entries. +// +// Shared Network Entry (SHR) is a debit Entry initiated at an “electronic terminal,” +// as that term is defined in Regulation E, to a Consumer Account of the Receiver to pay +// an obligation incurred in a point-of-sale transaction, or to effect a point-of-sale +// terminal cash withdrawal. Also an adjusting or other credit Entry related to such debit +// Entry, transfer of funds, or obligation. SHR Entries are initiated in a shared network +// where the ODFI and RDFI have an agreement in addition to these Rules to process such +// Entries. +type BatchSHR struct { + Batch +} + +// NewBatchSHR returns a *BatchSHR +func NewBatchSHR(bh *BatchHeader) *BatchSHR { + batch := new(BatchSHR) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} + +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the batch. +func (batch *BatchSHR) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + + // Add configuration and type specific validation for this type. + if batch.Header.StandardEntryClassCode != SHR { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, SHR) + } + + // SHR detail entries can only be a debit, ServiceClassCode must allow debits + switch batch.Header.ServiceClassCode { + case MixedDebitsAndCredits, CreditsOnly: + return batch.Error("ServiceClassCode", ErrBatchServiceClassCode, batch.Header.ServiceClassCode) + } + + for _, entry := range batch.Entries { + // SHR detail entries must be a debit + if entry.CreditOrDebit() != "D" { + return batch.Error("TransactionCode", ErrBatchDebitOnly, entry.TransactionCode) + } + if err := entry.isCardTransactionType(entry.DiscretionaryData); err != nil { + return batch.Error("CardTransactionType", ErrBatchInvalidCardTransactionType, entry.DiscretionaryData) + } + + // CardExpirationDate BatchSHR ACH File format is MMYY. Validate MM is 01-12. + month := entry.parseStringField(entry.SHRCardExpirationDateField()[0:2]) + year := entry.parseStringField(entry.SHRCardExpirationDateField()[2:4]) + if err := entry.isMonth(month); err != nil { + return fieldError("CardExpirationDate", ErrValidMonth, month) + } + if err := entry.isCreditCardYear(year); err != nil { + return fieldError("CardExpirationDate", ErrValidYear, year) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + if entry.Category == CategoryForward { + if !usabbrev.Valid(entry.Addenda02.TerminalState) { + return batch.Error("TerminalState", ErrValidState, entry.Addenda02.TerminalState) + } + } + } + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchSHR) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + // Additional steps specific to batch type + // ... + return batch.Validate() +} diff --git a/batchSHR_test.go b/batchSHR_test.go new file mode 100644 index 000000000..b6956d2ae --- /dev/null +++ b/batchSHR_test.go @@ -0,0 +1,571 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockBatchSHRHeader creates a BatchSHR BatchHeader +func mockBatchSHRHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = SHR + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH SHR" + bh.ODFIIdentification = "12104288" + return bh +} + +// mockSHREntryDetail creates a BatchSHR EntryDetail +func mockSHREntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.SetSHRCardExpirationDate("0722") + entry.SetSHRDocumentReferenceNumber("12345678910") + entry.SetSHRIndividualCardAccountNumber("1234567891123456789") + entry.SetTraceNumber(mockBatchSHRHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + return entry +} + +// mockBatchSHR creates a BatchSHR +func mockBatchSHR() *BatchSHR { + mockBatch := NewBatchSHR(mockBatchSHRHeader()) + mockBatch.AddEntry(mockSHREntryDetail()) + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + if err := mockBatch.Create(); err != nil { + panic(err) + } + return mockBatch +} + +// testBatchSHRHeader creates a BatchSHR BatchHeader +func testBatchSHRHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchSHRHeader()) + err, ok := batch.(*BatchSHR) + if !ok { + t.Errorf("Expecting BatchSHR got %T", err) + } +} + +// TestBatchSHRHeader tests validating BatchSHR BatchHeader +func TestBatchSHRHeader(t *testing.T) { + testBatchSHRHeader(t) +} + +// BenchmarkBatchSHRHeader benchmarks validating BatchSHR BatchHeader +func BenchmarkBatchSHRHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchSHRHeader(b) + } +} + +// testBatchSHRCreate validates BatchSHR create +func testBatchSHRCreate(t testing.TB) { + mockBatch := mockBatchSHR() + if err := mockBatch.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchSHRCreate tests validating BatchSHR create +func TestBatchSHRCreate(t *testing.T) { + testBatchSHRCreate(t) +} + +// BenchmarkBatchSHRCreate benchmarks validating BatchSHR create +func BenchmarkBatchSHRCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchSHRCreate(b) + } +} + +// testBatchSHRStandardEntryClassCode validates BatchSHR create for an invalid StandardEntryClassCode +func testBatchSHRStandardEntryClassCode(t testing.TB) { + mockBatch := mockBatchSHR() + mockBatch.Header.StandardEntryClassCode = WEB + err := mockBatch.Create() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchSHRStandardEntryClassCode tests validating BatchSHR create for an invalid StandardEntryClassCode +func TestBatchSHRStandardEntryClassCode(t *testing.T) { + testBatchSHRStandardEntryClassCode(t) +} + +// BenchmarkBatchSHRStandardEntryClassCode benchmarks validating BatchSHR create for an invalid StandardEntryClassCode +func BenchmarkBatchSHRStandardEntryClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchSHRStandardEntryClassCode(b) + } +} + +// testBatchSHRServiceClassCodeEquality validates service class code equality +func testBatchSHRServiceClassCodeEquality(t testing.TB) { + mockBatch := mockBatchSHR() + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(220, MixedDebitsAndCredits)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchSHRServiceClassCodeEquality tests validating service class code equality +func TestBatchSHRServiceClassCodeEquality(t *testing.T) { + testBatchSHRServiceClassCodeEquality(t) +} + +// BenchmarkBatchSHRServiceClassCodeEquality benchmarks validating service class code equality +func BenchmarkBatchSHRServiceClassCodeEquality(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchSHRServiceClassCodeEquality(b) + } +} + +// testBatchSHRMixedCreditsAndDebits validates BatchSHR create for an invalid MixedCreditsAndDebits +func testBatchSHRMixedCreditsAndDebits(t testing.TB) { + mockBatch := mockBatchSHR() + mockBatch.Header.ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(MixedDebitsAndCredits, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchSHRMixedCreditsAndDebits tests validating BatchSHR create for an invalid MixedCreditsAndDebits +func TestBatchSHRMixedCreditsAndDebits(t *testing.T) { + testBatchSHRMixedCreditsAndDebits(t) +} + +// BenchmarkBatchSHRMixedCreditsAndDebits benchmarks validating BatchSHR create for an invalid MixedCreditsAndDebits +func BenchmarkBatchSHRMixedCreditsAndDebits(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchSHRMixedCreditsAndDebits(b) + } +} + +// testBatchSHRCreditsOnly validates BatchSHR create for an invalid CreditsOnly +func testBatchSHRCreditsOnly(t testing.TB) { + mockBatch := mockBatchSHR() + mockBatch.Header.ServiceClassCode = CreditsOnly + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(CreditsOnly, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchSHRCreditsOnly tests validating BatchSHR create for an invalid CreditsOnly +func TestBatchSHRCreditsOnly(t *testing.T) { + testBatchSHRCreditsOnly(t) +} + +// BenchmarkBatchSHRCreditsOnly benchmarks validating BatchSHR create for an invalid CreditsOnly +func BenchmarkBatchSHRCreditsOnly(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchSHRCreditsOnly(b) + } +} + +// testBatchSHRAutomatedAccountingAdvices validates BatchSHR create for an invalid AutomatedAccountingAdvices +func testBatchSHRAutomatedAccountingAdvices(t testing.TB) { + mockBatch := mockBatchSHR() + mockBatch.Header.ServiceClassCode = AutomatedAccountingAdvices + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(AutomatedAccountingAdvices, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchSHRAutomatedAccountingAdvices tests validating BatchSHR create for an invalid AutomatedAccountingAdvices +func TestBatchSHRAutomatedAccountingAdvices(t *testing.T) { + testBatchSHRAutomatedAccountingAdvices(t) +} + +// BenchmarkBatchSHRAutomatedAccountingAdvices benchmarks validating BatchSHR create for an invalid AutomatedAccountingAdvices +func BenchmarkBatchSHRAutomatedAccountingAdvices(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchSHRAutomatedAccountingAdvices(b) + } +} + +// testBatchSHRTransactionCode validates BatchSHR TransactionCode is not a credit +func testBatchSHRTransactionCode(t testing.TB) { + mockBatch := mockBatchSHR() + mockBatch.GetEntries()[0].TransactionCode = CheckingCredit + err := mockBatch.Create() + if !base.Match(err, ErrBatchDebitOnly) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchSHRTransactionCode tests validating BatchSHR TransactionCode is not a credit +func TestBatchSHRTransactionCode(t *testing.T) { + testBatchSHRTransactionCode(t) +} + +// BenchmarkBatchSHRTransactionCode benchmarks validating BatchSHR TransactionCode is not a credit +func BenchmarkBatchSHRTransactionCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchSHRTransactionCode(b) + } +} + +// testBatchSHRAddendaCount validates BatchSHR Addendum count of 2 +func testBatchSHRAddendaCount(t testing.TB) { + mockBatch := mockBatchSHR() + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02() + err := mockBatch.Create() + // TODO: are we not expecting any errors here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchSHRAddendaCount tests validating BatchSHR Addendum count of 2 +func TestBatchSHRAddendaCount(t *testing.T) { + testBatchSHRAddendaCount(t) +} + +// BenchmarkBatchSHRAddendaCount benchmarks validating BatchSHR Addendum count of 2 +func BenchmarkBatchSHRAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchSHRAddendaCount(b) + } +} + +// testBatchSHRAddendaCountZero validates Addendum count of 0 +func testBatchSHRAddendaCountZero(t testing.TB) { + mockBatch := NewBatchSHR(mockBatchSHRHeader()) + mockBatch.AddEntry(mockSHREntryDetail()) + mockAddenda02 := mockAddenda02() + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02 + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality("225", "200")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchSHRAddendaCountZero tests validating Addendum count of 0 +func TestBatchSHRAddendaCountZero(t *testing.T) { + testBatchSHRAddendaCountZero(t) +} + +// BenchmarkBatchSHRAddendaCountZero benchmarks validating Addendum count of 0 +func BenchmarkBatchSHRAddendaCountZero(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchSHRAddendaCountZero(b) + } +} + +// TestBatchSHRAddendum98 validates Addenda98 returns an error +func TestBatchSHRAddendum98(t *testing.T) { + mockBatch := NewBatchSHR(mockBatchSHRHeader()) + mockBatch.AddEntry(mockSHREntryDetail()) + mockAddenda98 := mockAddenda98() + mockAddenda98.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchSHRAddendum99 validates Addenda99 returns an error +func TestBatchSHRAddendum99(t *testing.T) { + mockBatch := NewBatchSHR(mockBatchSHRHeader()) + mockBatch.AddEntry(mockSHREntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// testBatchSHRInvalidAddendum validates Addendum must be Addenda02 +func testBatchSHRInvalidAddendum(t testing.TB) { + mockBatch := NewBatchSHR(mockBatchSHRHeader()) + mockBatch.AddEntry(mockSHREntryDetail()) + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02() + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchSHRInvalidAddendum tests validating Addendum must be Addenda02 +func TestBatchSHRInvalidAddendum(t *testing.T) { + testBatchSHRInvalidAddendum(t) +} + +// BenchmarkBatchSHRInvalidAddendum benchmarks validating Addendum must be Addenda02 +func BenchmarkBatchSHRInvalidAddendum(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchSHRInvalidAddendum(b) + } +} + +// testBatchSHRInvalidAddenda validates Addendum must be Addenda02 +func testBatchSHRInvalidAddenda(t testing.TB) { + mockBatch := NewBatchSHR(mockBatchSHRHeader()) + mockBatch.AddEntry(mockSHREntryDetail()) + addenda02 := mockAddenda02() + addenda02.TypeCode = "63" + mockBatch.GetEntries()[0].Addenda02 = addenda02 + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchSHRInvalidAddenda tests validating Addendum must be Addenda02 +func TestBatchSHRInvalidAddenda(t *testing.T) { + testBatchSHRInvalidAddenda(t) +} + +// BenchmarkBatchSHRInvalidAddenda benchmarks validating Addendum must be Addenda02 +func BenchmarkBatchSHRInvalidAddenda(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchSHRInvalidAddenda(b) + } +} + +// testBatchSHRInvalidBuild validates an invalid batch build +func testBatchSHRInvalidBuild(t testing.TB) { + mockBatch := mockBatchSHR() + mockBatch.GetHeader().ServiceClassCode = 3 + err := mockBatch.Create() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchSHRInvalidBuild tests validating an invalid batch build +func TestBatchSHRInvalidBuild(t *testing.T) { + testBatchSHRInvalidBuild(t) +} + +// BenchmarkBatchSHRInvalidBuild benchmarks validating an invalid batch build +func BenchmarkBatchSHRInvalidBuild(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchSHRInvalidBuild(b) + } +} + +// testBatchSHRCardTransactionType validates BatchSHR create for an invalid CardTransactionType +func testBatchSHRCardTransactionType(t testing.TB) { + mockBatch := mockBatchSHR() + mockBatch.GetEntries()[0].DiscretionaryData = "555" + err := mockBatch.Validate() + if !base.Match(err, ErrBatchInvalidCardTransactionType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchSHRCardTransactionType tests validating BatchSHR create for an invalid CardTransactionType +func TestBatchSHRCardTransactionType(t *testing.T) { + testBatchSHRCardTransactionType(t) +} + +// BenchmarkBatchSHRCardTransactionType benchmarks validating BatchSHR create for an invalid CardTransactionType +func BenchmarkBatchSHRCardTransactionType(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchSHRCardTransactionType(b) + } +} + +// testBatchSHRCardExpirationDateField validates SHRCardExpirationDate +// characters 0-4 of underlying IdentificationNumber +func testBatchSHRCardExpirationDateField(t testing.TB) { + mockBatch := mockBatchSHR() + ts := mockBatch.Entries[0].SHRCardExpirationDateField() + if ts != "0722" { + t.Error("Card Expiration Date is invalid") + } +} + +// TestBatchSHRCardExpirationDateField tests validatingSHRCardExpirationDate +// characters 0-4 of underlying IdentificationNumber +func TestBatchSHRCardExpirationDateField(t *testing.T) { + testBatchSHRCardExpirationDateField(t) +} + +// BenchmarkBatchSHRCardExpirationDateField benchmarks validating SHRCardExpirationDate +// characters 0-4 of underlying IdentificationNumber +func BenchmarkBatchSHRCardExpirationDateField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchSHRCardExpirationDateField(b) + } +} + +// testBatchSHRDocumentReferenceNumberField validates SHRDocumentReferenceNumberField +// characters 5-15 of underlying IdentificationNumber +func testBatchSHRDocumentReferenceNumberField(t testing.TB) { + mockBatch := mockBatchSHR() + ts := mockBatch.Entries[0].SHRDocumentReferenceNumberField() + if ts != "12345678910" { + t.Error("Document Reference Number is invalid") + } +} + +// TestBatchSHRDocumentReferenceNumberField tests validating SHRDocumentReferenceNumberField +// characters 5-15 of underlying IdentificationNumber +func TestBatchSHRDocumentReferenceNumberField(t *testing.T) { + testBatchSHRDocumentReferenceNumberField(t) +} + +// BenchmarkBatchSHRDocumentReferenceNumberField benchmarks validating SHRDocumentReferenceNumberField +// characters 5-15 of underlying IdentificationNumber +func BenchmarkSHRDocumentReferenceNumberField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchSHRDocumentReferenceNumberField(b) + } +} + +// testBatchSHRIndividualCardAccountNumberField validates SHRIndividualCardAccountNumberField +// underlying IndividualName +func testBatchSHRIndividualCardAccountNumberField(t testing.TB) { + mockBatch := mockBatchSHR() + ts := mockBatch.Entries[0].SHRIndividualCardAccountNumberField() + if ts != "0001234567891123456789" { + t.Error("Individual Card Account Number is invalid") + } +} + +// TestBatchSHRIndividualCardAccountNumberField tests validating SHRIndividualCardAccountNumberField +// underlying IndividualName +func TestBatchSHRIndividualCardAccountNumberField(t *testing.T) { + testBatchSHRIndividualCardAccountNumberField(t) +} + +// BenchmarkBatchSHRIndividualCardAccountNumberField benchmarks validating SHRIndividualCardAccountNumberField +// underlying IndividualName +func BenchmarkBatchSHRDocumentReferenceNumberField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchSHRIndividualCardAccountNumberField(b) + } +} + +// testSHRCardExpirationDateMonth validates the month is valid for CardExpirationDate +func testSHRCardExpirationDateMonth(t testing.TB) { + mockBatch := mockBatchSHR() + mockBatch.GetEntries()[0].SetSHRCardExpirationDate("1306") + err := mockBatch.Validate() + if !base.Match(err, ErrValidMonth) { + t.Errorf("%T: %s", err, err) + } +} + +// TestSHRCardExpirationDateMonth tests validating the month is valid for CardExpirationDate +func TestSHRSHRCardExpirationDateMonth(t *testing.T) { + testSHRCardExpirationDateMonth(t) +} + +// BenchmarkSHRCardExpirationDateMonth test validating the month is valid for CardExpirationDate +func BenchmarkSHRCardExpirationDateMonth(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testSHRCardExpirationDateMonth(b) + } +} + +// testSHRCardExpirationDateYear validates the year is valid for CardExpirationDate +func testSHRCardExpirationDateYear(t testing.TB) { + mockBatch := mockBatchSHR() + mockBatch.GetEntries()[0].SetSHRCardExpirationDate("0612") + err := mockBatch.Validate() + if !base.Match(err, ErrValidYear) { + t.Errorf("%T: %s", err, err) + } +} + +// TestSHRCardExpirationDateYear tests validating the year is valid for CardExpirationDate +func TestSHRSHRCardExpirationDateYear(t *testing.T) { + testSHRCardExpirationDateYear(t) +} + +// BenchmarkSHRCardExpirationDateYear test validating the year is valid for CardExpirationDate +func BenchmarkSHRCardExpirationDateYear(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testSHRCardExpirationDateYear(b) + } +} + +// TestBatchSHRAddendum99Category validates Addenda99 returns an error +func TestBatchSHRAddendum99Category(t *testing.T) { + mockBatch := NewBatchSHR(mockBatchSHRHeader()) + mockBatch.AddEntry(mockSHREntryDetail()) + mockAddenda99 := mockAddenda99() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.Entries[0].Category = CategoryNOC + mockBatch.Entries[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchSHRTerminalState validates TerminalState returns an error if invalid from usabbrev +func TestBatchSHRTerminalState(t *testing.T) { + mockBatch := NewBatchSHR(mockBatchSHRHeader()) + mockBatch.AddEntry(mockSHREntryDetail()) + mockAddenda02 := mockAddenda02() + mockAddenda02.TerminalState = "YY" + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02 + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrValidState) { + t.Errorf("%T: %s", err, err) + } +} diff --git a/batchTEL.go b/batchTEL.go new file mode 100644 index 000000000..7fd1fc27a --- /dev/null +++ b/batchTEL.go @@ -0,0 +1,77 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +// BatchTEL is a batch that handles SEC payment type Telephone-Initiated Entries (TEL) +// Telephone-Initiated Entries (TEL) are consumer debit transactions. The NACHA Operating Rules permit TEL entries when +// the Originator obtains the Receiver's authorization for the debit entry orally via the telephone. +// An entry based upon a Receiver's oral authorization must utilize the TEL (Telephone-Initiated Entry) +// Standard Entry Class (SEC) Code. +type BatchTEL struct { + Batch +} + +// NewBatchTEL returns a *BatchTEL +func NewBatchTEL(bh *BatchHeader) *BatchTEL { + batch := new(BatchTEL) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} + +// Validate ensures the batch meets NACHA rules specific to the SEC type TEL +func (batch *BatchTEL) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + // Add configuration and type specific based validation for this type. + if batch.Header.StandardEntryClassCode != TEL { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, TEL) + } + // can not have credits in TEL batches + for _, entry := range batch.Entries { + if entry.CreditOrDebit() != "D" { + return batch.Error("TransactionCode", ErrBatchDebitOnly, entry.TransactionCode) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + } + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchTEL) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + + return batch.Validate() +} diff --git a/batchTEL_test.go b/batchTEL_test.go new file mode 100644 index 000000000..20442eb52 --- /dev/null +++ b/batchTEL_test.go @@ -0,0 +1,242 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockBatchTELHeader creates a TEL batch header +func mockBatchTELHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = TEL + bh.CompanyName = "Your Company, inc" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "Vndr Pay" + bh.ODFIIdentification = "12104288" + return bh +} + +// mockTELEntryDetail creates a TEL entry detail +func mockTELEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 5000000 + entry.IdentificationNumber = "Phone 333-2222" + entry.IndividualName = "Wade Arnold" + entry.SetTraceNumber(mockBatchTELHeader().ODFIIdentification, 1) + entry.SetPaymentType("S") + return entry +} + +// mockBatchTEL creates a TEL batch +func mockBatchTEL() *BatchTEL { + mockBatch := NewBatchTEL(mockBatchTELHeader()) + mockBatch.AddEntry(mockTELEntryDetail()) + if err := mockBatch.Create(); err != nil { + panic(err) + } + return mockBatch +} + +// testBatchTELHeader creates a TEL batch header +func testBatchTELHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchTELHeader()) + err, ok := batch.(*BatchTEL) + if !ok { + t.Errorf("Expecting BatchTEL got %T", err) + } +} + +// TestBatchTELHeader tests creating a TEL batch header +func TestBatchTELHeader(t *testing.T) { + testBatchTELHeader(t) +} + +// BenchmarkBatchTELHeader benchmarks creating a TEL batch header +func BenchmarkBatchTELHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTELHeader(b) + } +} + +// testBatchTELCreate validates batch create for an invalid service code +func testBatchTELCreate(t testing.TB) { + mockBatch := mockBatchTEL() + // Batch Header information is required to Create a batch. + mockBatch.GetHeader().ServiceClassCode = 0 + err := mockBatch.Create() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTELCreate tests validating batch create for an invalid service code +func TestBatchTELCreate(t *testing.T) { + testBatchTELCreate(t) +} + +// BenchmarkBatchTELCreate benchmarks validating batch create for an invalid service code +func BenchmarkBatchTELCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTELCreate(b) + } +} + +// testBatchTELAddendaCount validates addenda count for batch TEL +func testBatchTELAddendaCount(t testing.TB) { + mockBatch := mockBatchTEL() + // TEL can not have an addenda02 + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchCalculatedControlEquality(2, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTELAddendaCount tests validating addenda count for batch TEL +func TestBatchTELAddendaCount(t *testing.T) { + testBatchTELAddendaCount(t) +} + +// BenchmarkBatchTELAddendaCount benchmarks validating addenda count for batch TEL +func BenchmarkBatchTELAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTELAddendaCount(b) + } +} + +// testBatchTELSEC validates SEC code for batch TEL +func testBatchTELSEC(t testing.TB) { + mockBatch := mockBatchTEL() + mockBatch.Header.StandardEntryClassCode = RCK + err := mockBatch.Validate() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTELSEC tests validating SEC code for batch TEL +func TestBatchTELSEC(t *testing.T) { + testBatchTELSEC(t) +} + +// BenchmarkBatchTELSEC benchmarks validating SEC code for batch TEL +func BenchmarkBatchTELSEC(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTELSEC(b) + } +} + +// testBatchTELDebit validates Transaction code for TEL entry detail +func testBatchTELDebit(t testing.TB) { + mockBatch := mockBatchTEL() + mockBatch.GetEntries()[0].TransactionCode = CheckingCredit + err := mockBatch.Create() + if !base.Match(err, ErrBatchDebitOnly) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTELDebit tests validating Transaction code for TEL entry detail +func TestBatchTELDebit(t *testing.T) { + testBatchTELDebit(t) +} + +// BenchmarkBatchTELDebit benchmarks validating Transaction code for TEL entry detail +func BenchmarkBatchTELDebit(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTELDebit(b) + } +} + +// testBatchTELPaymentType validates that the entry detail +// payment type / discretionary data is either single or reoccurring +func testBatchTELPaymentType(t testing.TB) { + mockBatch := mockBatchTEL() + mockBatch.GetEntries()[0].DiscretionaryData = "AA" + err := mockBatch.Validate() + // TODO: are we expecting there to be no errors here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTELPaymentType tests validating that the entry detail +// payment type / discretionary data is either single or reoccurring +func TestBatchTELPaymentType(t *testing.T) { + testBatchTELPaymentType(t) +} + +// BenchmarkBatchTELPaymentTyp benchmarks validating that the entry detail +// payment type / discretionary data is either single or reoccurring +func BenchmarkBatchTELPaymentType(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTELPaymentType(b) + } +} + +// TestBatchTELAddendum98 validates Addenda98 returns an error +func TestBatchTELAddendum98(t *testing.T) { + mockBatch := NewBatchTEL(mockBatchTELHeader()) + mockBatch.AddEntry(mockTELEntryDetail()) + mockAddenda98 := mockAddenda98() + mockAddenda98.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTELAddendum99 validates Addenda99 returns an error +func TestBatchTELAddendum99(t *testing.T) { + mockBatch := NewBatchTEL(mockBatchTELHeader()) + mockBatch.AddEntry(mockTELEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTELValidTranCodeForServiceClassCode validates a transactionCode based on ServiceClassCode +func TestBatchTELValidTranCodeForServiceClassCode(t *testing.T) { + mockBatch := mockBatchTEL() + mockBatch.GetHeader().ServiceClassCode = CreditsOnly + err := mockBatch.Create() + if !base.Match(err, NewErrBatchServiceClassTranCode(CreditsOnly, 27)) { + t.Errorf("%T: %s", err, err) + } +} diff --git a/batchTRC.go b/batchTRC.go new file mode 100644 index 000000000..2e7097842 --- /dev/null +++ b/batchTRC.go @@ -0,0 +1,96 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +// BatchTRC holds the BatchHeader and BatchControl and all EntryDetail for TRC Entries. +// +// Check Truncation Entry (Truncated Entry) is used to identify a debit entry of a truncated check. +type BatchTRC struct { + Batch +} + +// NewBatchTRC returns a *BatchTRC +func NewBatchTRC(bh *BatchHeader) *BatchTRC { + batch := new(BatchTRC) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} + +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the batch. +func (batch *BatchTRC) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + // Add configuration and type specific validation for this type. + + if batch.Header.StandardEntryClassCode != TRC { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, TRC) + } + + // TRC detail entries can only be a debit, ServiceClassCode must allow debits + switch batch.Header.ServiceClassCode { + case CreditsOnly: + return batch.Error("ServiceClassCode", ErrBatchServiceClassCode, batch.Header.ServiceClassCode) + } + + for _, entry := range batch.Entries { + // TRC detail entries must be a debit + if entry.CreditOrDebit() != "D" { + return batch.Error("TransactionCode", ErrBatchDebitOnly, entry.TransactionCode) + } + // ProcessControlField underlying IdentificationNumber, must be defined + if entry.ProcessControlField() == "" { + return batch.Error("ProcessControlField", ErrFieldRequired) + } + // ItemResearchNumber underlying IdentificationNumber, must be defined + if entry.ItemResearchNumber() == "" { + return batch.Error("ItemResearchNumber", ErrFieldRequired) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + } + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchTRC) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + // Additional steps specific to batch type + // ... + + return batch.Validate() +} diff --git a/batchTRC_test.go b/batchTRC_test.go new file mode 100644 index 000000000..6e0284f0c --- /dev/null +++ b/batchTRC_test.go @@ -0,0 +1,443 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockBatchTRCHeader creates a BatchTRC BatchHeader +func mockBatchTRCHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = TRC + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = TRC + bh.ODFIIdentification = "12104288" + return bh +} + +// mockTRCEntryDetail creates a BatchTRC EntryDetail +func mockTRCEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.SetCheckSerialNumber("123456789") + entry.SetProcessControlField("CHECK1") + entry.SetItemResearchNumber("182726") + entry.SetItemTypeIndicator("01") + entry.SetTraceNumber(mockBatchTRCHeader().ODFIIdentification, 1) + entry.Category = CategoryForward + return entry +} + +// mockBatchTRC creates a BatchTRC +func mockBatchTRC() *BatchTRC { + mockBatch := NewBatchTRC(mockBatchTRCHeader()) + mockBatch.AddEntry(mockTRCEntryDetail()) + if err := mockBatch.Create(); err != nil { + panic(err) + } + return mockBatch +} + +// mockBatchTRCHeaderCredit creates a BatchTRC BatchHeader +func mockBatchTRCHeaderCredit() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = TRC + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = TRC + bh.ODFIIdentification = "12104288" + return bh +} + +// mockTRCEntryDetailCredit creates a TRC EntryDetail with a credit entry +func mockTRCEntryDetailCredit() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.SetCheckSerialNumber("123456789") + entry.SetProcessControlField("CHECK1") + entry.SetItemResearchNumber("182726") + entry.SetTraceNumber(mockBatchTRCHeader().ODFIIdentification, 1) + entry.Category = CategoryForward + return entry +} + +// mockBatchTRCCredit creates a BatchTRC with a Credit entry +func mockBatchTRCCredit() *BatchTRC { + mockBatch := NewBatchTRC(mockBatchTRCHeaderCredit()) + mockBatch.AddEntry(mockTRCEntryDetailCredit()) + return mockBatch +} + +// testBatchTRCHeader creates a BatchTRC BatchHeader +func testBatchTRCHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchTRCHeader()) + err, ok := batch.(*BatchTRC) + if !ok { + t.Errorf("Expecting BatchTRC got %T", err) + } +} + +// TestBatchTRCHeader tests validating BatchTRC BatchHeader +func TestBatchTRCHeader(t *testing.T) { + testBatchTRCHeader(t) +} + +// BenchmarkBatchTRCHeader benchmarks validating BatchTRC BatchHeader +func BenchmarkBatchTRCHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRCHeader(b) + } +} + +// testBatchTRCCreate validates BatchTRC create +func testBatchTRCCreate(t testing.TB) { + mockBatch := mockBatchTRC() + if err := mockBatch.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRCCreate tests validating BatchTRC create +func TestBatchTRCCreate(t *testing.T) { + testBatchTRCCreate(t) +} + +// BenchmarkBatchTRCCreate benchmarks validating BatchTRC create +func BenchmarkBatchTRCCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRCCreate(b) + } +} + +// testBatchTRCStandardEntryClassCode validates BatchTRC create for an invalid StandardEntryClassCode +func testBatchTRCStandardEntryClassCode(t testing.TB) { + mockBatch := mockBatchTRC() + mockBatch.Header.StandardEntryClassCode = WEB + err := mockBatch.Create() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRCStandardEntryClassCode tests validating BatchTRC create for an invalid StandardEntryClassCode +func TestBatchTRCStandardEntryClassCode(t *testing.T) { + testBatchTRCStandardEntryClassCode(t) +} + +// BenchmarkBatchTRCStandardEntryClassCode benchmarks validating BatchTRC create for an invalid StandardEntryClassCode +func BenchmarkBatchTRCStandardEntryClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRCStandardEntryClassCode(b) + } +} + +// testBatchTRCServiceClassCodeEquality validates service class code equality +func testBatchTRCServiceClassCodeEquality(t testing.TB) { + mockBatch := mockBatchTRC() + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(220, MixedDebitsAndCredits)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRCServiceClassCodeEquality tests validating service class code equality +func TestBatchTRCServiceClassCodeEquality(t *testing.T) { + testBatchTRCServiceClassCodeEquality(t) +} + +// BenchmarkBatchTRCServiceClassCodeEquality benchmarks validating service class code equality +func BenchmarkBatchTRCServiceClassCodeEquality(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRCServiceClassCodeEquality(b) + } +} + +// testBatchTRCMixedCreditsAndDebits validates BatchTRC create for an invalid MixedCreditsAndDebits +func testBatchTRCMixedCreditsAndDebits(t testing.TB) { + mockBatch := mockBatchTRC() + mockBatch.Header.ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(MixedDebitsAndCredits, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRCMixedCreditsAndDebits tests validating BatchTRC create for an invalid MixedCreditsAndDebits +func TestBatchTRCMixedCreditsAndDebits(t *testing.T) { + testBatchTRCMixedCreditsAndDebits(t) +} + +// BenchmarkBatchTRCMixedCreditsAndDebits benchmarks validating BatchTRC create for an invalid MixedCreditsAndDebits +func BenchmarkBatchTRCMixedCreditsAndDebits(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRCMixedCreditsAndDebits(b) + } +} + +// testBatchTRCCreditsOnly validates BatchTRC create for an invalid CreditsOnly +func testBatchTRCCreditsOnly(t testing.TB) { + mockBatch := mockBatchTRC() + mockBatch.Header.ServiceClassCode = CreditsOnly + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(CreditsOnly, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRCCreditsOnly tests validating BatchTRC create for an invalid CreditsOnly +func TestBatchTRCCreditsOnly(t *testing.T) { + testBatchTRCCreditsOnly(t) +} + +// BenchmarkBatchTRCCreditsOnly benchmarks validating BatchTRC create for an invalid CreditsOnly +func BenchmarkBatchTRCCreditsOnly(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRCCreditsOnly(b) + } +} + +// testBatchTRCAutomatedAccountingAdvices validates BatchTRC create for an invalid AutomatedAccountingAdvices +func testBatchTRCAutomatedAccountingAdvices(t testing.TB) { + mockBatch := mockBatchTRC() + mockBatch.Header.ServiceClassCode = AutomatedAccountingAdvices + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(AutomatedAccountingAdvices, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRCAutomatedAccountingAdvices tests validating BatchTRC create for an invalid AutomatedAccountingAdvices +func TestBatchTRCAutomatedAccountingAdvices(t *testing.T) { + testBatchTRCAutomatedAccountingAdvices(t) +} + +// BenchmarkBatchTRCAutomatedAccountingAdvices benchmarks validating BatchTRC create for an invalid AutomatedAccountingAdvices +func BenchmarkBatchTRCAutomatedAccountingAdvices(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRCAutomatedAccountingAdvices(b) + } +} + +// testBatchTRCCheckSerialNumber validates BatchTRC CheckSerialNumber is not mandatory +func testBatchTRCCheckSerialNumber(t testing.TB) { + mockBatch := mockBatchTRC() + // modify CheckSerialNumber / IdentificationNumber to nothing + mockBatch.GetEntries()[0].SetCheckSerialNumber("") + err := mockBatch.Validate() + // no error expected + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRCCheckSerialNumber tests validating BatchTRC +// CheckSerialNumber / IdentificationNumber is a mandatory field +func TestBatchTRCCheckSerialNumber(t *testing.T) { + testBatchTRCCheckSerialNumber(t) +} + +// BenchmarkBatchTRCCheckSerialNumber benchmarks validating BatchTRC +// CheckSerialNumber / IdentificationNumber is a mandatory field +func BenchmarkBatchTRCCheckSerialNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRCCheckSerialNumber(b) + } +} + +// testBatchTRCTransactionCode validates BatchTRC TransactionCode is not a credit +func testBatchTRCTransactionCode(t testing.TB) { + mockBatch := mockBatchTRCCredit() + err := mockBatch.Create() + if !base.Match(err, ErrBatchDebitOnly) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRCTransactionCode tests validating BatchTRC TransactionCode is not a credit +func TestBatchTRCTransactionCode(t *testing.T) { + testBatchTRCTransactionCode(t) +} + +// BenchmarkBatchTRCTransactionCode benchmarks validating BatchTRC TransactionCode is not a credit +func BenchmarkBatchTRCTransactionCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRCTransactionCode(b) + } +} + +// testBatchTRCAddendaCount validates BatchTRC Addenda count +func testBatchTRCAddendaCount(t testing.TB) { + mockBatch := mockBatchTRC() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRCAddendaCount tests validating BatchTRC Addenda count +func TestBatchTRCAddendaCount(t *testing.T) { + testBatchTRCAddendaCount(t) +} + +// BenchmarkBatchTRCAddendaCount benchmarks validating BatchTRC Addenda count +func BenchmarkBatchTRCAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRCAddendaCount(b) + } +} + +// testBatchTRCInvalidBuild validates an invalid batch build +func testBatchTRCInvalidBuild(t testing.TB) { + mockBatch := mockBatchTRC() + mockBatch.GetHeader().ServiceClassCode = 3 + err := mockBatch.Create() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRCInvalidBuild tests validating an invalid batch build +func TestBatchTRCInvalidBuild(t *testing.T) { + testBatchTRCInvalidBuild(t) +} + +// BenchmarkBatchTRCInvalidBuild benchmarks validating an invalid batch build +func BenchmarkBatchTRCInvalidBuild(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRCInvalidBuild(b) + } +} + +// TestBatchTRCAddendum98 validates Addenda98 returns an error +func TestBatchTRCAddendum98(t *testing.T) { + mockBatch := NewBatchTRC(mockBatchTRCHeader()) + mockBatch.AddEntry(mockTRCEntryDetail()) + mockAddenda98 := mockAddenda98() + mockAddenda98.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRCAddendum99 validates Addenda99 returns an error +func TestBatchTRCAddendum99(t *testing.T) { + mockBatch := NewBatchTRC(mockBatchTRCHeader()) + mockBatch.AddEntry(mockTRCEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRCAddendum99Category validates Addenda99 returns an error +func TestBatchTRCAddendum99Category(t *testing.T) { + mockBatch := NewBatchTRC(mockBatchTRCHeader()) + mockBatch.AddEntry(mockTRCEntryDetail()) + mockAddenda99 := mockAddenda99() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].Category = CategoryForward + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRCProcessControlField returns an error if ProcessControlField is not defined. +func TestBatchTRCProcessControlField(t *testing.T) { + mockBatch := NewBatchTRC(mockBatchTRCHeader()) + mockBatch.AddEntry(mockTRCEntryDetail()) + mockBatch.GetEntries()[0].SetProcessControlField("") + err := mockBatch.Create() + if !base.Match(err, ErrFieldRequired) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRCItemResearchNumber returns an error if ItemResearchNumber is not defined. +func TestBatchTRCItemResearchNumber(t *testing.T) { + mockBatch := NewBatchTRC(mockBatchTRCHeader()) + mockBatch.AddEntry(mockTRCEntryDetail()) + mockBatch.GetEntries()[0].IndividualName = "" + mockBatch.GetEntries()[0].SetProcessControlField("CHECK1") + mockBatch.GetEntries()[0].SetItemResearchNumber("") + err := mockBatch.Create() + if !base.Match(err, ErrFieldRequired) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRCItemTypeIndicator returns an error if ItemTypeIndicator is not 01. +func TestBatchTRCItemTypeIndicator(t *testing.T) { + mockBatch := NewBatchTRC(mockBatchTRCHeader()) + mockBatch.AddEntry(mockTRCEntryDetail()) + if err := mockBatch.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + if mockBatch.GetEntries()[0].ItemTypeIndicator() != "01" { + t.Error("ItemTypeIndicator does not validate") + } +} + +// testBatchTRCMixedDebitsAndCreditsServiceClassCodeEquality validates service class code +func testBatchTRCMixedDebitsAndCreditsServiceClassCode(t testing.TB) { + mockBatch := mockBatchTRC() + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + mockBatch.Header.ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRCMixedDebitsAndCreditsServiceClassCodeEquality tests validating service class code +func TestBatchTRCMixedDebitsAndCreditsServiceClassCode(t *testing.T) { + testBatchTRCMixedDebitsAndCreditsServiceClassCode(t) +} diff --git a/batchTRX.go b/batchTRX.go new file mode 100644 index 000000000..d8014996e --- /dev/null +++ b/batchTRX.go @@ -0,0 +1,102 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strconv" +) + +// BatchTRX holds the BatchHeader and BatchControl and all EntryDetail for TRX Entries. +// +// Check Truncation Entries Exchange is used to identify a debit entry of a truncated checks (multiple). +type BatchTRX struct { + Batch +} + +// NewBatchTRX returns a *BatchTRX +func NewBatchTRX(bh *BatchHeader) *BatchTRX { + batch := new(BatchTRX) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} + +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the batch. +func (batch *BatchTRX) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + // Add configuration and type specific validation for this type. + + if batch.Header.StandardEntryClassCode != TRX { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, TRX) + } + + // TRX detail entries can only be a debit, ServiceClassCode must allow debits + switch batch.Header.ServiceClassCode { + case CreditsOnly: + return batch.Error("ServiceClassCode", ErrBatchServiceClassCode, batch.Header.ServiceClassCode) + } + + for _, entry := range batch.Entries { + // TRX detail entries must be a debit + if entry.CreditOrDebit() != "D" { + return batch.Error("TransactionCode", ErrBatchDebitOnly, entry.TransactionCode) + } + // Trapping this error, as entry.CTXAddendaRecordsField() can not be greater than 9999 + if len(entry.Addenda05) > 9999 { + return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 9999)) + } + // validate CTXAddendaRecord Field is equal to the actual number of Addenda records + // use 0 value if there is no Addenda records + addendaRecords, _ := strconv.Atoi(entry.CATXAddendaRecordsField()) + if len(entry.Addenda05) != addendaRecords { + return batch.Error("AddendaCount", NewErrBatchExpectedAddendaCount(len(entry.Addenda05), addendaRecords)) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + } + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchTRX) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + // Additional steps specific to batch type + // ... + + return batch.Validate() +} diff --git a/batchTRX_test.go b/batchTRX_test.go new file mode 100644 index 000000000..f5d2947c2 --- /dev/null +++ b/batchTRX_test.go @@ -0,0 +1,618 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "log" + "testing" + + "github.com/moov-io/base" +) + +// mockBatchTRXHeader creates a BatchTRX BatchHeader +func mockBatchTRXHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = TRX + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH TRX" + bh.ODFIIdentification = "12104288" + return bh +} + +// mockTRXEntryDetail creates a BatchTRX EntryDetail +func mockTRXEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(1) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchTRXHeader().ODFIIdentification, 1) + entry.SetItemTypeIndicator("01") + entry.Category = CategoryForward + return entry +} + +// mockBatchTRX creates a BatchTRX +func mockBatchTRX() *BatchTRX { + mockBatch := NewBatchTRX(mockBatchTRXHeader()) + mockBatch.AddEntry(mockTRXEntryDetail()) + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + mockBatch.Entries[0].AddendaRecordIndicator = 1 + if err := mockBatch.Create(); err != nil { + log.Fatal(err) + } + return mockBatch +} + +// mockBatchTRXHeaderCredit creates a BatchTRX BatchHeader +func mockBatchTRXHeaderCredit() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = TRX + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = TRX + bh.ODFIIdentification = "12104288" + return bh +} + +// mockTRXEntryDetailCredit creates a TRX EntryDetail with a credit entry +func mockTRXEntryDetailCredit() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.SetCheckSerialNumber("123456789") + entry.SetProcessControlField("CHECK1") + entry.SetItemResearchNumber("182726") + entry.SetTraceNumber(mockBatchTRXHeader().ODFIIdentification, 1) + entry.Category = CategoryForward + return entry +} + +// mockBatchTRXCredit creates a BatchTRX with a Credit entry +func mockBatchTRXCredit() *BatchTRX { + mockBatch := NewBatchTRX(mockBatchTRXHeaderCredit()) + mockBatch.AddEntry(mockTRXEntryDetailCredit()) + return mockBatch +} + +// testBatchTRXHeader creates a BatchTRX BatchHeader +func testBatchTRXHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchTRXHeader()) + err, ok := batch.(*BatchTRX) + if !ok { + t.Errorf("Expecting BatchTRX got %T", err) + } +} + +// TestBatchTRXHeader tests validating BatchTRX BatchHeader +func TestBatchTRXHeader(t *testing.T) { + testBatchTRXHeader(t) +} + +// BenchmarkBatchTRXHeader benchmarks validating BatchTRX BatchHeader +func BenchmarkBatchTRXHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRXHeader(b) + } +} + +// testBatchTRXCreate validates BatchTRX create +func testBatchTRXCreate(t testing.TB) { + mockBatch := mockBatchTRX() + if err := mockBatch.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRXCreate tests validating BatchTRX create +func TestBatchTRXCreate(t *testing.T) { + testBatchTRXCreate(t) +} + +// BenchmarkBatchTRXCreate benchmarks validating BatchTRX create +func BenchmarkBatchTRXCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRXCreate(b) + } +} + +// testBatchTRXStandardEntryClassCode validates BatchTRX create for an invalid StandardEntryClassCode +func testBatchTRXStandardEntryClassCode(t testing.TB) { + mockBatch := mockBatchTRX() + mockBatch.Header.StandardEntryClassCode = WEB + err := mockBatch.Create() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRXStandardEntryClassCode tests validating BatchTRX create for an invalid StandardEntryClassCode +func TestBatchTRXStandardEntryClassCode(t *testing.T) { + testBatchTRXStandardEntryClassCode(t) +} + +// BenchmarkBatchTRXStandardEntryClassCode benchmarks validating BatchTRX create for an invalid StandardEntryClassCode +func BenchmarkBatchTRXStandardEntryClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRXStandardEntryClassCode(b) + } +} + +// testBatchTRXServiceClassCodeEquality validates service class code equality +func testBatchTRXServiceClassCodeEquality(t testing.TB) { + mockBatch := mockBatchTRX() + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(220, MixedDebitsAndCredits)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRXServiceClassCodeEquality tests validating service class code equality +func TestBatchTRXServiceClassCodeEquality(t *testing.T) { + testBatchTRXServiceClassCodeEquality(t) +} + +// BenchmarkBatchTRXServiceClassCodeEquality benchmarks validating service class code equality +func BenchmarkBatchTRXServiceClassCodeEquality(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRXServiceClassCodeEquality(b) + } +} + +// testBatchTRXAddendaCount validates BatchTRX Addendum count of 2 +func testBatchTRXAddendaCount(t testing.TB) { + mockBatch := mockBatchTRX() + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + err := mockBatch.Create() + if !base.Match(err, NewErrBatchExpectedAddendaCount(2, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRXAddendaCount tests validating BatchTRX Addendum count of 2 +func TestBatchTRXAddendaCount(t *testing.T) { + testBatchTRXAddendaCount(t) +} + +// BenchmarkBatchTRXAddendaCount benchmarks validating BatchTRX Addendum count of 2 +func BenchmarkBatchTRXAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRXAddendaCount(b) + } +} + +// testBatchTRXAddendaCountZero validates Addendum count of 0 +func testBatchTRXAddendaCountZero(t testing.TB) { + mockBatch := NewBatchTRX(mockBatchTRXHeader()) + mockBatch.AddEntry(mockTRXEntryDetail()) + err := mockBatch.Create() + if !base.Match(err, NewErrBatchExpectedAddendaCount(0, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRXAddendaCountZero tests validating Addendum count of 0 +func TestBatchTRXAddendaCountZero(t *testing.T) { + testBatchTRXAddendaCountZero(t) +} + +// BenchmarkBatchTRXAddendaCountZero benchmarks validating Addendum count of 0 +func BenchmarkBatchTRXAddendaCountZero(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRXAddendaCountZero(b) + } +} + +// testBatchTRXInvalidAddendum validates Addendum must be Addenda05 +func testBatchTRXInvalidAddendum(t testing.TB) { + mockBatch := NewBatchTRX(mockBatchTRXHeader()) + mockBatch.AddEntry(mockTRXEntryDetail()) + mockBatch.GetEntries()[0].Addenda02 = mockAddenda02() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, NewErrBatchExpectedAddendaCount(0, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRXInvalidAddendum tests validating Addendum must be Addenda05 +func TestBatchTRXInvalidAddendum(t *testing.T) { + testBatchTRXInvalidAddendum(t) +} + +// BenchmarkBatchTRXInvalidAddendum benchmarks validating Addendum must be Addenda05 +func BenchmarkBatchTRXInvalidAddendum(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRXInvalidAddendum(b) + } +} + +// testBatchTRXInvalidAddenda validates Addendum must be Addenda05 with type code 05 +func testBatchTRXInvalidAddenda(t testing.TB) { + mockBatch := NewBatchTRX(mockBatchTRXHeader()) + mockBatch.AddEntry(mockTRXEntryDetail()) + addenda05 := mockAddenda05() + addenda05.TypeCode = "63" + mockBatch.GetEntries()[0].AddAddenda05(addenda05) + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRXInvalidAddenda tests validating Addendum must be Addenda05 with record type 7 +func TestBatchTRXInvalidAddenda(t *testing.T) { + testBatchTRXInvalidAddenda(t) +} + +// BenchmarkBatchTRXInvalidAddenda benchmarks validating Addendum must be Addenda05 with record type 7 +func BenchmarkBatchTRXInvalidAddenda(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRXInvalidAddenda(b) + } +} + +// testBatchTRXInvalidBuild validates an invalid batch build +func testBatchTRXInvalidBuild(t testing.TB) { + mockBatch := mockBatchTRX() + mockBatch.GetHeader().ServiceClassCode = 3 + err := mockBatch.Create() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRXInvalidBuild tests validating an invalid batch build +func TestBatchTRXInvalidBuild(t *testing.T) { + testBatchTRXInvalidBuild(t) +} + +// BenchmarkBatchTRXInvalidBuild benchmarks validating an invalid batch build +func BenchmarkBatchTRXInvalidBuild(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRXInvalidBuild(b) + } +} + +// testBatchTRXAddenda10000 validates error for 10000 Addenda +func testBatchTRXAddenda10000(t testing.TB) { + + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = TRX + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH TRX" + bh.ODFIIdentification = "12104288" + + entry := NewEntryDetail() + entry.TransactionCode = CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(9999) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchTRXHeader().ODFIIdentification, 1) + entry.SetItemTypeIndicator("01") + entry.Category = CategoryForward + + mockBatch := NewBatchTRX(bh) + mockBatch.AddEntry(entry) + + for i := 0; i < 10000; i++ { + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + } + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, NewErrBatchAddendaCount(10000, 9999)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRXAddenda10000 tests validating error for 10000 Addenda +func TestBatchTRXAddenda10000(t *testing.T) { + testBatchTRXAddenda10000(t) +} + +// BenchmarkBatchTRXAddenda10000 benchmarks validating error for 10000 Addenda +func BenchmarkBatchTRXAddenda10000(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRXAddenda10000(b) + } +} + +// testBatchTRXAddendaRecords validates error for AddendaRecords not equal to addendum +func testBatchTRXAddendaRecords(t testing.TB) { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = TRX + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH TRX" + bh.ODFIIdentification = "12104288" + + entry := NewEntryDetail() + entry.TransactionCode = CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(500) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchTRXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + + mockBatch := NewBatchTRX(bh) + mockBatch.AddEntry(entry) + + for i := 0; i < 565; i++ { + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + } + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, NewErrBatchExpectedAddendaCount(565, 500)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRXAddendaRecords tests validating error for AddendaRecords not equal to addendum +func TestBatchTRXAddendaRecords(t *testing.T) { + testBatchTRXAddendaRecords(t) +} + +// BenchmarkBatchAddendaRecords benchmarks validating error for AddendaRecords not equal to addendum +func BenchmarkBatchTRXAddendaRecords(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRXAddendaRecords(b) + } +} + +// testBatchTRXReceivingCompany validates TRXReceivingCompany +func testBatchTRXReceivingCompany(t testing.TB) { + mockBatch := mockBatchTRX() + //mockBatch.GetEntries()[0].SetCATXReceivingCompany("Receiver") + + if mockBatch.GetEntries()[0].CATXReceivingCompanyField() != "Receiver Company" { + t.Errorf("expected %v got %v", "Receiver Company", mockBatch.GetEntries()[0].CATXReceivingCompanyField()) + } +} + +// TestBatchTRXReceivingCompany tests validating TRXReceivingCompany +func TestBatchTRXReceivingCompany(t *testing.T) { + testBatchTRXReceivingCompany(t) +} + +// BenchmarkBatchTRXReceivingCompany benchmarks validating TRXReceivingCompany +func BenchmarkBatchTRXReceivingCompany(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRXReceivingCompany(b) + } +} + +// testBatchTRXReserved validates TRXReservedField +func testBatchTRXReserved(t testing.TB) { + mockBatch := mockBatchTRX() + + if mockBatch.GetEntries()[0].CATXReservedField() != " " { + t.Errorf("expected %v got %v", " ", mockBatch.GetEntries()[0].CATXReservedField()) + } +} + +// TestBatchTRXReserved tests validating TRXReservedField +func TestBatchTRXReserved(t *testing.T) { + testBatchTRXReserved(t) +} + +// BenchmarkBatchTRXReserved benchmarks validating TRXReservedField +func BenchmarkBatchTRXReserved(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRXReserved(b) + } +} + +// testBatchTRXZeroAddendaRecords validates zero addenda records +func testBatchTRXZeroAddendaRecords(t testing.TB) { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = TRX + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH TRX" + bh.ODFIIdentification = "12104288" + + entry := NewEntryDetail() + entry.TransactionCode = CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(1) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchTRXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + + mockBatch := NewBatchTRX(bh) + mockBatch.AddEntry(entry) + + err := mockBatch.Create() + if !base.Match(err, NewErrBatchExpectedAddendaCount(0, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRXZeroAddendaRecords tests validating zero addenda records +func TestBatchTRXZeroAddendaRecords(t *testing.T) { + testBatchTRXZeroAddendaRecords(t) +} + +// BenchmarkBatchZeroAddendaRecords benchmarks validating zero addenda records +func BenchmarkBatchTRXZeroAddendaRecords(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRXZeroAddendaRecords(b) + } +} + +// testBatchTRXTransactionCode validates BatchTRX TransactionCode is not a credit +func testBatchTRXTransactionCode(t testing.TB) { + mockBatch := mockBatchTRXCredit() + err := mockBatch.Create() + if !base.Match(err, ErrBatchDebitOnly) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRXTransactionCode tests validating BatchTRX TransactionCode is not a credit +func TestBatchTRXTransactionCode(t *testing.T) { + testBatchTRXTransactionCode(t) +} + +// BenchmarkBatchTRXTransactionCode benchmarks validating BatchTRX TransactionCode is not a credit +func BenchmarkBatchTRXTransactionCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRXTransactionCode(b) + } +} + +// TestBatchTRXAddendum98 validates Addenda98 returns an error +func TestBatchTRXAddendum98(t *testing.T) { + mockBatch := NewBatchTRX(mockBatchTRXHeader()) + mockBatch.AddEntry(mockTRXEntryDetail()) + mockAddenda98 := mockAddenda98() + mockAddenda98.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRXAddendum99 validates Addenda99 returns an error +func TestBatchTRXAddendum99(t *testing.T) { + mockBatch := NewBatchTRX(mockBatchTRXHeader()) + mockBatch.AddEntry(mockCTXEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// testBatchTRXCreditsOnly validates BatchTRX create for an invalid CreditsOnly +func testBatchTRXCreditsOnly(t testing.TB) { + mockBatch := mockBatchTRX() + mockBatch.Header.ServiceClassCode = CreditsOnly + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(CreditsOnly, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRXCreditsOnly tests validating BatchTRX create for an invalid CreditsOnly +func TestBatchTRXCreditsOnly(t *testing.T) { + testBatchTRXCreditsOnly(t) +} + +// BenchmarkBatchTRXCreditsOnly benchmarks validating BatchTRX create for an invalid CreditsOnly +func BenchmarkBatchTRXCreditsOnly(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRXCreditsOnly(b) + } +} + +// testBatchTRXAutomatedAccountingAdvices validates BatchTRX create for an invalid AutomatedAccountingAdvices +func testBatchTRXAutomatedAccountingAdvices(t testing.TB) { + mockBatch := mockBatchTRX() + mockBatch.Header.ServiceClassCode = AutomatedAccountingAdvices + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(AutomatedAccountingAdvices, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRXAutomatedAccountingAdvices tests validating BatchTRX create for an invalid AutomatedAccountingAdvices +func TestBatchTRXAutomatedAccountingAdvices(t *testing.T) { + testBatchTRXAutomatedAccountingAdvices(t) +} + +// BenchmarkBatchTRXAutomatedAccountingAdvices benchmarks validating BatchTRX create for an invalid AutomatedAccountingAdvices +func BenchmarkBatchTRXAutomatedAccountingAdvices(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTRXAutomatedAccountingAdvices(b) + } +} + +// TestBatchTRXAddenda02 validates BatchTRX create for an invalid Addenda02 +func TestBatchTRXAddenda02(t *testing.T) { + mockBatch := mockBatchTRX() + mockBatch.Entries[0].Addenda02 = mockAddenda02() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// testBatchTRXMixedDebitsAndCreditsServiceClassCode validates MixedDebitsAndCredits service class code +func testBatchTRXMixedDebitsAndCreditsServiceClassCode(t testing.TB) { + mockBatch := mockBatchTRX() + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + mockBatch.Header.ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchTRXMixedDebitsAndCreditsServiceClassCode tests validates MixedDebitsAndCredits service class code +func TestBatchTRXMixedDebitsAndCreditsServiceClassCode(t *testing.T) { + testBatchTRXMixedDebitsAndCreditsServiceClassCode(t) +} diff --git a/batchWEB_test.go b/batchWEB_test.go index 50301d712..104134b63 100644 --- a/batchWEB_test.go +++ b/batchWEB_test.go @@ -1,146 +1,320 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + package ach -import "testing" +import ( + "testing" + + "github.com/moov-io/base" +) +// mockBatchWEBHeader creates a WEB batch header func mockBatchWEBHeader() *BatchHeader { bh := NewBatchHeader() - bh.ServiceClassCode = 220 - bh.StandardEntryClassCode = "WEB" + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = WEB bh.CompanyName = "Your Company, inc" - bh.CompanyIdentification = "123456789" + bh.CompanyIdentification = "121042882" bh.CompanyEntryDescription = "Online Order" - bh.ODFIIdentification = 6200001 + bh.ODFIIdentification = "12104288" return bh } +// mockWEBEntryDetail creates a WEB entry detail func mockWEBEntryDetail() *EntryDetail { entry := NewEntryDetail() - entry.TransactionCode = 22 - entry.SetRDFI(9101298) + entry.TransactionCode = CheckingCredit + entry.SetRDFI("231380104") entry.DFIAccountNumber = "123456789" entry.Amount = 100000000 entry.IndividualName = "Wade Arnold" - entry.TraceNumber = 123456789 + entry.SetTraceNumber(mockBatchWEBHeader().ODFIIdentification, 1) entry.SetPaymentType("S") return entry } +// mockBatchWEB creates a WEB batch func mockBatchWEB() *BatchWEB { - mockBatch := NewBatchWEB() - mockBatch.SetHeader(mockBatchWEBHeader()) + mockBatch := NewBatchWEB(mockBatchWEBHeader()) mockBatch.AddEntry(mockWEBEntryDetail()) - mockBatch.GetEntries()[0].AddAddenda(mockAddenda()) + mockBatch.GetEntries()[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) if err := mockBatch.Create(); err != nil { panic(err) } return mockBatch } -// A Batch web can only have one addendum per entry detail -func TestBatchWEBAddendumCount(t *testing.T) { +// testBatchWebAddenda validates No more than 1 batch per entry detail record can exist +// and no more than 1 addenda record per entry detail record can exist +func testBatchWebAddenda(t testing.TB) { mockBatch := mockBatchWEB() - // Adding a second addenda to the mock entry - mockBatch.GetEntries()[0].AddAddenda(mockAddenda()) - - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "EntryAddendaCount" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + // mock batch already has one addenda. Creating two addenda should error + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + err := mockBatch.Create() + if !base.Match(err, NewErrBatchAddendaCount(2, 1)) { + t.Errorf("%T: %s", err, err) } } -// No more than 1 batch per entry detail record can exist +// TestBatchWebAddenda tests validating no more than 1 batch per entry detail +// record can exist and no more than 1 addenda record per entry detail record can exist func TestBatchWebAddenda(t *testing.T) { + testBatchWebAddenda(t) +} + +// BenchmarkBatchWebAddenda benchmarks validating no more than 1 batch per entry detail +// record can exist and no more than 1 addenda record per entry detail record can exist +func BenchmarkBatchWebAddenda(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchWebAddenda(b) + } +} + +// testBatchWebIndividualNameRequired validates Individual name is a mandatory field +func testBatchWebIndividualNameRequired(t testing.TB) { mockBatch := mockBatchWEB() // mock batch already has one addenda. Creating two addenda should error - mockBatch.GetEntries()[0].AddAddenda(mockAddenda()) - if err := mockBatch.Create(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "AddendaCount" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + mockBatch.GetEntries()[0].IndividualName = "" + err := mockBatch.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) } } -// Individual name is a mandatory field +// TestBatchWebIndividualNameRequired tests validating Individual name is a mandatory field func TestBatchWebIndividualNameRequired(t *testing.T) { + testBatchWebIndividualNameRequired(t) +} + +// BenchmarkBatchWebIndividualNameRequired benchmarks validating Individual name is a mandatory field +func BenchmarkBatchWebIndividualNameRequired(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchWebIndividualNameRequired(b) + } +} + +// TestBatchWEBAddendum98 validates Addenda98 returns an error +func TestBatchWEBAddendum98(t *testing.T) { + mockBatch := NewBatchWEB(mockBatchWEBHeader()) + mockBatch.AddEntry(mockWEBEntryDetail()) + mockAddenda98 := mockAddenda98() + mockAddenda98.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchWEBAddendum99 validates Addenda99 returns an error +func TestBatchWEBAddendum99(t *testing.T) { + mockBatch := NewBatchWEB(mockBatchWEBHeader()) + mockBatch.AddEntry(mockWEBEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// testBatchWEBAddendaTypeCode validates addenda type code is valid +func testBatchWEBAddendaTypeCode(t testing.TB) { mockBatch := mockBatchWEB() - // mock batch already has one addenda. Creating two addenda should error - mockBatch.GetEntries()[0].IndividualName = "" - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "IndividualName" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + mockBatch.GetEntries()[0].Addenda05[0].TypeCode = "02" + err := mockBatch.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) } } -// verify addenda type code is 05 +// TestBatchWEBAddendaTypeCode tests validating addenda type code is valid func TestBatchWEBAddendaTypeCode(t *testing.T) { + testBatchWEBAddendaTypeCode(t) +} + +// BenchmarkBatchWEBAddendaTypeCode benchmarks validating addenda type code is valid +func BenchmarkBatchWEBAddendaTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchWEBAddendaTypeCode(b) + } +} + +// testBatchWebSEC validates that the standard entry class code is WEB for batch Web +func testBatchWebSEC(t testing.TB) { mockBatch := mockBatchWEB() - mockBatch.GetEntries()[0].Addendum[0].TypeCode = "07" - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "TypeCode" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + mockBatch.Header.StandardEntryClassCode = RCK + err := mockBatch.Validate() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) } } -// verify that the standard entry class code is WEB for batchWeb +// TestBatchWebSEC tests validating that the standard entry class code is WEB for batch WEB func TestBatchWebSEC(t *testing.T) { - mockBatch := mockBatchWEB() - mockBatch.header.StandardEntryClassCode = "RCK" - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "StandardEntryClassCode" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + testBatchWebSEC(t) +} + +// BenchmarkBatchWebSEC benchmarks validating that the standard entry class code is WEB for batch WEB +func BenchmarkBatchWebSEC(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchWebSEC(b) } } -// verify that the entry detail payment type / discretionary data is either single or reoccurring for the -func TestBatchWebPaymentType(t *testing.T) { +// testBatchWebPaymentType validates that the entry detail +// payment type / discretionary data is either single or reoccurring +func testBatchWebPaymentType(t testing.TB) { mockBatch := mockBatchWEB() mockBatch.GetEntries()[0].DiscretionaryData = "AA" - if err := mockBatch.Validate(); err != nil { - if e, ok := err.(*BatchError); ok { - if e.FieldName != "PaymentType" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + err := mockBatch.Validate() + // TODO: are we expecting there to be no errors here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) } } -func TestBatchWebCreate(t *testing.T) { +// TestBatchWebPaymentType tests validating that the entry detail +// payment type / discretionary data is either single or reoccurring +func TestBatchWebPaymentType(t *testing.T) { + testBatchWebPaymentType(t) +} + +// BenchmarkBatchWebPaymentType benchmarks validating that the entry detail +// payment type / discretionary data is either single or reoccurring +func BenchmarkBatchWebPaymentType(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchWebPaymentType(b) + } +} + +// testBatchWebCreate creates a WEB batch +func testBatchWebCreate(t testing.TB) { mockBatch := mockBatchWEB() // Must have valid batch header to create a batch mockBatch.GetHeader().ServiceClassCode = 63 - if err := mockBatch.Create(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "ServiceClassCode" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + err := mockBatch.Create() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchWebCreate tests creating a WEB batch +func TestBatchWebCreate(t *testing.T) { + testBatchWebCreate(t) +} + +// BenchmarkBatchWebCreate benchmarks creating a WEB batch +func BenchmarkBatchWebCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchWebCreate(b) + } +} + +// testBatchWebPaymentTypeR validates that the entry detail +// payment type / discretionary data is reoccurring +func testBatchWebPaymentTypeR(t testing.TB) { + mockBatch := mockBatchWEB() + mockBatch.GetEntries()[0].DiscretionaryData = "R" + err := mockBatch.Validate() + // TODO: are we expecting there to be no errors here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } + if mockBatch.GetEntries()[0].PaymentTypeField() != "R" { + t.Errorf("PaymentTypeField %v was expecting R", mockBatch.GetEntries()[0].PaymentTypeField()) + } +} + +// TestBatchWebPaymentTypeR tests validating that the entry detail +// payment type / discretionary data is reoccurring +func TestBatchWebPaymentTypeR(t *testing.T) { + testBatchWebPaymentTypeR(t) +} + +// BenchmarkBatchWebPaymentTypeR benchmarks validating that the entry detail +// payment type / discretionary data is reoccurring +func BenchmarkBatchWebPaymentTypeR(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchWebPaymentTypeR(b) + } +} + +// TestBatchWEBAddendum99Category validates Addenda99 returns an error +func TestBatchWEBAddendum99Category(t *testing.T) { + mockBatch := NewBatchWEB(mockBatchWEBHeader()) + mockBatch.AddEntry(mockWEBEntryDetail()) + mockAddenda99 := mockAddenda99() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.Entries[0].Category = CategoryForward + mockBatch.Entries[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchWEBCategoryReturn validates CategoryReturn returns an error +func TestBatchWEBCategoryReturn(t *testing.T) { + mockBatch := NewBatchWEB(mockBatchWEBHeader()) + mockBatch.AddEntry(mockWEBEntryDetail()) + mockAddenda05 := mockAddenda05() + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.GetEntries()[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05) + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchWEBCategoryReturnAddenda98 validates CategoryReturn returns an error +func TestBatchWEBCategoryReturnAddenda98(t *testing.T) { + mockBatch := NewBatchWEB(mockBatchWEBHeader()) + mockBatch.AddEntry(mockWEBEntryDetail()) + mockAddenda98 := mockAddenda98() + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.GetEntries()[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchWEBValidTranCodeForServiceClassCode validates a transactionCode based on ServiceClassCode +func TestBatchWEBValidTranCodeForServiceClassCode(t *testing.T) { + mockBatch := mockBatchWEB() + mockBatch.GetHeader().ServiceClassCode = DebitsOnly + err := mockBatch.Create() + if !base.Match(err, NewErrBatchServiceClassTranCode(DebitsOnly, 22)) { + t.Errorf("%T: %s", err, err) } } diff --git a/batchWeb.go b/batchWeb.go index af24c0383..8413876e0 100644 --- a/batchWeb.go +++ b/batchWeb.go @@ -1,31 +1,35 @@ -package ach +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. -import ( - "fmt" - "strings" -) +package ach // BatchWEB creates a batch file that handles SEC payment type WEB. // Entry submitted pursuant to an authorization obtained solely via the Internet or a wireless network // For consumer accounts only. type BatchWEB struct { - batch + Batch } // NewBatchWEB returns a *BatchWEB -func NewBatchWEB(params ...BatchParam) *BatchWEB { +func NewBatchWEB(bh *BatchHeader) *BatchWEB { batch := new(BatchWEB) batch.SetControl(NewBatchControl()) - - if len(params) > 0 { - bh := NewBatchHeader(params[0]) - bh.StandardEntryClassCode = "WEB" - batch.SetHeader(bh) - return batch - } - bh := NewBatchHeader() - bh.StandardEntryClassCode = "WEB" batch.SetHeader(bh) + batch.SetID(bh.ID) return batch } @@ -35,50 +39,38 @@ func (batch *BatchWEB) Validate() error { if err := batch.verify(); err != nil { return err } - // Add configuration based validation for this type. - // Web can have up to one addenda per entry record - if err := batch.isAddendaCount(1); err != nil { - return err - } - if err := batch.isTypeCode("05"); err != nil { - return err - } - - // Add type specific validation. - if batch.header.StandardEntryClassCode != "WEB" { - msg := fmt.Sprintf(msgBatchSECType, batch.header.StandardEntryClassCode, "WEB") - return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "StandardEntryClassCode", Msg: msg} + // Add configuration and type specific validation for this type. + if batch.Header.StandardEntryClassCode != WEB { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, WEB) } - if err := batch.isPaymentTypeCode(); err != nil { - return err + for _, entry := range batch.Entries { + // WEB can have up to one Addenda05 record + if len(entry.Addenda05) > 1 { + return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 1)) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } } - return nil } -// Create builds the batch sequence numbers and batch control. Additional creation +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. func (batch *BatchWEB) Create() error { // generates sequence numbers and batch control if err := batch.build(); err != nil { return err } - if err := batch.Validate(); err != nil { - return err - } - return nil -} - -// isPaymentTypeCode checks that the Entry detail records have either: -// "R" For a recurring WEB Entry -// "S" For a Single-Entry WEB Entry -func (batch *BatchWEB) isPaymentTypeCode() error { - for _, entry := range batch.entries { - if !strings.Contains(strings.ToUpper(entry.PaymentType()), "S") && !strings.Contains(strings.ToUpper(entry.PaymentType()), "R") { - msg := fmt.Sprintf(msgBatchWebPaymentType, entry.PaymentType()) - return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "PaymentType", Msg: msg} - } - } - return nil + return batch.Validate() } diff --git a/batchXCK.go b/batchXCK.go new file mode 100644 index 000000000..b0be396f5 --- /dev/null +++ b/batchXCK.go @@ -0,0 +1,100 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +// BatchXCK holds the BatchHeader and BatchControl and all EntryDetail for XCK Entries. +// +// Destroyed Check Entry identifies a debit entry initiated for a XCK eligible items. +type BatchXCK struct { + Batch +} + +// NewBatchXCK returns a *BatchXCK +func NewBatchXCK(bh *BatchHeader) *BatchXCK { + batch := new(BatchXCK) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + batch.SetID(bh.ID) + return batch +} + +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the batch. +func (batch *BatchXCK) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + // Add configuration and type specific validation for this type. + + if batch.Header.StandardEntryClassCode != XCK { + return batch.Error("StandardEntryClassCode", ErrBatchSECType, XCK) + } + + // XCK detail entries can only be a debit, ServiceClassCode must allow debits + switch batch.Header.ServiceClassCode { + case CreditsOnly: + return batch.Error("ServiceClassCode", ErrBatchServiceClassCode, batch.Header.ServiceClassCode) + } + + for _, entry := range batch.Entries { + // XCK detail entries must be a debit + if entry.CreditOrDebit() != "D" { + return batch.Error("TransactionCode", ErrBatchDebitOnly, entry.TransactionCode) + } + // Amount must be 2,500 or less + if entry.Amount > 250000 { + return batch.Error("Amount", NewErrBatchAmount(entry.Amount, 250000)) + } + // ProcessControlField underlying IdentificationNumber, must be defined + if entry.ProcessControlField() == "" { + return batch.Error("ProcessControlField", ErrFieldRequired) + } + // ItemResearchNumber underlying IdentificationNumber, must be defined + if entry.ItemResearchNumber() == "" { + return batch.Error("ItemResearchNumber", ErrFieldRequired) + } + // Verify the TransactionCode is valid for a ServiceClassCode + if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { + return err + } + // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode + if err := batch.addendaFieldInclusion(entry); err != nil { + return err + } + } + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (batch *BatchXCK) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + // Additional steps specific to batch type + // ... + + return batch.Validate() +} diff --git a/batchXCK_test.go b/batchXCK_test.go new file mode 100644 index 000000000..b437d2db8 --- /dev/null +++ b/batchXCK_test.go @@ -0,0 +1,441 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" + + "github.com/moov-io/base" +) + +// mockBatchXCKHeader creates a BatchXCK BatchHeader +func mockBatchXCKHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = XCK + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = XCK + bh.ODFIIdentification = "12104288" + return bh +} + +// mockXCKEntryDetail creates a BatchXCK EntryDetail +func mockXCKEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.SetCheckSerialNumber("123456789") + entry.SetProcessControlField("CHECK1") + entry.SetItemResearchNumber("182726") + entry.DiscretionaryData = "" + entry.SetTraceNumber(mockBatchXCKHeader().ODFIIdentification, 1) + entry.Category = CategoryForward + return entry +} + +// mockBatchXCK creates a BatchXCK +func mockBatchXCK() *BatchXCK { + mockBatch := NewBatchXCK(mockBatchXCKHeader()) + mockBatch.AddEntry(mockXCKEntryDetail()) + if err := mockBatch.Create(); err != nil { + panic(err) + } + return mockBatch +} + +// mockBatchXCKHeaderCredit creates a BatchXCK BatchHeader +func mockBatchXCKHeaderCredit() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = DebitsOnly + bh.StandardEntryClassCode = XCK + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = XCK + bh.ODFIIdentification = "12104288" + return bh +} + +// mockXCKEntryDetailCredit creates a XCK EntryDetail with a credit entry +func mockXCKEntryDetailCredit() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.SetCheckSerialNumber("123456789") + entry.SetProcessControlField("CHECK1") + entry.SetItemResearchNumber("182726") + entry.SetTraceNumber(mockBatchXCKHeader().ODFIIdentification, 1) + entry.Category = CategoryForward + return entry +} + +// mockBatchXCKCredit creates a BatchXCK with a Credit entry +func mockBatchXCKCredit() *BatchXCK { + mockBatch := NewBatchXCK(mockBatchXCKHeaderCredit()) + mockBatch.AddEntry(mockXCKEntryDetailCredit()) + return mockBatch +} + +// testBatchXCKHeader creates a BatchXCK BatchHeader +func testBatchXCKHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchXCKHeader()) + err, ok := batch.(*BatchXCK) + if !ok { + t.Errorf("Expecting BatchXCK got %T", err) + } +} + +// TestBatchXCKHeader tests validating BatchXCK BatchHeader +func TestBatchXCKHeader(t *testing.T) { + testBatchXCKHeader(t) +} + +// BenchmarkBatchXCKHeader benchmarks validating BatchXCK BatchHeader +func BenchmarkBatchXCKHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchXCKHeader(b) + } +} + +// testBatchXCKCreate validates BatchXCK create +func testBatchXCKCreate(t testing.TB) { + mockBatch := mockBatchXCK() + if err := mockBatch.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchXCKCreate tests validating BatchXCK create +func TestBatchXCKCreate(t *testing.T) { + testBatchXCKCreate(t) +} + +// BenchmarkBatchXCKCreate benchmarks validating BatchXCK create +func BenchmarkBatchXCKCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchXCKCreate(b) + } +} + +// testBatchXCKStandardEntryClassCode validates BatchXCK create for an invalid StandardEntryClassCode +func testBatchXCKStandardEntryClassCode(t testing.TB) { + mockBatch := mockBatchXCK() + mockBatch.Header.StandardEntryClassCode = WEB + err := mockBatch.Create() + if !base.Match(err, ErrBatchSECType) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchXCKStandardEntryClassCode tests validating BatchXCK create for an invalid StandardEntryClassCode +func TestBatchXCKStandardEntryClassCode(t *testing.T) { + testBatchXCKStandardEntryClassCode(t) +} + +// BenchmarkBatchXCKStandardEntryClassCode benchmarks validating BatchXCK create for an invalid StandardEntryClassCode +func BenchmarkBatchXCKStandardEntryClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchXCKStandardEntryClassCode(b) + } +} + +// testBatchXCKServiceClassCodeEquality validates service class code equality +func testBatchXCKServiceClassCodeEquality(t testing.TB) { + mockBatch := mockBatchXCK() + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(220, MixedDebitsAndCredits)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchXCKServiceClassCodeEquality tests validating service class code equality +func TestBatchXCKServiceClassCodeEquality(t *testing.T) { + testBatchXCKServiceClassCodeEquality(t) +} + +// BenchmarkBatchXCKServiceClassCodeEquality benchmarks validating service class code equality +func BenchmarkBatchXCKServiceClassCodeEquality(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchXCKServiceClassCodeEquality(b) + } +} + +// testBatchXCKMixedCreditsAndDebits validates BatchXCK create for an invalid MixedCreditsAndDebits +func testBatchXCKMixedCreditsAndDebits(t testing.TB) { + mockBatch := mockBatchXCK() + mockBatch.Header.ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(MixedDebitsAndCredits, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchXCKMixedCreditsAndDebits tests validating BatchXCK create for an invalid MixedCreditsAndDebits +func TestBatchXCKMixedCreditsAndDebits(t *testing.T) { + testBatchXCKMixedCreditsAndDebits(t) +} + +// BenchmarkBatchXCKMixedCreditsAndDebits benchmarks validating BatchXCK create for an invalid MixedCreditsAndDebits +func BenchmarkBatchXCKMixedCreditsAndDebits(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchXCKMixedCreditsAndDebits(b) + } +} + +// testBatchXCKCreditsOnly validates BatchXCK create for an invalid CreditsOnly +func testBatchXCKCreditsOnly(t testing.TB) { + mockBatch := mockBatchXCK() + mockBatch.Header.ServiceClassCode = CreditsOnly + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(CreditsOnly, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchXCKCreditsOnly tests validating BatchXCK create for an invalid CreditsOnly +func TestBatchXCKCreditsOnly(t *testing.T) { + testBatchXCKCreditsOnly(t) +} + +// BenchmarkBatchXCKCreditsOnly benchmarks validating BatchXCK create for an invalid CreditsOnly +func BenchmarkBatchXCKCreditsOnly(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchXCKCreditsOnly(b) + } +} + +// testBatchXCKAutomatedAccountingAdvices validates BatchXCK create for an invalid AutomatedAccountingAdvices +func testBatchXCKAutomatedAccountingAdvices(t testing.TB) { + mockBatch := mockBatchXCK() + mockBatch.Header.ServiceClassCode = AutomatedAccountingAdvices + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchHeaderControlEquality(AutomatedAccountingAdvices, 225)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchXCKAutomatedAccountingAdvices tests validating BatchXCK create for an invalid AutomatedAccountingAdvices +func TestBatchXCKAutomatedAccountingAdvices(t *testing.T) { + testBatchXCKAutomatedAccountingAdvices(t) +} + +// BenchmarkBatchXCKAutomatedAccountingAdvices benchmarks validating BatchXCK create for an invalid AutomatedAccountingAdvices +func BenchmarkBatchXCKAutomatedAccountingAdvices(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchXCKAutomatedAccountingAdvices(b) + } +} + +// testBatchXCKCheckSerialNumber validates BatchXCK CheckSerialNumber is not mandatory +func testBatchXCKCheckSerialNumber(t testing.TB) { + mockBatch := mockBatchXCK() + // modify CheckSerialNumber / IdentificationNumber to nothing + mockBatch.GetEntries()[0].SetCheckSerialNumber("") + err := mockBatch.Validate() + // no error expected + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchXCKCheckSerialNumber tests validating BatchXCK +// CheckSerialNumber / IdentificationNumber is a mandatory field +func TestBatchXCKCheckSerialNumber(t *testing.T) { + testBatchXCKCheckSerialNumber(t) +} + +// BenchmarkBatchXCKCheckSerialNumber benchmarks validating BatchXCK +// CheckSerialNumber / IdentificationNumber is a mandatory field +func BenchmarkBatchXCKCheckSerialNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchXCKCheckSerialNumber(b) + } +} + +// testBatchXCKTransactionCode validates BatchXCK TransactionCode is not a credit +func testBatchXCKTransactionCode(t testing.TB) { + mockBatch := mockBatchXCKCredit() + err := mockBatch.Create() + if !base.Match(err, ErrBatchDebitOnly) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchXCKTransactionCode tests validating BatchXCK TransactionCode is not a credit +func TestBatchXCKTransactionCode(t *testing.T) { + testBatchXCKTransactionCode(t) +} + +// BenchmarkBatchXCKTransactionCode benchmarks validating BatchXCK TransactionCode is not a credit +func BenchmarkBatchXCKTransactionCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchXCKTransactionCode(b) + } +} + +// testBatchXCKAddendaCount validates BatchXCK Addenda count +func testBatchXCKAddendaCount(t testing.TB) { + mockBatch := mockBatchXCK() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchXCKAddendaCount tests validating BatchXCK Addenda count +func TestBatchXCKAddendaCount(t *testing.T) { + testBatchXCKAddendaCount(t) +} + +// BenchmarkBatchXCKAddendaCount benchmarks validating BatchXCK Addenda count +func BenchmarkBatchXCKAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchXCKAddendaCount(b) + } +} + +// testBatchXCKInvalidBuild validates an invalid batch build +func testBatchXCKInvalidBuild(t testing.TB) { + mockBatch := mockBatchXCK() + mockBatch.GetHeader().ServiceClassCode = 3 + err := mockBatch.Create() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchXCKInvalidBuild tests validating an invalid batch build +func TestBatchXCKInvalidBuild(t *testing.T) { + testBatchXCKInvalidBuild(t) +} + +// BenchmarkBatchXCKInvalidBuild benchmarks validating an invalid batch build +func BenchmarkBatchXCKInvalidBuild(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchXCKInvalidBuild(b) + } +} + +// TestBatchXCKAddendum98 validates Addenda98 returns an error +func TestBatchXCKAddendum98(t *testing.T) { + mockBatch := NewBatchXCK(mockBatchXCKHeader()) + mockBatch.AddEntry(mockXCKEntryDetail()) + mockAddenda98 := mockAddenda98() + mockAddenda98.TypeCode = "05" + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.GetEntries()[0].Addenda98 = mockAddenda98 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchXCKAddendum99 validates Addenda99 returns an error +func TestBatchXCKAddendum99(t *testing.T) { + mockBatch := NewBatchXCK(mockBatchXCKHeader()) + mockBatch.AddEntry(mockXCKEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchXCKAddendum99Category validates Addenda99 returns an error +func TestBatchXCKAddendum99Category(t *testing.T) { + mockBatch := NewBatchXCK(mockBatchXCKHeader()) + mockBatch.AddEntry(mockXCKEntryDetail()) + mockAddenda99 := mockAddenda99() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].Category = CategoryForward + mockBatch.GetEntries()[0].Addenda99 = mockAddenda99 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaCategory) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchXCKProcessControlField returns an error if ProcessControlField is not defined. +func TestBatchXCKProcessControlField(t *testing.T) { + mockBatch := NewBatchXCK(mockBatchXCKHeader()) + mockBatch.AddEntry(mockXCKEntryDetail()) + mockBatch.GetEntries()[0].SetProcessControlField("") + err := mockBatch.Create() + if !base.Match(err, ErrFieldRequired) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchXCKItemResearchNumber returns an error if ItemResearchNumber is not defined. +func TestBatchXCKItemResearchNumber(t *testing.T) { + mockBatch := NewBatchXCK(mockBatchXCKHeader()) + mockBatch.AddEntry(mockXCKEntryDetail()) + mockBatch.GetEntries()[0].IndividualName = "" + mockBatch.GetEntries()[0].SetProcessControlField("CHECK1") + mockBatch.GetEntries()[0].SetItemResearchNumber("") + err := mockBatch.Create() + if !base.Match(err, ErrFieldRequired) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchXCKAmount validates BatchXCK create for an invalid Amount +func TestBatchXCKAmount(t *testing.T) { + mockBatch := mockBatchXCK() + mockBatch.Entries[0].Amount = 260000 + err := mockBatch.Create() + if !base.Match(err, NewErrBatchAmount(260000, 250000)) { + t.Errorf("%T: %s", err, err) + } +} + +// testBatchXCKMixedDebitsAndCreditsServiceClassCode validates MixedDebitsAndCredits service class code +func testBatchXCKMixedDebitsAndCreditsServiceClassCode(t testing.TB) { + mockBatch := mockBatchXCK() + mockBatch.GetControl().ServiceClassCode = MixedDebitsAndCredits + mockBatch.Header.ServiceClassCode = MixedDebitsAndCredits + err := mockBatch.Validate() + if err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchXCKMixedDebitsAndCreditsServiceClassCode tests validates MixedDebitsAndCredits service class code +func TestBatchXCKMixedDebitsAndCreditsServiceClassCode(t *testing.T) { + testBatchXCKMixedDebitsAndCreditsServiceClassCode(t) +} diff --git a/batch_test.go b/batch_test.go new file mode 100644 index 000000000..b61ae1b25 --- /dev/null +++ b/batch_test.go @@ -0,0 +1,1550 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "bytes" + "encoding/json" + "os" + "path/filepath" + "reflect" + "strconv" + "strings" + "testing" + "time" + + "github.com/moov-io/base" + + "github.com/stretchr/testify/require" +) + +// batch should never be used directly. +func mockBatch() *Batch { + mockBatch := &Batch{} + mockBatch.SetHeader(mockBatchHeader()) + mockBatch.AddEntry(mockEntryDetail()) + if err := mockBatch.build(); err != nil { + panic(err) + } + return mockBatch +} + +// Batch with mismatched TraceNumber ODFI +func mockBatchInvalidTraceNumberODFI() *Batch { + mockBatch := &Batch{} + mockBatch.SetHeader(mockBatchHeader()) + mockBatch.AddEntry(mockEntryDetailInvalidTraceNumberODFI()) + return mockBatch +} + +// EntryDetail with mismatched TraceNumber ODFI +func mockEntryDetailInvalidTraceNumberODFI() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("121042882") + entry.DFIAccountNumber = "123456789" + entry.Amount = 100000000 + entry.IndividualName = "Wade Arnold" + entry.SetTraceNumber("9928272", 1) + entry.IdentificationNumber = "ABC##jvkdjfuiwn" + entry.Category = CategoryForward + return entry +} + +// Batch with no entries +func mockBatchNoEntry() *Batch { + mockBatch := &Batch{} + mockBatch.SetHeader(mockBatchHeader()) + return mockBatch +} + +// Invalid SEC CODE BatchHeader +func mockBatchInvalidSECHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = "NIL" + bh.CompanyName = "ACME Corporation" + bh.CompanyIdentification = "123456789" + bh.CompanyEntryDescription = "PAYROLL" + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "123456789" + return bh +} + +func TestBatch__MarshalJSON(t *testing.T) { + b := mockBatch() + b.WithOffset(&Offset{ + RoutingNumber: "987654320", + }) + + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(b); err != nil { + t.Fatal(err) + } + if s := buf.String(); !strings.Contains(s, `"offset":{"routingNumber":"987654320"`) { + t.Errorf("unexpected JSON: %v", s) + } +} + +// TestBatch__UnmarshalJSON reads an example File (with Batches) and attempts to unmarshal it as JSON +func TestBatch__UnmarshalJSON(t *testing.T) { + // Make sure we don't panic with nil in the mix + var batch *Batch + if err := batch.UnmarshalJSON(nil); err != nil && !strings.Contains(err.Error(), "unexpected end of JSON input") { + t.Fatal(err) + } + + // Read file, convert to JSON + fd, err := os.Open(filepath.Join("test", "ach-pos-read", "pos-debit.ach")) + if err != nil { + t.Fatal(err) + } + f, err := NewReader(fd).Read() + if err != nil { + t.Fatal(err) + } + + bs, err := json.Marshal(f) + if err != nil { + t.Fatal(err) + } + + // Read as JSON + file, err := FileFromJSON(bs) + if err != nil { + t.Fatal(err) + } + if file == nil { + t.Fatal("file == nil") + } + if v := file.Header.FileCreationDate; v != "180614" { + t.Errorf("got FileCreationDate of %q", v) + } + if v := file.Header.FileCreationTime; v != "0000" { + t.Errorf("got FileCreationTime of %q", v) + } +} + +// Test cases that apply to all batch types +// testBatchNumberMismatch validates BatchNumber mismatch +func testBatchNumberMismatch(t testing.TB) { + mockBatch := mockBatch() + mockBatch.GetControl().BatchNumber = 2 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchHeaderControlEquality(1, 2)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchNumberMismatch tests validating BatchNumber mismatch +func TestBatchNumberMismatch(t *testing.T) { + testBatchNumberMismatch(t) +} + +// BenchmarkBatchNumberMismatch benchmarks validating BatchNumber mismatch +func BenchmarkBatchNumberMismatch(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchNumberMismatch(b) + } +} + +// testCreditBatchIsBatchAmount validates Batch TotalCreditEntryDollarAmount +func testCreditBatchIsBatchAmount(t testing.TB) { + mockBatch := mockBatch() + mockBatch.SetHeader(mockBatchHeader()) + e1 := mockBatch.GetEntries()[0] + e1.TransactionCode = CheckingCredit + e1.Amount = 100 + e2 := mockEntryDetail() + e2.TransactionCode = CheckingCredit + e2.Amount = 100 + // replace last 2 of TraceNumber + e2.TraceNumber = e1.TraceNumber[:13] + "10" + mockBatch.AddEntry(e2) + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + mockBatch.GetControl().TotalCreditEntryDollarAmount = 1 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchCalculatedControlEquality(200, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestCreditBatchIsBatchAmount test validating Batch TotalCreditEntryDollarAmount +func TestCreditBatchIsBatchAmount(t *testing.T) { + testCreditBatchIsBatchAmount(t) +} + +// BenchmarkCreditBatchIsBatchAmount benchmarks Batch TotalCreditEntryDollarAmount +func BenchmarkCreditBatchIsBatchAmount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testCreditBatchIsBatchAmount(b) + } + +} + +// testSavingsBatchIsBatchAmount validates Batch TotalDebitEntryDollarAmount +func testSavingsBatchIsBatchAmount(t testing.TB) { + mockBatch := mockBatch() + mockBatch.SetHeader(mockBatchHeader()) + e1 := mockBatch.GetEntries()[0] + e1.TransactionCode = SavingsCredit + e1.Amount = 100 + e2 := mockEntryDetail() + e2.TransactionCode = SavingsDebit + e2.Amount = 100 + // replace last 2 of TraceNumber + e2.TraceNumber = e1.TraceNumber[:13] + "10" + + mockBatch.AddEntry(e2) + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + mockBatch.GetControl().TotalDebitEntryDollarAmount = 1 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchCalculatedControlEquality(200, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestSavingsBatchIsBatchAmount tests validating Batch TotalDebitEntryDollarAmount +func TestSavingsBatchIsBatchAmount(t *testing.T) { + testSavingsBatchIsBatchAmount(t) +} + +// BenchmarkSavingsBatchIsBatchAmount benchmarks validating Batch TotalDebitEntryDollarAmount +func BenchmarkSavingsBatchIsBatchAmount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testSavingsBatchIsBatchAmount(b) + } +} + +func testBatchIsEntryHash(t testing.TB) { + mockBatch := mockBatch() + mockBatch.GetControl().EntryHash = 1 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchCalculatedControlEquality(12104288, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +func TestBatchIsEntryHash(t *testing.T) { + testBatchIsEntryHash(t) +} + +func BenchmarkBatchIsEntryHash(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchIsEntryHash(b) + } +} + +func testBatchDNEMismatch(t testing.TB) { + bh := mockBatchHeader() + bh.StandardEntryClassCode = DNE + mockBatch := mockBatch() + mockBatch.SetHeader(bh) + ed := mockBatch.GetEntries()[0] + ed.AddAddenda05(mockAddenda05()) + ed.AddAddenda05(mockAddenda05()) + mockBatch.build() + + mockBatch.GetHeader().OriginatorStatusCode = 1 + mockBatch.GetEntries()[0].TransactionCode = CheckingPrenoteCredit + err := mockBatch.verify() + if !base.Match(err, ErrBatchOriginatorDNE) { + t.Errorf("%T: %s", err, err) + } +} + +func TestBatchDNEMismatch(t *testing.T) { + testBatchDNEMismatch(t) +} + +func BenchmarkBatchDNEMismatch(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchDNEMismatch(b) + } +} + +func TestBatch__DNEOriginatorCheck(t *testing.T) { + bh := mockBatchHeader() + bh.OriginatorStatusCode = 1 + bh.StandardEntryClassCode = PPD + + batch := mockBatch() + batch.SetHeader(bh) + + if err := batch.isOriginatorDNE(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +func testBatchTraceNumberNotODFI(t testing.TB) { + mockBatch := mockBatch() + mockBatch.GetEntries()[0].SetTraceNumber("12345678", 1) + err := mockBatch.verify() + if !base.Match(err, NewErrBatchTraceNumberNotODFI("12104288", "12345678")) { + t.Errorf("%T: %s", err, err) + } +} + +func TestBatchTraceNumberNotODFI(t *testing.T) { + testBatchTraceNumberNotODFI(t) +} + +func BenchmarkBatchTraceNumberNotODFI(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTraceNumberNotODFI(b) + } +} + +func testBatchEntryCountEquality(t testing.TB) { + mockBatch := mockBatch() + mockBatch.SetHeader(mockBatchHeader()) + e := mockEntryDetail() + a := mockAddenda05() + e.AddAddenda05(a) + mockBatch.AddEntry(e) + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + mockBatch.GetControl().EntryAddendaCount = 1 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchCalculatedControlEquality(3, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +func TestBatchEntryCountEquality(t *testing.T) { + testBatchEntryCountEquality(t) +} + +func BenchmarkBatchEntryCountEquality(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchEntryCountEquality(b) + } +} + +func testBatchAddendaIndicator(t testing.TB) { + mockBatch := mockBatch() + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + mockBatch.GetEntries()[0].AddendaRecordIndicator = 0 + mockBatch.GetControl().EntryAddendaCount = 2 + err := mockBatch.verify() + if !base.Match(err, ErrBatchAddendaIndicator) { + t.Errorf("%T: %s", err, err) + } +} + +func TestBatchAddendaIndicator(t *testing.T) { + testBatchAddendaIndicator(t) +} + +func BenchmarkBatchAddendaIndicator(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchAddendaIndicator(b) + } +} + +func testBatchIsAddendaSeqAscending(t testing.TB) { + mockBatch := mockBatch() + ed := mockBatch.GetEntries()[0] + ed.AddAddenda05(mockAddenda05()) + ed.AddAddenda05(mockAddenda05()) + mockBatch.build() + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].Addenda05[0].SequenceNumber = 2 + mockBatch.GetEntries()[0].Addenda05[1].SequenceNumber = 1 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchAscending(2, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +func TestBatchIsAddendaSeqAscending(t *testing.T) { + testBatchIsAddendaSeqAscending(t) +} +func BenchmarkBatchIsAddendaSeqAscending(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchIsAddendaSeqAscending(b) + } +} + +func testBatchIsSequenceAscending(t testing.TB) { + mockBatch := mockBatch() + e3 := mockEntryDetail() + e3.TraceNumber = "1" + mockBatch.AddEntry(e3) + mockBatch.GetControl().EntryAddendaCount = 2 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchAscending(121042880000001, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +func TestBatchIsSequenceAscending(t *testing.T) { + testBatchIsSequenceAscending(t) +} + +func BenchmarkBatchIsSequenceAscending(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchIsSequenceAscending(b) + } +} + +func testBatchAddendaTraceNumber(t testing.TB) { + mockBatch := mockBatch() + mockBatch.GetEntries()[0].AddAddenda05(mockAddenda05()) + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + mockBatch.Entries[0].AddendaRecordIndicator = 1 + mockBatch.GetEntries()[0].Addenda05[0].EntryDetailSequenceNumber = 99 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchAscending("1", "1")) { + t.Errorf("%T: %s", err, err) + } +} + +func TestBatchAddendaTraceNumber(t *testing.T) { + testBatchAddendaTraceNumber(t) +} + +func BenchmarkBatchAddendaTraceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchAddendaTraceNumber(b) + } +} + +// testNewBatchDefault validates error for NewBatch if invalid SEC Code +func testNewBatchDefault(t testing.TB) { + _, err := NewBatch(mockBatchInvalidSECHeader()) + + if err != NewErrFileUnknownSEC("NIL") { + t.Errorf("%T: %s", err, err) + } +} + +// TestNewBatchDefault test validating error for NewBatch if invalid SEC Code +func TestNewBatchDefault(t *testing.T) { + testNewBatchDefault(t) +} + +// BenchmarkNewBatchDefault benchmarks validating error for NewBatch if +// invalid SEC Code +func BenchmarkNewBatchDefault(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testNewBatchDefault(b) + } +} + +// testBatchCategory validates Batch Category +func testBatchCategory(t testing.TB) { + mockBatch := mockBatch() + // Add a Addenda Return to the mock batch + entry := mockEntryDetail() + entry.Addenda99 = mockAddenda99() + entry.Category = CategoryReturn + mockBatch.AddEntry(entry) + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + if mockBatch.Category() != CategoryReturn { + t.Errorf("Addenda99 added to batch and category is %s", mockBatch.Category()) + } +} + +// TestBatchCategory tests validating Batch Category +func TestBatchCategory(t *testing.T) { + testBatchCategory(t) +} + +// BenchmarkBatchCategory benchmarks validating Batch Category +func BenchmarkBatchCategory(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCategory(b) + } +} + +// testBatchCategoryForwardReturn validates Category based on EntryDetail +func testBatchCategoryForwardReturn(t testing.TB) { + mockBatch := mockBatch() + // Add a Addenda Return to the mock batch + entry := mockEntryDetail() + entry.Addenda99 = mockAddenda99() + entry.Category = CategoryReturn + // replace last 2 of TraceNumber + entry.TraceNumber = entry.TraceNumber[:13] + "10" + entry.AddendaRecordIndicator = 1 + mockBatch.AddEntry(entry) + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + err := mockBatch.verify() + if !base.Match(err, NewErrBatchCategory("Return", "Forward")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchCategoryForwardReturn tests validating Category based on EntryDetail +func TestBatchCategoryForwardReturn(t *testing.T) { + testBatchCategoryForwardReturn(t) +} + +// BenchmarkBatchCategoryForwardReturn benchmarks validating Category based on EntryDetail +func BenchmarkBatchCategoryForwardReturn(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchCategoryForwardReturn(b) + } +} + +// Don't over write a batch trace number when building if it already exists +func testBatchTraceNumberExists(t testing.TB) { + mockBatch := mockBatch() + entry := mockEntryDetail() + traceBefore := entry.TraceNumberField() + mockBatch.AddEntry(entry) + mockBatch.build() + traceAfter := mockBatch.GetEntries()[1].TraceNumberField() + if traceBefore != traceAfter { + t.Errorf("Trace number was set to %v before batch.build and is now %v\n", traceBefore, traceAfter) + } +} + +func TestBatchTraceNumberExists(t *testing.T) { + testBatchTraceNumberExists(t) +} + +func BenchmarkBatchTraceNumberExists(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchTraceNumberExists(b) + } +} + +func testBatchFieldInclusion(t testing.TB) { + mockBatch := mockBatch() + mockBatch.Header.ODFIIdentification = "" + err := mockBatch.verify() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +func TestBatchFieldInclusion(t *testing.T) { + testBatchFieldInclusion(t) +} + +func BenchmarkBatchFieldInclusion(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchFieldInclusion(b) + } +} + +// testBatchInvalidTraceNumberODFI validates TraceNumberODFI +func testBatchInvalidTraceNumberODFI(t testing.TB) { + mockBatch := mockBatchInvalidTraceNumberODFI() + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchInvalidTraceNumberODFI tests validating TraceNumberODFI +func TestBatchInvalidTraceNumberODFI(t *testing.T) { + testBatchInvalidTraceNumberODFI(t) +} + +// BenchmarkBatchInvalidTraceNumberODFI benchmarks validating TraceNumberODFI +func BenchmarkBatchInvalidTraceNumberODFI(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchInvalidTraceNumberODFI(b) + } +} + +// testBatchNoEntry validates error for a batch with no entries +func testBatchNoEntry(t testing.TB) { + mockBatch := mockBatchNoEntry() + err := mockBatch.build() + if !base.Match(err, ErrBatchNoEntries) { + t.Errorf("%T: %s", err, err) + } + + // test verify + err = mockBatch.verify() + if !base.Match(err, ErrBatchNoEntries) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchNoEntry tests validating error for a batch with no entries +func TestBatchNoEntry(t *testing.T) { + testBatchNoEntry(t) +} + +// BenchmarkBatchNoEntry benchmarks validating error for a batch with no entries +func BenchmarkBatchNoEntry(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchNoEntry(b) + } +} + +// testBatchControl validates BatchControl ODFIIdentification +func testBatchControl(t testing.TB) { + mockBatch := mockBatch() + mockBatch.Control.ODFIIdentification = "" + err := mockBatch.verify() + if !base.Match(err, NewErrBatchHeaderControlEquality("12104288", "")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchControl tests validating BatchControl ODFIIdentification +func TestBatchControl(t *testing.T) { + testBatchControl(t) +} + +// BenchmarkBatchControl benchmarks validating BatchControl ODFIIdentification +func BenchmarkBatchControl(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchControl(b) + } +} + +// testIATBatch validates an IAT batch returns an error for batch +func testIATBatch(t testing.TB) { + bh := NewBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.StandardEntryClassCode = IAT + bh.CompanyName = "ACME Corporation" + bh.CompanyIdentification = "123456789" + bh.CompanyEntryDescription = "PAYROLL" + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "123456789" + + _, err := NewBatch(bh) + + if err != ErrFileIATSEC { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatch tests validating an IAT batch returns an error for batch +func TestIATBatch(t *testing.T) { + testIATBatch(t) +} + +// BenchmarkIATBatch benchmarks validating an IAT batch returns an error for batch +func BenchmarkIATBatch(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatch(b) + } +} + +func TestBatchInvalidServiceClassCode(t *testing.T) { + batch := mockBatch() + require.NoError(t, batch.build()) + + batch.Control.ServiceClassCode = DebitsOnly + err := batch.verify() + if !base.Match(err, NewErrBatchHeaderControlEquality("280", "220")) { + t.Errorf("%T: %s", err, err) + } + + batch.SetValidation(&ValidateOpts{ + UnequalServiceClassCode: true, + }) + err = batch.verify() + require.NoError(t, err) +} + +// TestBatchADVInvalidServiceClassCode validates ServiceClassCode +func TestBatchADVInvalidServiceClassCode(t *testing.T) { + mockBatch := mockBatchADV() + if err := mockBatch.Create(); err != nil { + t.Fatal(err) + } + mockBatch.ADVControl.ServiceClassCode = CreditsOnly + err := mockBatch.verify() + if !base.Match(err, NewErrBatchHeaderControlEquality("280", "220")) { + t.Errorf("%T: %s", err, err) + } + + mockBatch.SetValidation(&ValidateOpts{ + UnequalServiceClassCode: true, + }) + err = mockBatch.verify() + require.NoError(t, err) +} + +// TestBatchADVInvalidODFIIdentification validates ODFIIdentification +func TestBatchADVInvalidODFIIdentification(t *testing.T) { + mockBatch := mockBatchADV() + if err := mockBatch.Create(); err != nil { + t.Fatal(err) + } + mockBatch.ADVControl.ODFIIdentification = "231380104" + err := mockBatch.verify() + if !base.Match(err, NewErrBatchHeaderControlEquality("12104288", "231380104")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchADVInvalidBatchNumber validates BatchNumber +func TestBatchADVInvalidBatchNumber(t *testing.T) { + mockBatch := mockBatchADV() + if err := mockBatch.Create(); err != nil { + t.Fatal(err) + } + mockBatch.ADVControl.BatchNumber = 2 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchHeaderControlEquality("1", "2")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchADVEntryAddendaCount validates EntryAddendaCount +func TestBatchADVInvalidEntryAddendaCount(t *testing.T) { + mockBatch := mockBatchADV() + if err := mockBatch.Create(); err != nil { + t.Fatal(err) + } + mockBatch.ADVControl.EntryAddendaCount = CheckingCredit + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchCalculatedControlEquality(1, 22)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchADVTotalDebitEntryDollarAmount validates TotalDebitEntryDollarAmount +func TestBatchADVInvalidTotalDebitEntryDollarAmount(t *testing.T) { + mockBatch := mockBatchADV() + mockBatch.GetADVEntries()[0].TransactionCode = DebitForCreditsOriginated + if err := mockBatch.Create(); err != nil { + t.Fatal(err) + } + mockBatch.ADVControl.TotalDebitEntryDollarAmount = 2200 + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchCalculatedControlEquality(50000, 2200)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchADVTotalCreditEntryDollarAmount validates TotalCreditEntryDollarAmount +func TestBatchADVInvalidTotalCreditEntryDollarAmount(t *testing.T) { + mockBatch := mockBatchADV() + if err := mockBatch.Create(); err != nil { + t.Fatal(err) + } + mockBatch.ADVControl.TotalCreditEntryDollarAmount = 2200 + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchCalculatedControlEquality(50000, 2200)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchADVEntryHash validates EntryHash +func TestBatchADVInvalidEntryHash(t *testing.T) { + mockBatch := mockBatchADV() + if err := mockBatch.Create(); err != nil { + t.Fatal(err) + } + mockBatch.ADVControl.EntryHash = 2200233 + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchCalculatedControlEquality(23138010, 2200233)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchAddenda98InvalidAddendaRecordIndicator validates AddendaRecordIndicator +func TestBatchAddenda98InvalidAddendaRecordIndicator(t *testing.T) { + mockBatch := mockBatchCOR() + mockBatch.GetEntries()[0].AddendaRecordIndicator = 0 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaIndicator) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchAddenda02InvalidAddendaRecordIndicator validates AddendaRecordIndicator +func TestBatchAddenda02InvalidAddendaRecordIndicator(t *testing.T) { + mockBatch := mockBatchPOS() + mockBatch.GetEntries()[0].AddendaRecordIndicator = 0 + err := mockBatch.Create() + if !base.Match(err, ErrBatchAddendaIndicator) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchADVCategory validates Category +func TestBatchADVCategory(t *testing.T) { + mockBatch := mockBatchADV() + + entryOne := NewADVEntryDetail() + entryOne.TransactionCode = CreditForDebitsOriginated + entryOne.SetRDFI("231380104") + entryOne.DFIAccountNumber = "744-5678-99" + entryOne.Amount = 50000 + entryOne.AdviceRoutingNumber = "121042882" + entryOne.FileIdentification = "FILE1" + entryOne.ACHOperatorData = "" + entryOne.IndividualName = "Name" + entryOne.DiscretionaryData = "" + entryOne.AddendaRecordIndicator = 0 + entryOne.ACHOperatorRoutingNumber = "01100001" + entryOne.JulianDay = 50 + entryOne.SequenceNumber = 1 + entryOne.Category = CategoryReturn + + mockBatch.AddADVEntry(entryOne) + err := mockBatch.Create() + if !base.Match(err, NewErrBatchCategory("Return", "Forward")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchDishonoredReturnsCategory validates Category for Returns +func TestBatchDishonoredReturnsCategory(t *testing.T) { + entry := NewEntryDetail() + entry.TransactionCode = CheckingDebit + entry.SetRDFI("121042882") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.IdentificationNumber = "45689033" + entry.IndividualName = "Wade Arnold" + entry.SetTraceNumber(mockBatchPOSHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.AddendaRecordIndicator = 1 + entry.Category = CategoryDishonoredReturn + + addenda99Dishonored := mockAddenda99Dishonored() + entry.Addenda99Dishonored = addenda99Dishonored + + entryOne := NewEntryDetail() + entryOne.TransactionCode = CheckingDebit + entryOne.SetRDFI("121042882") + entryOne.DFIAccountNumber = "744-5678-99" + entryOne.Amount = 23000 + entryOne.IdentificationNumber = "45689033" + entryOne.IndividualName = "Adam Decaf" + entryOne.SetTraceNumber(mockBatchPOSHeader().ODFIIdentification, 1) + entryOne.DiscretionaryData = "01" + entryOne.AddendaRecordIndicator = 1 + entryOne.Category = CategoryReturn + + addenda99DishonoredOne := mockAddenda99Dishonored() + entryOne.Addenda99Dishonored = addenda99DishonoredOne + + posHeader := NewBatchHeader() + posHeader.ServiceClassCode = DebitsOnly + posHeader.StandardEntryClassCode = POS + posHeader.CompanyName = "Payee Name" + posHeader.CompanyIdentification = "231380104" + posHeader.CompanyEntryDescription = "ACH POS" + posHeader.ODFIIdentification = "23138010" + + batch := NewBatchPOS(posHeader) + batch.SetHeader(posHeader) + batch.AddEntry(entry) + batch.AddEntry(entryOne) + + err := batch.Create() + if !base.Match(err, NewErrBatchCategory("Return", "DishonoredReturn")) { + t.Errorf("%T: %s", err, err) + } + + // Fix the entry after ensuring validation works + entryOne.Category = CategoryDishonoredReturn + + file := NewFile() + file.SetHeader(mockFileHeader()) + file.Control = mockFileControl() + file.AddBatch(batch) + + if err := file.Create(); err != nil { + t.Fatal(err) + } + + var buf bytes.Buffer + if err := NewWriter(&buf).Write(file); err != nil { + t.Fatal(err) + } + + path := filepath.Join("examples", "testdata", "dishonored-return.ach") + err = os.WriteFile(path, buf.Bytes(), 0600) + if err != nil { + t.Fatal(err) + } +} + +// TestBatchConvertBatchType validates ConvertBatchType +func TestBatchConvertBatchType(t *testing.T) { + mockBatchACK := mockBatchACK() + convertedACK := ConvertBatchType(mockBatchACK.Batch) + if reflect.TypeOf(convertedACK) != reflect.TypeOf(mockBatchACK) { + t.Error("ACK batch type is not converted correctly") + } + mockBatchADV := mockBatchADV() + convertedADV := ConvertBatchType(mockBatchADV.Batch) + if reflect.TypeOf(convertedADV) != reflect.TypeOf(mockBatchADV) { + t.Error("ADV batch type is not converted correctly") + } + mockBatchARC := mockBatchARC() + convertedARC := ConvertBatchType(mockBatchARC.Batch) + if reflect.TypeOf(convertedARC) != reflect.TypeOf(mockBatchARC) { + t.Error("ARC batch type is not converted correctly") + } + mockBatchATX := mockBatchATX() + convertedATX := ConvertBatchType(mockBatchATX.Batch) + if reflect.TypeOf(convertedATX) != reflect.TypeOf(mockBatchATX) { + t.Error("ATX batch type is not converted correctly") + } + mockBatchBOC := mockBatchBOC() + convertedBOC := ConvertBatchType(mockBatchBOC.Batch) + if reflect.TypeOf(convertedBOC) != reflect.TypeOf(mockBatchBOC) { + t.Error("BOC batch type is not converted correctly") + } + mockBatchCCD := mockBatchCCD() + convertedCCD := ConvertBatchType(mockBatchCCD.Batch) + if reflect.TypeOf(convertedCCD) != reflect.TypeOf(mockBatchCCD) { + t.Error("CCD batch type is not converted correctly") + } + mockBatchCIE := mockBatchCIE() + convertedCIE := ConvertBatchType(mockBatchCIE.Batch) + if reflect.TypeOf(convertedCIE) != reflect.TypeOf(mockBatchCIE) { + t.Error("CIE batch type is not converted correctly") + } + mockBatchCOR := mockBatchCOR() + convertedCOR := ConvertBatchType(mockBatchCOR.Batch) + if reflect.TypeOf(convertedCOR) != reflect.TypeOf(mockBatchCOR) { + t.Error("COR batch type is not converted correctly") + } + mockBatchCTX := mockBatchCTX() + convertedCTX := ConvertBatchType(mockBatchCTX.Batch) + if reflect.TypeOf(convertedCTX) != reflect.TypeOf(mockBatchCTX) { + t.Error("CTX batch type is not converted correctly") + } + mockBatchDNE := mockBatchDNE() + convertedDNE := ConvertBatchType(mockBatchDNE.Batch) + if reflect.TypeOf(convertedDNE) != reflect.TypeOf(mockBatchDNE) { + t.Error("DNE batch type is not converted correctly") + } + mockBatchENR := mockBatchENR() + convertedENR := ConvertBatchType(mockBatchENR.Batch) + if reflect.TypeOf(convertedENR) != reflect.TypeOf(mockBatchENR) { + t.Error("ENR batch type is not converted correctly") + } + mockBatchMTE := mockBatchMTE() + convertedMTE := ConvertBatchType(mockBatchMTE.Batch) + if reflect.TypeOf(convertedMTE) != reflect.TypeOf(mockBatchMTE) { + t.Error("MTE batch type is not converted correctly") + } + mockBatchPOP := mockBatchPOP() + convertedPOP := ConvertBatchType(mockBatchPOP.Batch) + if reflect.TypeOf(convertedPOP) != reflect.TypeOf(mockBatchPOP) { + t.Error("POP batch type is not converted correctly") + } + mockBatchPOS := mockBatchPOS() + convertedPOS := ConvertBatchType(mockBatchPOS.Batch) + if reflect.TypeOf(convertedPOS) != reflect.TypeOf(mockBatchPOS) { + t.Error("POS batch type is not converted correctly") + } + mockBatchPPD := mockBatchPPD() + convertedPPD := ConvertBatchType(mockBatchPPD.Batch) + if reflect.TypeOf(convertedPPD) != reflect.TypeOf(mockBatchPPD) { + t.Error("PPD batch type is not converted correctly") + } + mockBatchRCK := mockBatchRCK() + convertedRCK := ConvertBatchType(mockBatchRCK.Batch) + if reflect.TypeOf(convertedRCK) != reflect.TypeOf(mockBatchRCK) { + t.Error("RCK batch type is not converted correctly") + } + mockBatchSHR := mockBatchSHR() + convertedSHR := ConvertBatchType(mockBatchSHR.Batch) + if reflect.TypeOf(convertedSHR) != reflect.TypeOf(mockBatchSHR) { + t.Error("SHR batch type is not converted correctly") + } + mockBatchTEL := mockBatchTEL() + convertedTEL := ConvertBatchType(mockBatchTEL.Batch) + if reflect.TypeOf(convertedTEL) != reflect.TypeOf(mockBatchTEL) { + t.Error("TEL batch type is not converted correctly") + } + mockBatchTRC := mockBatchTRC() + convertedTRC := ConvertBatchType(mockBatchTRC.Batch) + if reflect.TypeOf(convertedTRC) != reflect.TypeOf(mockBatchTRC) { + t.Error("TRC batch type is not converted correctly") + } + mockBatchTRX := mockBatchTRX() + convertedTRX := ConvertBatchType(mockBatchTRX.Batch) + if reflect.TypeOf(convertedTRX) != reflect.TypeOf(mockBatchTRX) { + t.Error("TRX batch type is not converted correctly") + } + mockBatchWEB := mockBatchWEB() + convertedWEB := ConvertBatchType(mockBatchWEB.Batch) + if reflect.TypeOf(convertedWEB) != reflect.TypeOf(mockBatchWEB) { + t.Error("WEB batch type is not converted correctly") + } + mockBatchXCK := mockBatchXCK() + convertedXCK := ConvertBatchType(mockBatchXCK.Batch) + if reflect.TypeOf(convertedXCK) != reflect.TypeOf(mockBatchXCK) { + t.Error("XCK batch type is not converted correctly") + } +} + +func TestBatch__Equal(t *testing.T) { + testFile := func(t *testing.T) *File { + t.Helper() + fd, err := os.Open(filepath.Join("test", "testdata", "ppd-debit.ach")) + if err != nil { + t.Fatal(err) + } + defer fd.Close() + file, err := NewReader(fd).Read() + if err != nil { + t.Fatal(err) + } + return &file + } + + firstBatch := testFile(t).Batches[0] + + // Let's check and ensure equality + secondBatch := testFile(t).Batches[0] + if !firstBatch.Equal(secondBatch) { + t.Fatal("identical .Equal failed, uhh") + } + + // nil cases + var b *Batch + if b.Equal(secondBatch) || secondBatch.Equal(nil) { + t.Fatalf("b.Equal(secondBatch)=%v secondBatch.Equal(nil)=%v", b.Equal(secondBatch), secondBatch.Equal(nil)) + } + + // Now change each field in .Equal and see + secondBatch = testFile(t).Batches[0] + secondBatch.GetHeader().ServiceClassCode = 1 + if firstBatch.Equal(secondBatch) { + t.Error("changed ServiceClassCode, expected not equal") + } + + secondBatch = testFile(t).Batches[0] + secondBatch.GetHeader().StandardEntryClassCode = "ZZZ" + if firstBatch.Equal(secondBatch) { + t.Error("changed StandardEntryClassCode, expected not equal") + } + + secondBatch = testFile(t).Batches[0] + secondBatch.GetHeader().CompanyName = "foo" + if firstBatch.Equal(secondBatch) { + t.Error("changed CompanyName, expected not equal") + } + + secondBatch = testFile(t).Batches[0] + secondBatch.GetHeader().CompanyIdentification = "new company" + if firstBatch.Equal(secondBatch) { + t.Error("changed CompanyIdentification, expected not equal") + } + + secondBatch = testFile(t).Batches[0] + secondBatch.GetHeader().EffectiveEntryDate = "1111" + if firstBatch.Equal(secondBatch) { + t.Error("changed EffectiveEntryDate, expected not equal") + } + + secondBatch = testFile(t).Batches[0] + secondBatch.GetHeader().ODFIIdentification = "12" + if firstBatch.Equal(secondBatch) { + t.Error("changed ODFIIdentification, expected not equal") + } + + // Check differences in EntryDetail + secondBatch = testFile(t).Batches[0] + secondBatch.GetEntries()[0].TransactionCode = 1 + if firstBatch.Equal(secondBatch) { + t.Error("changed TransactionCode, expected not equal") + } + + secondBatch = testFile(t).Batches[0] + secondBatch.GetEntries()[0].RDFIIdentification = "41" + if firstBatch.Equal(secondBatch) { + t.Error("changed RDFIIdentification, expected not equal") + } + + secondBatch = testFile(t).Batches[0] + secondBatch.GetEntries()[0].DFIAccountNumber = "542" + if firstBatch.Equal(secondBatch) { + t.Error("changed DFIAccountNumber, expected not equal") + } + + secondBatch = testFile(t).Batches[0] + secondBatch.GetEntries()[0].Amount = 1 + if firstBatch.Equal(secondBatch) { + t.Error("changed Amount, expected not equal") + } + + secondBatch = testFile(t).Batches[0] + secondBatch.GetEntries()[0].IdentificationNumber = "99" + if firstBatch.Equal(secondBatch) { + t.Error("changed IdentificationNumber, expected not equal") + } + + secondBatch = testFile(t).Batches[0] + secondBatch.GetEntries()[0].IndividualName = "jane doe" + if firstBatch.Equal(secondBatch) { + t.Error("changed IndividualName, expected not equal") + } + + secondBatch = testFile(t).Batches[0] + secondBatch.GetEntries()[0].DiscretionaryData = "other info" + if firstBatch.Equal(secondBatch) { + t.Error("changed DiscretionaryData, expected not equal") + } + + // Add another EntryDetail and make sure we fail + secondBatch = testFile(t).Batches[0] + secondBatch.AddEntry(secondBatch.GetEntries()[0]) + if firstBatch.Equal(secondBatch) { + t.Error("added EntryDetail, expected not equal") + } +} + +func TestBatchABA8(t *testing.T) { + routingNumber := "231380104" + if v := aba8(routingNumber); v != "23138010" { + t.Errorf("got %s", v) + } + // 10 digit from ACH server + if v := aba8("0123456789"); v != "12345678" { + t.Errorf("got %s", v) + } + if v := aba8(""); v != "" { + t.Errorf("got %s", v) + } + if v := aba8(strings.Repeat("1", 20)); v != "" { + t.Errorf("got %s", v) + } + if v := aba8("2123456789"); v != "" { + t.Errorf("got %s", v) + } +} + +func TestBatch__lastTraceNumber(t *testing.T) { + if n := lastTraceNumber(nil); n != 0 { + t.Errorf("lastTraceNumber=%d", n) + } + + var entries []*EntryDetail + entries = append(entries, &EntryDetail{ + TraceNumber: "1241", + }) + entries = append(entries, &EntryDetail{ + TraceNumber: "1244", + }) + if n := lastTraceNumber(entries); n != 1244 { + t.Errorf("lastTraceNumber=%d", n) + } + + // invalid TraceNumber + entries = []*EntryDetail{ + { + TraceNumber: "AA", + }, + } + if n := lastTraceNumber(entries); n != 0 { + t.Errorf("lastTraceNumber=%d", n) + } +} + +func TestBatch__CalculateBalancedOffsetCredit(t *testing.T) { + f := mockFilePPD() + b, ok := f.Batches[0].(*BatchPPD) + if !ok { + t.Fatalf("got %T: %#v", f.Batches[0], f.Batches[0]) + } + + b.Header.ServiceClassCode = MixedDebitsAndCredits + b.Entries[0].TransactionCode = CheckingDebit + if err := f.Create(); err != nil { + t.Fatal(err) + } + b.WithOffset(&Offset{ + RoutingNumber: "121042882", + AccountNumber: "123456789", + AccountType: OffsetChecking, + Description: "test offset", + }) + + if err := f.Batches[0].Create(); err != nil { + t.Error(err) + } + if len(b.Entries) != 2 { + t.Errorf("got %d Entries", len(b.Entries)) + } + off := b.Entries[1] + if off.Amount != b.Entries[0].Amount { + t.Errorf("offset.Amount=%d b.Entries[0].Amount=%d", off.Amount, b.Entries[0].Amount) + } + if off.TransactionCode != CheckingCredit { + t.Errorf("unexpected TransactionCode: %d", off.TransactionCode) + } + if b.Control.TotalDebitEntryDollarAmount != b.Control.TotalCreditEntryDollarAmount { + t.Errorf("debits=%d credits=%d", b.Control.TotalDebitEntryDollarAmount, b.Control.TotalCreditEntryDollarAmount) + } +} + +func TestBatch__CalculateBalancedOffsetDebit(t *testing.T) { + f := mockFilePPD() + b, ok := f.Batches[0].(*BatchPPD) + if !ok { + t.Fatalf("got %T: %#v", f.Batches[0], f.Batches[0]) + } + + b.Header.ServiceClassCode = MixedDebitsAndCredits + b.Entries[0].TransactionCode = CheckingCredit + if err := f.Create(); err != nil { + t.Fatal(err) + } + b.WithOffset(&Offset{ + RoutingNumber: "121042882", + AccountNumber: "123456789", + AccountType: OffsetSavings, + Description: "test offset", + }) + if err := b.Create(); err != nil { + t.Error(err) + } + if len(b.Entries) != 2 { + t.Fatalf("got %d Entries", len(b.Entries)) + } + + off := b.Entries[1] + if off.Amount != b.Entries[0].Amount { + t.Errorf("offset.Amount=%d b.Entries[0].Amount=%d", off.Amount, b.Entries[0].Amount) + } + if off.TransactionCode != SavingsDebit { + t.Errorf("unexpected TransactionCode: %d", off.TransactionCode) + } + + if b.Control.TotalDebitEntryDollarAmount != b.Control.TotalCreditEntryDollarAmount { + t.Errorf("debits=%d credits=%d", b.Control.TotalDebitEntryDollarAmount, b.Control.TotalCreditEntryDollarAmount) + } +} + +func TestBatch__CalculateBalancedOffsetDebitAndCredit(t *testing.T) { + off := &Offset{ + RoutingNumber: "121042882", + AccountNumber: "123456789", + AccountType: OffsetSavings, + Description: "test offset", + } + + // Setup a file and make our only batch a Debit + f := mockFilePPD() + b, ok := f.Batches[0].(*BatchPPD) + if !ok { + t.Fatalf("got %T: %#v", f.Batches[0], f.Batches[0]) + } + b.Header.ServiceClassCode = MixedDebitsAndCredits + b.Entries[0].TransactionCode = SavingsDebit // force one debit tx + b.WithOffset(off) + + // Add another EntryDetail + ed := mockEntryDetail() + ed.Amount = 500 + ed.TraceNumber = strconv.Itoa(lastTraceNumber([]*EntryDetail{ed}) + 1) + b.AddEntry(ed) + + if err := b.Create(); err != nil { + t.Fatal(err) + } + + // Append a second (Credit) Batch + f2 := mockFilePPD() + b, ok = f2.Batches[0].(*BatchPPD) + if !ok { + t.Fatalf("got %T: %#v", f.Batches[0], f.Batches[0]) + } + b.Entries[0].TransactionCode = CheckingCredit // force one credit tx + b.Entries[0].Amount += 1000 + b.Header.ServiceClassCode = MixedDebitsAndCredits + b.WithOffset(off) + if err := b.Create(); err != nil { + t.Fatal(err) + } + f.AddBatch(b) + + // Assemble the larger File + if err := f.Create(); err != nil { + t.Fatal(err) + } + if len(f.Batches) != 2 { + t.Errorf("expected 2 Batches, but got %d", len(f.Batches)) + for i := range f.Batches { + t.Errorf("batch %d/%d: %#v", i+1, len(f.Batches), f.Batches[i]) + entries := f.Batches[i].GetEntries() + for j := range entries { + t.Errorf(" entry %d/%d: %#v", j+1, len(entries), entries[j]) + } + t.Error("") + } + t.Fatal("") + } + + // Check first batch + b = f.Batches[0].(*BatchPPD) + if len(b.Entries) != 4 { + t.Errorf("got %d batches, expected 4", len(b.Entries)) + } + // First EntryDetail with its offset + if b.Entries[0].TransactionCode != SavingsDebit || b.Entries[0].Amount != 100000000 { + t.Errorf("entry=%d TransactionCode=%d Amount=%d", 0, b.Entries[0].TransactionCode, 100000000) + } + if b.Entries[3].TransactionCode != SavingsCredit || b.Entries[3].Amount != 100000000 { + t.Errorf("entry=%d TransactionCode=%d Amount=%d", 3, b.Entries[3].TransactionCode, 100000000) + } + // Second EntryDetail with its offset + if b.Entries[1].TransactionCode != CheckingCredit || b.Entries[1].Amount != 500 { + t.Errorf("entry=%d TransactionCode=%d Amount=%d", 1, b.Entries[1].TransactionCode, 100000000) + } + if b.Entries[2].TransactionCode != SavingsDebit || b.Entries[2].Amount != 500 { + t.Errorf("entry=%d TransactionCode=%d Amount=%d", 2, b.Entries[2].TransactionCode, 100000000) + } + + // Second batch + b = f.Batches[1].(*BatchPPD) + if len(b.Entries) != 2 { + t.Errorf("got %d batches, expected 2", len(b.Entries)) + } + if b.Entries[0].TransactionCode != CheckingCredit || b.Entries[0].Amount != 100001000 { + t.Errorf("entry=%d TransactionCode=%d Amount=%d", 0, b.Entries[0].TransactionCode, 100000000) + } + if b.Entries[1].TransactionCode != SavingsDebit || b.Entries[1].Amount != 100001000 { + t.Errorf("entry=%d TransactionCode=%d Amount=%d", 1, b.Entries[1].TransactionCode, 100000000) + } +} + +func TestBatch__upsertOffsetIdempotent(t *testing.T) { + f := mockFilePPD() + b, ok := f.Batches[0].(*BatchPPD) + if !ok { + t.Fatalf("got %T: %#v", f.Batches[0], f.Batches[0]) + } + + b.Header.ServiceClassCode = MixedDebitsAndCredits + b.Entries[0].TransactionCode = CheckingCredit + if err := f.Create(); err != nil { + t.Fatal(err) + } + // create an offset and then create it again (replace via upsertOffset) + b.WithOffset(&Offset{ + RoutingNumber: "121042882", + AccountNumber: "123456789", + AccountType: OffsetSavings, + Description: "test offset", + }) + if err := b.Create(); err != nil { + t.Error(err) + } + if len(b.Entries) != 2 { + t.Errorf("got %d Entries", len(b.Entries)) + } + + // Call Create / upsertOffsets again and verify idempotence + if err := b.Create(); err != nil { + t.Error(err) + } + if len(b.Entries) != 2 { + t.Errorf("got %d Entries", len(b.Entries)) + } + + // Verify the offset EntryDetail + off := b.Entries[1] + if off.Amount != b.Entries[0].Amount { + t.Errorf("offset.Amount=%d b.Entries[0].Amount=%d", off.Amount, b.Entries[0].Amount) + } + if off.TransactionCode != SavingsDebit { + t.Errorf("unexpected TransactionCode: %d", off.TransactionCode) + } + + if b.Control.TotalDebitEntryDollarAmount != b.Control.TotalCreditEntryDollarAmount { + t.Errorf("debits=%d credits=%d", b.Control.TotalDebitEntryDollarAmount, b.Control.TotalCreditEntryDollarAmount) + } +} + +func TestBatch__upsertOffsetsErr(t *testing.T) { + f := mockFilePPD() + b, ok := f.Batches[0].(*BatchPPD) + if !ok { + t.Fatalf("got %T: %#v", f.Batches[0], f.Batches[0]) + } + + b.Header.ServiceClassCode = MixedDebitsAndCredits + b.Entries[0].TransactionCode = CheckingCredit + if err := f.Create(); err != nil { + t.Fatal(err) + } + // create an offset and then create it again (replace via upsertOffset) + b.WithOffset(&Offset{ + RoutingNumber: "121042882", + AccountNumber: "123456789", + AccountType: OffsetAccountType("invalid"), + Description: "test offset", + }) + if err := b.Create(); err == nil { + t.Error("expected error") + } + + // break the offset routing number + b.offset.RoutingNumber = "1" + if err := f.Batches[0].Create(); err == nil { + t.Error("expected error") + } else if !strings.Contains(err.Error(), "offset: invalid routing number") { + t.Errorf("unexpected error: %v", err) + } +} + +func TestBatch__isTraceNumberODFI(t *testing.T) { + file := mockFilePPD() + batch := file.Batches[0] + + // Set invalid TraceNumber and ensure it's rejected + if b, ok := batch.(*BatchPPD); ok { + b.Entries[0].TraceNumber = "333333333333333" // invalid + + if err := b.isTraceNumberODFI(); err == nil { + t.Error("expected error") + } + } else { + t.Fatalf("%T %#v", batch, batch) + } + if err := file.Validate(); err == nil { + t.Error("expected error") + } + if err := batch.Validate(); err == nil { + t.Error("expected error") + } + + // Set a shorter trace number (0's for routing number) and + // ensure it's rejected as well. + if b, ok := batch.(*BatchPPD); ok { + b.Entries[0].TraceNumber = "3" // invalid + + if err := b.isTraceNumberODFI(); err == nil { + t.Error("expected error") + } + } + + // Allow the failure with an invalid TraceNumber + batch.SetValidation(&ValidateOpts{ + BypassOriginValidation: true, + }) + + if err := batch.Validate(); err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +func TestBatch__CustomTraceNumbers(t *testing.T) { + file := mockFilePPD() + + file.Batches[0].SetValidation(&ValidateOpts{ + BypassOriginValidation: false, + CustomTraceNumbers: false, + }) + + batch, ok := file.Batches[0].(*BatchPPD) + if !ok { + t.Fatalf("unexpected batch: %T", file.Batches[0]) + } + + for i := range batch.Entries { + batch.Entries[i].TraceNumber = "333344445" + } + + if err := batch.build(); err != nil { + t.Fatal(err) + } + + if batch.Entries[0].TraceNumber != "121042880000001" { + t.Errorf("unexpected trace number: %v", batch.Entries[0].TraceNumber) + } +} + +func TestBatch__CompanyIdentificationMismatch(t *testing.T) { + file := mockFilePPD() + batcher := file.Batches[0] + batcher.SetValidation(&ValidateOpts{ + BypassCompanyIdentificationMatch: true, + }) + + bh := batcher.GetHeader() + bh.CompanyIdentification = "121042882" + + bc := batcher.GetControl() + bc.CompanyIdentification = "111111111" + + err := batcher.Validate() + if err != nil { + t.Fatalf("unexpected validation error: %v", err) + } + +} + +func TestBatch_calculateEntryHash(t *testing.T) { + b := &Batch{} + b.SetHeader(mockBatchHeader()) + + ed1 := mockEntryDetail() + ed1.RDFIIdentification = "05600507" + b.AddEntry(ed1) + + ed2 := mockEntryDetail() + ed2.RDFIIdentification = "05140225" + b.AddEntry(ed2) + + ed3 := mockEntryDetail() + ed3.RDFIIdentification = "11400065" + b.AddEntry(ed3) + + hash := b.calculateEntryHash() + require.Equal(t, 22140797, hash) +} diff --git a/batcher.go b/batcher.go index da0042632..abd04cca2 100644 --- a/batcher.go +++ b/batcher.go @@ -1,3 +1,20 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + package ach import ( @@ -8,71 +25,53 @@ import ( // Each batch type is defined by SEC (Standard Entry Class) code in the Batch Header // * SEC identifies the payment type (product) found within an ACH batch-using a 3-character code // * The SEC Code pertains to all items within batch -// * Determines format of the entry detail records -// * Determines addenda records (required or optional PLUS one or up to 9,999 records) -// * Determines rules to follow (return timeframes) -// * Some SEC codes require specific data in predetermined fields within the ACH record +// - Determines format of the entry detail records +// - Determines addenda records (required or optional PLUS one or up to 9,999 records) +// - Determines rules to follow (return time frames) +// - Some SEC codes require specific data in predetermined fields within the ACH record type Batcher interface { GetHeader() *BatchHeader SetHeader(*BatchHeader) GetControl() *BatchControl SetControl(*BatchControl) + GetADVControl() *ADVBatchControl + SetADVControl(*ADVBatchControl) GetEntries() []*EntryDetail AddEntry(*EntryDetail) + GetADVEntries() []*ADVEntryDetail + AddADVEntry(*ADVEntryDetail) Create() error Validate() error + SetID(string) + ID() string + // Category defines if a Forward or Return + Category() string + Error(string, error, ...interface{}) error + Equal(other Batcher) bool + WithOffset(off *Offset) + SetValidation(*ValidateOpts) } -// BatchError is an Error that describes batch validation issues -type BatchError struct { - BatchNumber int - FieldName string - Msg string -} - -func (e *BatchError) Error() string { - return fmt.Sprintf("BatchNumber %d %s %s", e.BatchNumber, e.FieldName, e.Msg) +// Offset contains the associated information to append an 'Offset Record' on an ACH batch during Create. +type Offset struct { + RoutingNumber string `json:"routingNumber"` + AccountNumber string `json:"accountNumber"` + AccountType OffsetAccountType `json:"accountType"` + Description string `json:"description"` } -// BatchParam contains information about the company(Originator) and the type of detail records to follow. -// It is a subset of BatchHeader used for simplifying the client api build process. -type BatchParam struct { - // ServiceClassCode a three digit code identifies: - // - 200 mixed debits and credits - // - 220 credits only - // - 225 debits only - ServiceClassCode string `json:"service_class_code"` - // CompanyName is the legal company name making the transaction. - CompanyName string `json:"company_name"` - // CompanyIdentification is assigned by your bank to identify your company. Frequently the federal tax ID - CompanyIdentification string `json:"company_identification"` - // StandardEntryClass identifies the payment type (product) found within the batch using a 3-character code - StandardEntryClass string `json:"standard_entry_class"` - // CompanyEntryDescription describes the transaction. For example "PAYROLL" - CompanyEntryDescription string `json:"company_entry_description"` - // CompanyDescriptiveDate a date chosen to identify the transactions in YYMMDD format. - CompanyDescriptiveDate string `json:"company_descriptive_date"` - // Date transactions are to be posted to the receivers’ account in YYMMDD format. - EffectiveEntryDate string `json:"effective_entry_date"` - // ODFIIdentification originating ODFI's routing number without the last digit - ODFIIdentification string `json:"ODFI_identification"` -} +type OffsetAccountType string -// Errors specific to parsing a Batch container -var ( - // generic messages - msgBatchHeaderControlEquality = "header %v is not equal to control %v" - msgBatchCalculatedControlEquality = "calculated %v is out-of-balance with control %v" - msgBatchAscending = "%v is less than last %v. Must be in ascending order" - msgBatchFieldInclusion = "%v is a required field " - // specific messages for error - msgBatchOriginatorDNE = "%v is not “2” for DNE with entry transaction code of 23 or 33" - msgBatchTraceNumberNotODFI = "%v in header does not match entry trace number %v" - msgBatchAddendaIndicator = "is 0 but found addenda record(s)" - msgBatchAddendaTraceNumber = "%v does not match proceeding entry detail trace number %v" - msgBatchEntries = "must have Entry Record(s) to be built" - msgBatchAddendaCount = "%v addendum found where %v is allowed for batch type %v" - msgBatchTransactionCodeCredit = "%v a credit is not allowed" - msgBatchSECType = "header SEC type code %v for batch type %v" - msgBatchTypeCode = "%v found in addenda and expecting %v for batch type %v" +const ( + OffsetChecking OffsetAccountType = "checking" + OffsetSavings OffsetAccountType = "savings" ) + +func (t OffsetAccountType) validate() error { + switch t { + case OffsetChecking, OffsetSavings: + return nil + default: + return fmt.Errorf("unknown offset account type: %s", t) + } +} diff --git a/batcher_test.go b/batcher_test.go new file mode 100644 index 000000000..2068b2058 --- /dev/null +++ b/batcher_test.go @@ -0,0 +1,34 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "testing" +) + +func TestOffsetAccountType(t *testing.T) { + if err := OffsetChecking.validate(); err != nil { + t.Fatal(err) + } + if err := OffsetSavings.validate(); err != nil { + t.Fatal(err) + } + if err := OffsetAccountType("invalid").validate(); err == nil { + t.Error("expected error") + } +} diff --git a/cmd/achcli/describe.go b/cmd/achcli/describe.go new file mode 100644 index 000000000..2fb8e8670 --- /dev/null +++ b/cmd/achcli/describe.go @@ -0,0 +1,73 @@ +// Copyright 2020 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "os" + + "github.com/moov-io/ach" + "github.com/moov-io/ach/cmd/achcli/describe" +) + +func dumpFiles(paths []string) error { + var files []*ach.File + for i := range paths { + f, err := readACHFile(paths[i]) + if err != nil { + fmt.Printf("WARN: problem reading %s:\n %v\n\n", paths[i], err) + } + files = append(files, f) + } + + if *flagMerge { + merged, err := ach.MergeFiles(files) + if err != nil { + fmt.Printf("ERROR: merging files: %v\n", err) + } + fmt.Printf("Describing %d file(s) merged into %d file(s)\n", len(paths), len(merged)) + files = merged + } + + if *flagFlatten { + for i := range files { + fmt.Printf("attempting flattening %d\n", i) + file, err := files[i].FlattenBatches() + if err != nil { + fmt.Printf("ERROR: problem flattening file: %v\n", err) + } + files[i] = file + } + } + + for i := range files { + if i > 0 && len(files) > 1 { + fmt.Println("") // extra newline between multiple ACH files + } + if !*flagMerge { + fmt.Printf("Describing ACH file '%s'\n\n", paths[i]) + } + if files[i] != nil { + describe.File(os.Stdout, files[i], &describe.Opts{ + MaskAccountNumbers: *flagMask, + }) + } else { + fmt.Printf("nil ACH file in position %d\n", i) + } + } + + return nil +} + +func readACHFile(path string) (*ach.File, error) { + fd, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("problem opening %s: %v", path, err) + } + defer fd.Close() + + f, err := ach.NewReader(fd).Read() + return &f, err +} diff --git a/cmd/achcli/describe/code_map.go b/cmd/achcli/describe/code_map.go new file mode 100644 index 000000000..ec56e9780 --- /dev/null +++ b/cmd/achcli/describe/code_map.go @@ -0,0 +1,78 @@ +// Copyright 2020 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package describe + +import ( + "fmt" + + "github.com/moov-io/ach" +) + +var serviceClassCodes = map[int]string{ + ach.MixedDebitsAndCredits: "(Mixed Debits and Credits)", + ach.CreditsOnly: "(Credits Only)", + ach.DebitsOnly: "(Debits Only)", + ach.AutomatedAccountingAdvices: "(Automated Accounting Analysis)", +} + +type transactionType string + +const ( + debit transactionType = "Debit" + credit transactionType = "Credit" +) + +var transactionCodes = map[int]string{ + ach.CheckingCredit: entry("Checking", credit), + ach.CheckingReturnNOCCredit: noc("Checking", credit), + ach.CheckingPrenoteCredit: prenote("Checking", credit), + ach.CheckingZeroDollarRemittanceCredit: remittance("Checking", credit), + + ach.CheckingDebit: entry("Checking", debit), + ach.CheckingReturnNOCDebit: noc("Checking", debit), + ach.CheckingPrenoteDebit: prenote("Checking", debit), + ach.CheckingZeroDollarRemittanceDebit: remittance("Checking", debit), + + ach.SavingsCredit: entry("Savings", credit), + ach.SavingsReturnNOCCredit: noc("Savings", credit), + ach.SavingsPrenoteCredit: prenote("Savings", credit), + ach.SavingsZeroDollarRemittanceCredit: remittance("Savings", credit), + + ach.SavingsDebit: entry("Savings", debit), + ach.SavingsReturnNOCDebit: noc("Savings", debit), + ach.SavingsPrenoteDebit: prenote("Savings", debit), + ach.SavingsZeroDollarRemittanceDebit: remittance("Savings", debit), + + ach.GLCredit: entry("GL", credit), + ach.GLReturnNOCCredit: noc("GL", credit), + ach.GLPrenoteCredit: prenote("GL", credit), + ach.GLZeroDollarRemittanceCredit: remittance("Gl", credit), + + ach.GLDebit: entry("GL", debit), + ach.GLReturnNOCDebit: noc("GL", debit), + ach.GLPrenoteDebit: prenote("GL", debit), + ach.GLZeroDollarRemittanceDebit: remittance("Gl", debit), + + ach.LoanCredit: entry("Loan", credit), + ach.LoanReturnNOCCredit: noc("Loan", credit), + ach.LoanPrenoteCredit: prenote("Loan", credit), + ach.LoanZeroDollarRemittanceCredit: remittance("Loan", credit), + + ach.LoanDebit: entry("Loan", debit), + ach.LoanReturnNOCDebit: noc("Loan", debit), +} + +func entry(s string, t transactionType) string { + return fmt.Sprintf("(%s %s)", s, t) +} +func noc(s string, t transactionType) string { + return fmt.Sprintf("(%s Return NOC %s)", s, t) +} +func prenote(s string, t transactionType) string { + return fmt.Sprintf("(%s Prenote %s)", s, t) +} +func remittance(s string, t transactionType) string { + return fmt.Sprintf("(%s Zero Dollar Remittance %s)", s, t) +} diff --git a/cmd/achcli/describe/file.go b/cmd/achcli/describe/file.go new file mode 100644 index 000000000..d4ddf00df --- /dev/null +++ b/cmd/achcli/describe/file.go @@ -0,0 +1,171 @@ +// Copyright 2020 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package describe + +import ( + "fmt" + "io" + "strings" + "text/tabwriter" + "unicode/utf8" + + "github.com/moov-io/ach" +) + +type Opts struct { + MaskNames bool + MaskAccountNumbers bool +} + +func File(ww io.Writer, file *ach.File, opts *Opts) { + if file == nil { + return + } + if opts == nil { + opts = &Opts{} + } + + w := tabwriter.NewWriter(ww, 0, 0, 2, ' ', 0) + defer w.Flush() + + fh, fc := file.Header, file.Control + + // FileHeader + fmt.Fprintln(w, " Origin\tOriginName\tDestination\tDestinationName\tFileCreationDate\tFileCreationTime") + fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%s\t%s\n", fh.ImmediateOrigin, fh.ImmediateOriginName, fh.ImmediateDestination, fh.ImmediateDestinationName, fh.FileCreationDate, fh.FileCreationTime) + + // Batches + for i := range file.Batches { + fmt.Fprintln(w, "\n BatchNumber\tSECCode\tServiceClassCode\tCompanyName\tDiscretionaryData\tIdentification\tEntryDescription\tEffectiveEntryDate\tDescriptiveDate") + + bh := file.Batches[i].GetHeader() + if bh != nil { + fmt.Fprintf(w, " %d\t%s\t%d %s\t%s\t%s\t%s\t%s\t%s\t%s\n", + bh.BatchNumber, + bh.StandardEntryClassCode, + bh.ServiceClassCode, + serviceClassCodes[bh.ServiceClassCode], + bh.CompanyName, + bh.CompanyDiscretionaryData, + bh.CompanyIdentification, + bh.CompanyEntryDescription, + bh.EffectiveEntryDate, + bh.CompanyDescriptiveDate, + ) + } + + entries := file.Batches[i].GetEntries() + for j := range entries { + fmt.Fprintln(w, "\n TransactionCode\tRDFIIdentification\tAccountNumber\tAmount\tName\tTraceNumber\tCategory") + + e := entries[j] + accountNumber := e.DFIAccountNumber + if opts.MaskAccountNumbers { + accountNumber = maskAccountNumber(strings.TrimSpace(accountNumber)) + } + + fmt.Fprintf(w, " %d %s\t%s\t%s\t%d\t%s\t%s\t%s\n", e.TransactionCode, transactionCodes[e.TransactionCode], e.RDFIIdentification, accountNumber, e.Amount, e.IndividualName, e.TraceNumber, e.Category) + + dumpAddenda02(w, e.Addenda02) + for i := range e.Addenda05 { + if i == 0 { + fmt.Fprintln(w, "\n Addenda05") + } + dumpAddenda05(w, e.Addenda05[i]) + } + dumpAddenda98(w, e.Addenda98) + dumpAddenda99(w, e.Addenda99) + dumpAddenda99Dishonored(w, e.Addenda99Dishonored) + dumpAddenda99Contested(w, e.Addenda99Contested) + } + + bc := file.Batches[i].GetControl() + if bc != nil { + fmt.Fprintln(w, "\n ServiceClassCode\tEntryAddendaCount\tEntryHash\tTotalDebits\tTotalCredits\tMACCode\tODFIIdentification\tBatchNumber") + fmt.Fprintf(w, " %d %s\t%d\t%d\t%d\t%d\t%s\t%s\t%d\n", bc.ServiceClassCode, serviceClassCodes[bh.ServiceClassCode], bc.EntryAddendaCount, bc.EntryHash, bc.TotalDebitEntryDollarAmount, bc.TotalCreditEntryDollarAmount, bc.MessageAuthenticationCode, bc.ODFIIdentification, bc.BatchNumber) + } + } + + // FileControl + fmt.Fprintln(w, "\n BatchCount\tBlockCount\tEntryAddendaCount\tTotalDebitAmount\tTotalCreditAmount") + fmt.Fprintf(w, " %d\t%d\t%d\t%d\t%d\n", fc.BatchCount, fc.BlockCount, fc.EntryAddendaCount, fc.TotalDebitEntryDollarAmountInFile, fc.TotalCreditEntryDollarAmountInFile) +} + +func dumpAddenda02(w *tabwriter.Writer, a *ach.Addenda02) { + if a == nil { + return + } + + fmt.Fprintln(w, "\n Addenda02") + fmt.Fprintln(w, " ReferenceInfoOne\tReferenceInfoTwo\tTerminalIdentification\tTransactionSerial\tDate\tAuthCodeOrExires\tLocation\tCity\tState\tTraceNumber") + fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", + a.ReferenceInformationOne, a.ReferenceInformationTwo, a.TerminalIdentificationCode, a.TransactionSerialNumber, + a.TransactionDate, a.AuthorizationCodeOrExpireDate, a.TerminalLocation, a.TerminalCity, a.TerminalState, a.TraceNumber) +} + +func dumpAddenda99Dishonored(w *tabwriter.Writer, a *ach.Addenda99Dishonored) { + if a == nil { + return + } + + fmt.Fprintln(w, "\n Dishonored Addenda99") + fmt.Fprintln(w, " Dis. ReturnCode\tOrig. TraceNumber\tRDFI Identification\tTraceNumber\tSettlementDate\tReturnCode\tAddendaInformation\tTraceNumber") + fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", + a.DishonoredReturnReasonCode, a.OriginalEntryTraceNumber, a.OriginalReceivingDFIIdentification, a.ReturnTraceNumber, + a.ReturnSettlementDate, a.ReturnReasonCode, a.AddendaInformation, a.TraceNumber) +} + +func dumpAddenda99Contested(w *tabwriter.Writer, a *ach.Addenda99Contested) { + if a == nil { + return + } + + fmt.Fprintln(w, "\n Contested Dishonored Addenda99") + fmt.Fprintln(w, " ContestedReturnCode\tOrig. TraceNumber\tOrig Date Returned\tOrig. RDFIIdentification\tOrig. SettlementDate\tReturnTraceNumber") + fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%s\t%s\n", + a.ContestedReturnCode, a.OriginalEntryTraceNumber, a.DateOriginalEntryReturned, a.OriginalReceivingDFIIdentification, + a.OriginalSettlementDate, a.ReturnTraceNumber) + + fmt.Fprintln(w, " ReturnSettlementDate\tReturnReasonCode\tDishonoredTraceNumber\tDishonoredSettlementDate\tDishonoredReasonCode\tTraceNumber") + fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%s\t%s\n", + a.ReturnSettlementDate, a.ReturnReasonCode, a.DishonoredReturnTraceNumber, a.DishonoredReturnSettlementDate, a.DishonoredReturnReasonCode, a.TraceNumber) +} + +func dumpAddenda05(w *tabwriter.Writer, a *ach.Addenda05) { + if a == nil { + return + } + + fmt.Fprintln(w, " PaymentRelatedInformation\tSequenceNumber\tEntryDetailSequenceNumber") + fmt.Fprintf(w, " %s\t%d\t%d\n", a.PaymentRelatedInformation, a.SequenceNumber, a.EntryDetailSequenceNumber) +} + +func dumpAddenda98(w *tabwriter.Writer, a *ach.Addenda98) { + if a == nil { + return + } + + fmt.Fprintln(w, "\n Addenda98") + fmt.Fprintln(w, " ChangeCode\tOriginalTrace\tOriginalDFI\tCorrectedData\tTraceNumber") + fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%s\n", a.ChangeCode, a.OriginalTrace, a.OriginalDFI, a.CorrectedData, a.TraceNumber) +} + +func dumpAddenda99(w *tabwriter.Writer, a *ach.Addenda99) { + if a == nil { + return + } + + fmt.Fprintln(w, "\n Addenda99") + fmt.Fprintln(w, " ReturnCode\tOriginalTrace\tDateOfDeath\tOriginalDFI\tAddendaInformation\tTraceNumber") + fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%s\t%s\n", a.ReturnCode, a.OriginalTrace, a.DateOfDeath, a.OriginalDFI, a.AddendaInformation, a.TraceNumber) +} + +func maskAccountNumber(s string) string { + length := utf8.RuneCountInString(s) + if length < 5 { + return "****" // too short, we can't keep anything + } + return strings.Repeat("*", length-4) + s[length-4:] +} diff --git a/cmd/achcli/diff.go b/cmd/achcli/diff.go new file mode 100644 index 000000000..56e3304c4 --- /dev/null +++ b/cmd/achcli/diff.go @@ -0,0 +1,115 @@ +// Copyright 2019 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "fmt" + "os" + // "text/tabwriter" + + "github.com/moov-io/ach" + + "github.com/juju/ansiterm" + "github.com/mattn/go-isatty" +) + +func diffFiles(paths []string) error { + if len(paths) != 2 { + return fmt.Errorf("expected 2 files, but got %d", len(paths)) + } + f1, f2, err := readTwoFiles(paths) + if err != nil { + return err + } + + // w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + // defer w.Flush() + + w := ansiterm.NewTabWriter(os.Stdout, 0, 0, 1, ' ', 0) + w = w.Init(os.Stdout, 0, 0, 1, ' ', 0) + defer w.Flush() + + switch { + case f1.Header.ImmediateOrigin != f2.Header.ImmediateOrigin: + printDiffedFileHeader(w, f1, f2) + case f1.Header.ImmediateOriginName != f2.Header.ImmediateOriginName: + printDiffedFileHeader(w, f1, f2) + case f1.Header.ImmediateDestination != f2.Header.ImmediateDestination: + printDiffedFileHeader(w, f1, f2) + case f1.Header.ImmediateDestinationName != f2.Header.ImmediateDestinationName: + printDiffedFileHeader(w, f1, f2) + } + + return nil +} + +func readTwoFiles(paths []string) (*ach.File, *ach.File, error) { + f1, err := readACHFile(paths[0]) + if err != nil { + return nil, nil, fmt.Errorf("problem reading %s: %v", paths[0], err) + } + f2, err := readACHFile(paths[1]) + if err != nil { + return nil, nil, fmt.Errorf("problem reading %s: %v", paths[1], err) + } + return f1, f2, nil +} + +// TODO(adam): we should lookup batches which are in f1 against those in f2 and if a similar +// batch is found (TODO: what, if any fields can we do exact matches on?) print that in the +// order of f1. Otherwise show missing batches (from f1's view) and additional batches (from +// f2's view) at the end. + +func printDiffedFileHeader(w *ansiterm.TabWriter, f1, f2 *ach.File) { + fmt.Fprintln(w, " Origin\tOriginName\tDestination\tDestinationName") + + var minusBuf bytes.Buffer + var plusBuf bytes.Buffer + + minusBuf.WriteString("- ") + plusBuf.WriteString("+ ") + + printColumn(&minusBuf, &plusBuf, f1.Header.ImmediateOrigin, f2.Header.ImmediateOrigin) + printColumn(&minusBuf, &plusBuf, f1.Header.ImmediateOriginName, f2.Header.ImmediateOriginName) + printColumn(&minusBuf, &plusBuf, f1.Header.ImmediateDestination, f2.Header.ImmediateDestination) + printColumn(&minusBuf, &plusBuf, f1.Header.ImmediateDestinationName, f2.Header.ImmediateDestinationName) + + fmt.Fprintln(w, minusBuf.String()) + fmt.Fprintln(w, plusBuf.String()) + + // fmt.Fprintln(w, fmt.Sprintf("- %s\t%s\t%s\t%s\t", f2.Header.ImmediateOrigin, f2.Header.ImmediateOriginName, f2.Header.ImmediateDestination, f2.Header.ImmediateDestinationName)) + // fmt.Fprintln(w, fmt.Sprintf("+ %s\t%s\t%s\t%s\t", f1.Header.ImmediateOrigin, f1.Header.ImmediateOriginName, f1.Header.ImmediateDestination, f1.Header.ImmediateDestinationName)) +} + +func printColumn(minusBuf, plusBuf *bytes.Buffer, v1, v2 string) { + if v1 != v2 { + w := ansiterm.NewWriter(minusBuf) + ctx := ansiterm.Foreground(ansiterm.Green) + ctx.Fprintf(w, v2) + ctx.SetForeground(ansiterm.Default) + ctx.Fprintf(w, "\t") + + fmt.Printf("%T %#v\n", w, w) + fmt.Printf("%T: %v\n", os.Stdout, isatty.IsTerminal(os.Stdout.Fd())) + + w = ansiterm.NewWriter(plusBuf) + ctx = ansiterm.Foreground(ansiterm.Red) + ctx.Fprintf(w, v1) + ctx.SetForeground(ansiterm.Default) + ctx.Fprintf(w, "\t") + + // minusBuf.WriteString( + + // ansiterm.Fprint(minusBuf, fmt.Sprintf("[red]%s[reset]\t", v2)) + // ansiterm.Fprint(plusBuf, fmt.Sprintf("[green]%s[reset]\t", v1)) + + // ansiterm.Write([]byte(fmt.Sprintf("[red]%s[reset]\t", v2))) + // ansiterm.Write([]byte(fmt.Sprintf("[green]%s[reset]\t", v1))) + } else { + minusBuf.WriteString(fmt.Sprintf("%s\t", v2)) + plusBuf.WriteString(fmt.Sprintf("%s\t", v1)) + } +} diff --git a/cmd/achcli/main.go b/cmd/achcli/main.go new file mode 100644 index 000000000..39313f665 --- /dev/null +++ b/cmd/achcli/main.go @@ -0,0 +1,96 @@ +// Copyright 2019 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package main + +import ( + "flag" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/moov-io/ach" +) + +var ( + flagVerbose = flag.Bool("v", false, "Print verbose details about each ACH file") + flagVersion = flag.Bool("version", false, "Print moov-io/ach cli version") + + flagDiff = flag.Bool("diff", false, "Compare two files against each other") + flagFlatten = flag.Bool("flatten", false, "Flatten batches in each file") + flagMerge = flag.Bool("merge", false, "Merge files before describing") + flagReformat = flag.String("reformat", "", "Reformat an incoming ACH file to another format") + + flagMask = flag.Bool("mask", false, "Mask/hide full account numbers") + + programName = filepath.Base(os.Args[0]) +) + +func init() { + flag.Usage = func() { + fmt.Printf("Usage of ach (%s):\n", ach.Version) + fmt.Printf(" usage: %s [] ", programName) + fmt.Println("") + fmt.Println("Commands: ") + fmt.Printf(" %s -diff first.ach second.ach", programName) + fmt.Println(" Show the difference between two ACH files") + fmt.Printf(" %s -mask file.ach", programName) + fmt.Println(" Print file details with personally identifiable information partially removed") + fmt.Printf(" %s -reformat=json first.ach", programName) + fmt.Println(" Convert an incoming ACH file into another format (options: ach, json)") + fmt.Printf(" %s 20060102.ach", programName) + fmt.Println(" Summarize an ACH file for human readability") + fmt.Println("") + fmt.Println("Flags: ") + flag.PrintDefaults() + } +} + +func main() { + flag.Parse() + + switch { + case *flagVersion: + fmt.Printf("moov-io/ach:%s cli tool\n", ach.Version) + return + case *flagVerbose: + fmt.Printf("moov-io/ach:%s cli tool\n", ach.Version) + } + + args := flag.Args() + + // error conditions, verify we're okay for whatever the task at hand is + switch { + case *flagDiff && len(args) != 2: + fmt.Printf("with -diff exactly two files are expected, found %d files\n", len(args)) + os.Exit(1) + } + + // minor debugging + if *flagVerbose { + fmt.Printf("found %d ACH files to describe: %s\n", len(args), strings.Join(args, ", ")) + } + + // pick our command to do + switch { + case *flagDiff: + if err := diffFiles(args); err != nil { + fmt.Printf("ERROR: %v\n", err) + os.Exit(1) + } + + case *flagReformat != "" && len(args) == 1: + if err := reformat(*flagReformat, args[0]); err != nil { + fmt.Printf("ERROR: %v\n", err) + os.Exit(1) + } + + default: + if err := dumpFiles(args); err != nil { + fmt.Printf("ERROR: %v\n", err) + os.Exit(1) + } + } +} diff --git a/cmd/achcli/reformat.go b/cmd/achcli/reformat.go new file mode 100644 index 000000000..28a276571 --- /dev/null +++ b/cmd/achcli/reformat.go @@ -0,0 +1,69 @@ +// Copyright 2019 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package main + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "github.com/moov-io/ach" +) + +func reformat(as string, filepath string) error { + if _, err := os.Stat(filepath); err != nil { + return err + } + + file, err := readIncomingFile(filepath) + if err != nil { + return err + } + + switch as { + case "ach": + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + return err + } + + case "json": + if err := json.NewEncoder(os.Stdout).Encode(file); err != nil { + return err + } + + default: + return fmt.Errorf("unknown format %s", as) + } + return nil +} + +func readIncomingFile(path string) (*ach.File, error) { + file, err := readJsonFile(path) + if file != nil && err == nil { + return file, nil + } + file, err = readACHFile(path) + if file != nil && err == nil { + return file, nil + } + return nil, fmt.Errorf("unable to read %s:\n %v", path, err) +} + +func readJsonFile(path string) (*ach.File, error) { + fd, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("problem opening %s: %v", path, err) + } + defer fd.Close() + + bs, err := io.ReadAll(fd) + if err != nil { + return nil, fmt.Errorf("problem reading %s: %v", path, err) + } + + return ach.FileFromJSON(bs) +} diff --git a/cmd/readACH/201805101354.ach b/cmd/readACH/201805101354.ach new file mode 100644 index 000000000..f4cfb70e7 --- /dev/null +++ b/cmd/readACH/201805101354.ach @@ -0,0 +1,10010 @@ +101 231380104 1210428821902280000A094101Citadel Wells Fargo +5200Wells Fargo 121042882 PPDTrans. Des 190301 1121042880000001 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000001 +705bonus pay for amazing work on #OSS 00010000001 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000002 +705bonus pay for amazing work on #OSS 00010000002 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000003 +705bonus pay for amazing work on #OSS 00010000003 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000004 +705bonus pay for amazing work on #OSS 00010000004 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000005 +705bonus pay for amazing work on #OSS 00010000005 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000006 +705bonus pay for amazing work on #OSS 00010000006 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000007 +705bonus pay for amazing work on #OSS 00010000007 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000008 +705bonus pay for amazing work on #OSS 00010000008 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000009 +705bonus pay for amazing work on #OSS 00010000009 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000010 +705bonus pay for amazing work on #OSS 00010000010 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000011 +705bonus pay for amazing work on #OSS 00010000011 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000012 +705bonus pay for amazing work on #OSS 00010000012 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000013 +705bonus pay for amazing work on #OSS 00010000013 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000014 +705bonus pay for amazing work on #OSS 00010000014 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000015 +705bonus pay for amazing work on #OSS 00010000015 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000016 +705bonus pay for amazing work on #OSS 00010000016 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000017 +705bonus pay for amazing work on #OSS 00010000017 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000018 +705bonus pay for amazing work on #OSS 00010000018 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000019 +705bonus pay for amazing work on #OSS 00010000019 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000020 +705bonus pay for amazing work on #OSS 00010000020 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000021 +705bonus pay for amazing work on #OSS 00010000021 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000022 +705bonus pay for amazing work on #OSS 00010000022 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000023 +705bonus pay for amazing work on #OSS 00010000023 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000024 +705bonus pay for amazing work on #OSS 00010000024 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000025 +705bonus pay for amazing work on #OSS 00010000025 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000026 +705bonus pay for amazing work on #OSS 00010000026 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000027 +705bonus pay for amazing work on #OSS 00010000027 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000028 +705bonus pay for amazing work on #OSS 00010000028 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000029 +705bonus pay for amazing work on #OSS 00010000029 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000030 +705bonus pay for amazing work on #OSS 00010000030 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000031 +705bonus pay for amazing work on #OSS 00010000031 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000032 +705bonus pay for amazing work on #OSS 00010000032 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000033 +705bonus pay for amazing work on #OSS 00010000033 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000034 +705bonus pay for amazing work on #OSS 00010000034 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000035 +705bonus pay for amazing work on #OSS 00010000035 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000036 +705bonus pay for amazing work on #OSS 00010000036 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000037 +705bonus pay for amazing work on #OSS 00010000037 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000038 +705bonus pay for amazing work on #OSS 00010000038 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000039 +705bonus pay for amazing work on #OSS 00010000039 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000040 +705bonus pay for amazing work on #OSS 00010000040 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000041 +705bonus pay for amazing work on #OSS 00010000041 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000042 +705bonus pay for amazing work on #OSS 00010000042 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000043 +705bonus pay for amazing work on #OSS 00010000043 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000044 +705bonus pay for amazing work on #OSS 00010000044 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000045 +705bonus pay for amazing work on #OSS 00010000045 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000046 +705bonus pay for amazing work on #OSS 00010000046 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000047 +705bonus pay for amazing work on #OSS 00010000047 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000048 +705bonus pay for amazing work on #OSS 00010000048 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000049 +705bonus pay for amazing work on #OSS 00010000049 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000050 +705bonus pay for amazing work on #OSS 00010000050 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000051 +705bonus pay for amazing work on #OSS 00010000051 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000052 +705bonus pay for amazing work on #OSS 00010000052 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000053 +705bonus pay for amazing work on #OSS 00010000053 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000054 +705bonus pay for amazing work on #OSS 00010000054 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000055 +705bonus pay for amazing work on #OSS 00010000055 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000056 +705bonus pay for amazing work on #OSS 00010000056 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000057 +705bonus pay for amazing work on #OSS 00010000057 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000058 +705bonus pay for amazing work on #OSS 00010000058 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000059 +705bonus pay for amazing work on #OSS 00010000059 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000060 +705bonus pay for amazing work on #OSS 00010000060 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000061 +705bonus pay for amazing work on #OSS 00010000061 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000062 +705bonus pay for amazing work on #OSS 00010000062 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000063 +705bonus pay for amazing work on #OSS 00010000063 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000064 +705bonus pay for amazing work on #OSS 00010000064 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000065 +705bonus pay for amazing work on #OSS 00010000065 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000066 +705bonus pay for amazing work on #OSS 00010000066 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000067 +705bonus pay for amazing work on #OSS 00010000067 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000068 +705bonus pay for amazing work on #OSS 00010000068 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000069 +705bonus pay for amazing work on #OSS 00010000069 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000070 +705bonus pay for amazing work on #OSS 00010000070 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000071 +705bonus pay for amazing work on #OSS 00010000071 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000072 +705bonus pay for amazing work on #OSS 00010000072 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000073 +705bonus pay for amazing work on #OSS 00010000073 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000074 +705bonus pay for amazing work on #OSS 00010000074 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000075 +705bonus pay for amazing work on #OSS 00010000075 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000076 +705bonus pay for amazing work on #OSS 00010000076 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000077 +705bonus pay for amazing work on #OSS 00010000077 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000078 +705bonus pay for amazing work on #OSS 00010000078 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000079 +705bonus pay for amazing work on #OSS 00010000079 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000080 +705bonus pay for amazing work on #OSS 00010000080 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000081 +705bonus pay for amazing work on #OSS 00010000081 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000082 +705bonus pay for amazing work on #OSS 00010000082 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000083 +705bonus pay for amazing work on #OSS 00010000083 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000084 +705bonus pay for amazing work on #OSS 00010000084 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000085 +705bonus pay for amazing work on #OSS 00010000085 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000086 +705bonus pay for amazing work on #OSS 00010000086 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000087 +705bonus pay for amazing work on #OSS 00010000087 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000088 +705bonus pay for amazing work on #OSS 00010000088 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000089 +705bonus pay for amazing work on #OSS 00010000089 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000090 +705bonus pay for amazing work on #OSS 00010000090 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000091 +705bonus pay for amazing work on #OSS 00010000091 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000092 +705bonus pay for amazing work on #OSS 00010000092 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000093 +705bonus pay for amazing work on #OSS 00010000093 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000094 +705bonus pay for amazing work on #OSS 00010000094 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000095 +705bonus pay for amazing work on #OSS 00010000095 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000096 +705bonus pay for amazing work on #OSS 00010000096 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000097 +705bonus pay for amazing work on #OSS 00010000097 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000098 +705bonus pay for amazing work on #OSS 00010000098 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000099 +705bonus pay for amazing work on #OSS 00010000099 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000100 +705bonus pay for amazing work on #OSS 00010000100 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000101 +705bonus pay for amazing work on #OSS 00010000101 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000102 +705bonus pay for amazing work on #OSS 00010000102 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000103 +705bonus pay for amazing work on #OSS 00010000103 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000104 +705bonus pay for amazing work on #OSS 00010000104 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000105 +705bonus pay for amazing work on #OSS 00010000105 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000106 +705bonus pay for amazing work on #OSS 00010000106 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000107 +705bonus pay for amazing work on #OSS 00010000107 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000108 +705bonus pay for amazing work on #OSS 00010000108 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000109 +705bonus pay for amazing work on #OSS 00010000109 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000110 +705bonus pay for amazing work on #OSS 00010000110 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000111 +705bonus pay for amazing work on #OSS 00010000111 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000112 +705bonus pay for amazing work on #OSS 00010000112 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000113 +705bonus pay for amazing work on #OSS 00010000113 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000114 +705bonus pay for amazing work on #OSS 00010000114 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000115 +705bonus pay for amazing work on #OSS 00010000115 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000116 +705bonus pay for amazing work on #OSS 00010000116 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000117 +705bonus pay for amazing work on #OSS 00010000117 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000118 +705bonus pay for amazing work on #OSS 00010000118 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000119 +705bonus pay for amazing work on #OSS 00010000119 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000120 +705bonus pay for amazing work on #OSS 00010000120 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000121 +705bonus pay for amazing work on #OSS 00010000121 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000122 +705bonus pay for amazing work on #OSS 00010000122 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000123 +705bonus pay for amazing work on #OSS 00010000123 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000124 +705bonus pay for amazing work on #OSS 00010000124 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000125 +705bonus pay for amazing work on #OSS 00010000125 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000126 +705bonus pay for amazing work on #OSS 00010000126 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000127 +705bonus pay for amazing work on #OSS 00010000127 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000128 +705bonus pay for amazing work on #OSS 00010000128 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000129 +705bonus pay for amazing work on #OSS 00010000129 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000130 +705bonus pay for amazing work on #OSS 00010000130 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000131 +705bonus pay for amazing work on #OSS 00010000131 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000132 +705bonus pay for amazing work on #OSS 00010000132 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000133 +705bonus pay for amazing work on #OSS 00010000133 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000134 +705bonus pay for amazing work on #OSS 00010000134 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000135 +705bonus pay for amazing work on #OSS 00010000135 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000136 +705bonus pay for amazing work on #OSS 00010000136 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000137 +705bonus pay for amazing work on #OSS 00010000137 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000138 +705bonus pay for amazing work on #OSS 00010000138 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000139 +705bonus pay for amazing work on #OSS 00010000139 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000140 +705bonus pay for amazing work on #OSS 00010000140 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000141 +705bonus pay for amazing work on #OSS 00010000141 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000142 +705bonus pay for amazing work on #OSS 00010000142 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000143 +705bonus pay for amazing work on #OSS 00010000143 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000144 +705bonus pay for amazing work on #OSS 00010000144 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000145 +705bonus pay for amazing work on #OSS 00010000145 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000146 +705bonus pay for amazing work on #OSS 00010000146 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000147 +705bonus pay for amazing work on #OSS 00010000147 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000148 +705bonus pay for amazing work on #OSS 00010000148 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000149 +705bonus pay for amazing work on #OSS 00010000149 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000150 +705bonus pay for amazing work on #OSS 00010000150 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000151 +705bonus pay for amazing work on #OSS 00010000151 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000152 +705bonus pay for amazing work on #OSS 00010000152 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000153 +705bonus pay for amazing work on #OSS 00010000153 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000154 +705bonus pay for amazing work on #OSS 00010000154 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000155 +705bonus pay for amazing work on #OSS 00010000155 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000156 +705bonus pay for amazing work on #OSS 00010000156 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000157 +705bonus pay for amazing work on #OSS 00010000157 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000158 +705bonus pay for amazing work on #OSS 00010000158 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000159 +705bonus pay for amazing work on #OSS 00010000159 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000160 +705bonus pay for amazing work on #OSS 00010000160 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000161 +705bonus pay for amazing work on #OSS 00010000161 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000162 +705bonus pay for amazing work on #OSS 00010000162 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000163 +705bonus pay for amazing work on #OSS 00010000163 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000164 +705bonus pay for amazing work on #OSS 00010000164 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000165 +705bonus pay for amazing work on #OSS 00010000165 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000166 +705bonus pay for amazing work on #OSS 00010000166 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000167 +705bonus pay for amazing work on #OSS 00010000167 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000168 +705bonus pay for amazing work on #OSS 00010000168 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000169 +705bonus pay for amazing work on #OSS 00010000169 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000170 +705bonus pay for amazing work on #OSS 00010000170 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000171 +705bonus pay for amazing work on #OSS 00010000171 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000172 +705bonus pay for amazing work on #OSS 00010000172 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000173 +705bonus pay for amazing work on #OSS 00010000173 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000174 +705bonus pay for amazing work on #OSS 00010000174 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000175 +705bonus pay for amazing work on #OSS 00010000175 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000176 +705bonus pay for amazing work on #OSS 00010000176 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000177 +705bonus pay for amazing work on #OSS 00010000177 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000178 +705bonus pay for amazing work on #OSS 00010000178 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000179 +705bonus pay for amazing work on #OSS 00010000179 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000180 +705bonus pay for amazing work on #OSS 00010000180 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000181 +705bonus pay for amazing work on #OSS 00010000181 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000182 +705bonus pay for amazing work on #OSS 00010000182 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000183 +705bonus pay for amazing work on #OSS 00010000183 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000184 +705bonus pay for amazing work on #OSS 00010000184 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000185 +705bonus pay for amazing work on #OSS 00010000185 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000186 +705bonus pay for amazing work on #OSS 00010000186 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000187 +705bonus pay for amazing work on #OSS 00010000187 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000188 +705bonus pay for amazing work on #OSS 00010000188 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000189 +705bonus pay for amazing work on #OSS 00010000189 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000190 +705bonus pay for amazing work on #OSS 00010000190 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000191 +705bonus pay for amazing work on #OSS 00010000191 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000192 +705bonus pay for amazing work on #OSS 00010000192 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000193 +705bonus pay for amazing work on #OSS 00010000193 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000194 +705bonus pay for amazing work on #OSS 00010000194 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000195 +705bonus pay for amazing work on #OSS 00010000195 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000196 +705bonus pay for amazing work on #OSS 00010000196 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000197 +705bonus pay for amazing work on #OSS 00010000197 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000198 +705bonus pay for amazing work on #OSS 00010000198 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000199 +705bonus pay for amazing work on #OSS 00010000199 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000200 +705bonus pay for amazing work on #OSS 00010000200 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000201 +705bonus pay for amazing work on #OSS 00010000201 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000202 +705bonus pay for amazing work on #OSS 00010000202 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000203 +705bonus pay for amazing work on #OSS 00010000203 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000204 +705bonus pay for amazing work on #OSS 00010000204 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000205 +705bonus pay for amazing work on #OSS 00010000205 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000206 +705bonus pay for amazing work on #OSS 00010000206 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000207 +705bonus pay for amazing work on #OSS 00010000207 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000208 +705bonus pay for amazing work on #OSS 00010000208 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000209 +705bonus pay for amazing work on #OSS 00010000209 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000210 +705bonus pay for amazing work on #OSS 00010000210 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000211 +705bonus pay for amazing work on #OSS 00010000211 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000212 +705bonus pay for amazing work on #OSS 00010000212 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000213 +705bonus pay for amazing work on #OSS 00010000213 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000214 +705bonus pay for amazing work on #OSS 00010000214 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000215 +705bonus pay for amazing work on #OSS 00010000215 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000216 +705bonus pay for amazing work on #OSS 00010000216 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000217 +705bonus pay for amazing work on #OSS 00010000217 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000218 +705bonus pay for amazing work on #OSS 00010000218 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000219 +705bonus pay for amazing work on #OSS 00010000219 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000220 +705bonus pay for amazing work on #OSS 00010000220 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000221 +705bonus pay for amazing work on #OSS 00010000221 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000222 +705bonus pay for amazing work on #OSS 00010000222 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000223 +705bonus pay for amazing work on #OSS 00010000223 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000224 +705bonus pay for amazing work on #OSS 00010000224 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000225 +705bonus pay for amazing work on #OSS 00010000225 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000226 +705bonus pay for amazing work on #OSS 00010000226 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000227 +705bonus pay for amazing work on #OSS 00010000227 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000228 +705bonus pay for amazing work on #OSS 00010000228 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000229 +705bonus pay for amazing work on #OSS 00010000229 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000230 +705bonus pay for amazing work on #OSS 00010000230 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000231 +705bonus pay for amazing work on #OSS 00010000231 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000232 +705bonus pay for amazing work on #OSS 00010000232 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000233 +705bonus pay for amazing work on #OSS 00010000233 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000234 +705bonus pay for amazing work on #OSS 00010000234 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000235 +705bonus pay for amazing work on #OSS 00010000235 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000236 +705bonus pay for amazing work on #OSS 00010000236 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000237 +705bonus pay for amazing work on #OSS 00010000237 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000238 +705bonus pay for amazing work on #OSS 00010000238 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000239 +705bonus pay for amazing work on #OSS 00010000239 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000240 +705bonus pay for amazing work on #OSS 00010000240 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000241 +705bonus pay for amazing work on #OSS 00010000241 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000242 +705bonus pay for amazing work on #OSS 00010000242 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000243 +705bonus pay for amazing work on #OSS 00010000243 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000244 +705bonus pay for amazing work on #OSS 00010000244 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000245 +705bonus pay for amazing work on #OSS 00010000245 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000246 +705bonus pay for amazing work on #OSS 00010000246 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000247 +705bonus pay for amazing work on #OSS 00010000247 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000248 +705bonus pay for amazing work on #OSS 00010000248 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000249 +705bonus pay for amazing work on #OSS 00010000249 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000250 +705bonus pay for amazing work on #OSS 00010000250 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000251 +705bonus pay for amazing work on #OSS 00010000251 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000252 +705bonus pay for amazing work on #OSS 00010000252 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000253 +705bonus pay for amazing work on #OSS 00010000253 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000254 +705bonus pay for amazing work on #OSS 00010000254 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000255 +705bonus pay for amazing work on #OSS 00010000255 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000256 +705bonus pay for amazing work on #OSS 00010000256 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000257 +705bonus pay for amazing work on #OSS 00010000257 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000258 +705bonus pay for amazing work on #OSS 00010000258 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000259 +705bonus pay for amazing work on #OSS 00010000259 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000260 +705bonus pay for amazing work on #OSS 00010000260 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000261 +705bonus pay for amazing work on #OSS 00010000261 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000262 +705bonus pay for amazing work on #OSS 00010000262 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000263 +705bonus pay for amazing work on #OSS 00010000263 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000264 +705bonus pay for amazing work on #OSS 00010000264 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000265 +705bonus pay for amazing work on #OSS 00010000265 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000266 +705bonus pay for amazing work on #OSS 00010000266 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000267 +705bonus pay for amazing work on #OSS 00010000267 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000268 +705bonus pay for amazing work on #OSS 00010000268 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000269 +705bonus pay for amazing work on #OSS 00010000269 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000270 +705bonus pay for amazing work on #OSS 00010000270 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000271 +705bonus pay for amazing work on #OSS 00010000271 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000272 +705bonus pay for amazing work on #OSS 00010000272 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000273 +705bonus pay for amazing work on #OSS 00010000273 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000274 +705bonus pay for amazing work on #OSS 00010000274 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000275 +705bonus pay for amazing work on #OSS 00010000275 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000276 +705bonus pay for amazing work on #OSS 00010000276 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000277 +705bonus pay for amazing work on #OSS 00010000277 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000278 +705bonus pay for amazing work on #OSS 00010000278 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000279 +705bonus pay for amazing work on #OSS 00010000279 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000280 +705bonus pay for amazing work on #OSS 00010000280 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000281 +705bonus pay for amazing work on #OSS 00010000281 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000282 +705bonus pay for amazing work on #OSS 00010000282 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000283 +705bonus pay for amazing work on #OSS 00010000283 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000284 +705bonus pay for amazing work on #OSS 00010000284 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000285 +705bonus pay for amazing work on #OSS 00010000285 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000286 +705bonus pay for amazing work on #OSS 00010000286 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000287 +705bonus pay for amazing work on #OSS 00010000287 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000288 +705bonus pay for amazing work on #OSS 00010000288 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000289 +705bonus pay for amazing work on #OSS 00010000289 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000290 +705bonus pay for amazing work on #OSS 00010000290 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000291 +705bonus pay for amazing work on #OSS 00010000291 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000292 +705bonus pay for amazing work on #OSS 00010000292 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000293 +705bonus pay for amazing work on #OSS 00010000293 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000294 +705bonus pay for amazing work on #OSS 00010000294 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000295 +705bonus pay for amazing work on #OSS 00010000295 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000296 +705bonus pay for amazing work on #OSS 00010000296 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000297 +705bonus pay for amazing work on #OSS 00010000297 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000298 +705bonus pay for amazing work on #OSS 00010000298 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000299 +705bonus pay for amazing work on #OSS 00010000299 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000300 +705bonus pay for amazing work on #OSS 00010000300 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000301 +705bonus pay for amazing work on #OSS 00010000301 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000302 +705bonus pay for amazing work on #OSS 00010000302 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000303 +705bonus pay for amazing work on #OSS 00010000303 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000304 +705bonus pay for amazing work on #OSS 00010000304 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000305 +705bonus pay for amazing work on #OSS 00010000305 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000306 +705bonus pay for amazing work on #OSS 00010000306 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000307 +705bonus pay for amazing work on #OSS 00010000307 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000308 +705bonus pay for amazing work on #OSS 00010000308 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000309 +705bonus pay for amazing work on #OSS 00010000309 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000310 +705bonus pay for amazing work on #OSS 00010000310 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000311 +705bonus pay for amazing work on #OSS 00010000311 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000312 +705bonus pay for amazing work on #OSS 00010000312 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000313 +705bonus pay for amazing work on #OSS 00010000313 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000314 +705bonus pay for amazing work on #OSS 00010000314 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000315 +705bonus pay for amazing work on #OSS 00010000315 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000316 +705bonus pay for amazing work on #OSS 00010000316 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000317 +705bonus pay for amazing work on #OSS 00010000317 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000318 +705bonus pay for amazing work on #OSS 00010000318 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000319 +705bonus pay for amazing work on #OSS 00010000319 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000320 +705bonus pay for amazing work on #OSS 00010000320 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000321 +705bonus pay for amazing work on #OSS 00010000321 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000322 +705bonus pay for amazing work on #OSS 00010000322 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000323 +705bonus pay for amazing work on #OSS 00010000323 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000324 +705bonus pay for amazing work on #OSS 00010000324 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000325 +705bonus pay for amazing work on #OSS 00010000325 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000326 +705bonus pay for amazing work on #OSS 00010000326 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000327 +705bonus pay for amazing work on #OSS 00010000327 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000328 +705bonus pay for amazing work on #OSS 00010000328 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000329 +705bonus pay for amazing work on #OSS 00010000329 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000330 +705bonus pay for amazing work on #OSS 00010000330 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000331 +705bonus pay for amazing work on #OSS 00010000331 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000332 +705bonus pay for amazing work on #OSS 00010000332 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000333 +705bonus pay for amazing work on #OSS 00010000333 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000334 +705bonus pay for amazing work on #OSS 00010000334 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000335 +705bonus pay for amazing work on #OSS 00010000335 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000336 +705bonus pay for amazing work on #OSS 00010000336 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000337 +705bonus pay for amazing work on #OSS 00010000337 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000338 +705bonus pay for amazing work on #OSS 00010000338 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000339 +705bonus pay for amazing work on #OSS 00010000339 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000340 +705bonus pay for amazing work on #OSS 00010000340 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000341 +705bonus pay for amazing work on #OSS 00010000341 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000342 +705bonus pay for amazing work on #OSS 00010000342 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000343 +705bonus pay for amazing work on #OSS 00010000343 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000344 +705bonus pay for amazing work on #OSS 00010000344 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000345 +705bonus pay for amazing work on #OSS 00010000345 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000346 +705bonus pay for amazing work on #OSS 00010000346 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000347 +705bonus pay for amazing work on #OSS 00010000347 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000348 +705bonus pay for amazing work on #OSS 00010000348 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000349 +705bonus pay for amazing work on #OSS 00010000349 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000350 +705bonus pay for amazing work on #OSS 00010000350 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000351 +705bonus pay for amazing work on #OSS 00010000351 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000352 +705bonus pay for amazing work on #OSS 00010000352 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000353 +705bonus pay for amazing work on #OSS 00010000353 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000354 +705bonus pay for amazing work on #OSS 00010000354 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000355 +705bonus pay for amazing work on #OSS 00010000355 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000356 +705bonus pay for amazing work on #OSS 00010000356 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000357 +705bonus pay for amazing work on #OSS 00010000357 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000358 +705bonus pay for amazing work on #OSS 00010000358 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000359 +705bonus pay for amazing work on #OSS 00010000359 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000360 +705bonus pay for amazing work on #OSS 00010000360 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000361 +705bonus pay for amazing work on #OSS 00010000361 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000362 +705bonus pay for amazing work on #OSS 00010000362 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000363 +705bonus pay for amazing work on #OSS 00010000363 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000364 +705bonus pay for amazing work on #OSS 00010000364 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000365 +705bonus pay for amazing work on #OSS 00010000365 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000366 +705bonus pay for amazing work on #OSS 00010000366 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000367 +705bonus pay for amazing work on #OSS 00010000367 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000368 +705bonus pay for amazing work on #OSS 00010000368 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000369 +705bonus pay for amazing work on #OSS 00010000369 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000370 +705bonus pay for amazing work on #OSS 00010000370 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000371 +705bonus pay for amazing work on #OSS 00010000371 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000372 +705bonus pay for amazing work on #OSS 00010000372 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000373 +705bonus pay for amazing work on #OSS 00010000373 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000374 +705bonus pay for amazing work on #OSS 00010000374 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000375 +705bonus pay for amazing work on #OSS 00010000375 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000376 +705bonus pay for amazing work on #OSS 00010000376 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000377 +705bonus pay for amazing work on #OSS 00010000377 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000378 +705bonus pay for amazing work on #OSS 00010000378 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000379 +705bonus pay for amazing work on #OSS 00010000379 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000380 +705bonus pay for amazing work on #OSS 00010000380 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000381 +705bonus pay for amazing work on #OSS 00010000381 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000382 +705bonus pay for amazing work on #OSS 00010000382 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000383 +705bonus pay for amazing work on #OSS 00010000383 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000384 +705bonus pay for amazing work on #OSS 00010000384 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000385 +705bonus pay for amazing work on #OSS 00010000385 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000386 +705bonus pay for amazing work on #OSS 00010000386 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000387 +705bonus pay for amazing work on #OSS 00010000387 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000388 +705bonus pay for amazing work on #OSS 00010000388 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000389 +705bonus pay for amazing work on #OSS 00010000389 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000390 +705bonus pay for amazing work on #OSS 00010000390 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000391 +705bonus pay for amazing work on #OSS 00010000391 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000392 +705bonus pay for amazing work on #OSS 00010000392 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000393 +705bonus pay for amazing work on #OSS 00010000393 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000394 +705bonus pay for amazing work on #OSS 00010000394 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000395 +705bonus pay for amazing work on #OSS 00010000395 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000396 +705bonus pay for amazing work on #OSS 00010000396 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000397 +705bonus pay for amazing work on #OSS 00010000397 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000398 +705bonus pay for amazing work on #OSS 00010000398 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000399 +705bonus pay for amazing work on #OSS 00010000399 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000400 +705bonus pay for amazing work on #OSS 00010000400 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000401 +705bonus pay for amazing work on #OSS 00010000401 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000402 +705bonus pay for amazing work on #OSS 00010000402 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000403 +705bonus pay for amazing work on #OSS 00010000403 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000404 +705bonus pay for amazing work on #OSS 00010000404 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000405 +705bonus pay for amazing work on #OSS 00010000405 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000406 +705bonus pay for amazing work on #OSS 00010000406 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000407 +705bonus pay for amazing work on #OSS 00010000407 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000408 +705bonus pay for amazing work on #OSS 00010000408 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000409 +705bonus pay for amazing work on #OSS 00010000409 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000410 +705bonus pay for amazing work on #OSS 00010000410 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000411 +705bonus pay for amazing work on #OSS 00010000411 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000412 +705bonus pay for amazing work on #OSS 00010000412 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000413 +705bonus pay for amazing work on #OSS 00010000413 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000414 +705bonus pay for amazing work on #OSS 00010000414 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000415 +705bonus pay for amazing work on #OSS 00010000415 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000416 +705bonus pay for amazing work on #OSS 00010000416 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000417 +705bonus pay for amazing work on #OSS 00010000417 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000418 +705bonus pay for amazing work on #OSS 00010000418 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000419 +705bonus pay for amazing work on #OSS 00010000419 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000420 +705bonus pay for amazing work on #OSS 00010000420 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000421 +705bonus pay for amazing work on #OSS 00010000421 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000422 +705bonus pay for amazing work on #OSS 00010000422 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000423 +705bonus pay for amazing work on #OSS 00010000423 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000424 +705bonus pay for amazing work on #OSS 00010000424 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000425 +705bonus pay for amazing work on #OSS 00010000425 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000426 +705bonus pay for amazing work on #OSS 00010000426 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000427 +705bonus pay for amazing work on #OSS 00010000427 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000428 +705bonus pay for amazing work on #OSS 00010000428 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000429 +705bonus pay for amazing work on #OSS 00010000429 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000430 +705bonus pay for amazing work on #OSS 00010000430 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000431 +705bonus pay for amazing work on #OSS 00010000431 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000432 +705bonus pay for amazing work on #OSS 00010000432 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000433 +705bonus pay for amazing work on #OSS 00010000433 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000434 +705bonus pay for amazing work on #OSS 00010000434 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000435 +705bonus pay for amazing work on #OSS 00010000435 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000436 +705bonus pay for amazing work on #OSS 00010000436 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000437 +705bonus pay for amazing work on #OSS 00010000437 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000438 +705bonus pay for amazing work on #OSS 00010000438 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000439 +705bonus pay for amazing work on #OSS 00010000439 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000440 +705bonus pay for amazing work on #OSS 00010000440 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000441 +705bonus pay for amazing work on #OSS 00010000441 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000442 +705bonus pay for amazing work on #OSS 00010000442 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000443 +705bonus pay for amazing work on #OSS 00010000443 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000444 +705bonus pay for amazing work on #OSS 00010000444 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000445 +705bonus pay for amazing work on #OSS 00010000445 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000446 +705bonus pay for amazing work on #OSS 00010000446 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000447 +705bonus pay for amazing work on #OSS 00010000447 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000448 +705bonus pay for amazing work on #OSS 00010000448 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000449 +705bonus pay for amazing work on #OSS 00010000449 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000450 +705bonus pay for amazing work on #OSS 00010000450 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000451 +705bonus pay for amazing work on #OSS 00010000451 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000452 +705bonus pay for amazing work on #OSS 00010000452 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000453 +705bonus pay for amazing work on #OSS 00010000453 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000454 +705bonus pay for amazing work on #OSS 00010000454 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000455 +705bonus pay for amazing work on #OSS 00010000455 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000456 +705bonus pay for amazing work on #OSS 00010000456 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000457 +705bonus pay for amazing work on #OSS 00010000457 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000458 +705bonus pay for amazing work on #OSS 00010000458 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000459 +705bonus pay for amazing work on #OSS 00010000459 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000460 +705bonus pay for amazing work on #OSS 00010000460 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000461 +705bonus pay for amazing work on #OSS 00010000461 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000462 +705bonus pay for amazing work on #OSS 00010000462 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000463 +705bonus pay for amazing work on #OSS 00010000463 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000464 +705bonus pay for amazing work on #OSS 00010000464 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000465 +705bonus pay for amazing work on #OSS 00010000465 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000466 +705bonus pay for amazing work on #OSS 00010000466 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000467 +705bonus pay for amazing work on #OSS 00010000467 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000468 +705bonus pay for amazing work on #OSS 00010000468 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000469 +705bonus pay for amazing work on #OSS 00010000469 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000470 +705bonus pay for amazing work on #OSS 00010000470 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000471 +705bonus pay for amazing work on #OSS 00010000471 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000472 +705bonus pay for amazing work on #OSS 00010000472 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000473 +705bonus pay for amazing work on #OSS 00010000473 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000474 +705bonus pay for amazing work on #OSS 00010000474 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000475 +705bonus pay for amazing work on #OSS 00010000475 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000476 +705bonus pay for amazing work on #OSS 00010000476 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000477 +705bonus pay for amazing work on #OSS 00010000477 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000478 +705bonus pay for amazing work on #OSS 00010000478 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000479 +705bonus pay for amazing work on #OSS 00010000479 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000480 +705bonus pay for amazing work on #OSS 00010000480 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000481 +705bonus pay for amazing work on #OSS 00010000481 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000482 +705bonus pay for amazing work on #OSS 00010000482 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000483 +705bonus pay for amazing work on #OSS 00010000483 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000484 +705bonus pay for amazing work on #OSS 00010000484 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000485 +705bonus pay for amazing work on #OSS 00010000485 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000486 +705bonus pay for amazing work on #OSS 00010000486 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000487 +705bonus pay for amazing work on #OSS 00010000487 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000488 +705bonus pay for amazing work on #OSS 00010000488 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000489 +705bonus pay for amazing work on #OSS 00010000489 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000490 +705bonus pay for amazing work on #OSS 00010000490 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000491 +705bonus pay for amazing work on #OSS 00010000491 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000492 +705bonus pay for amazing work on #OSS 00010000492 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000493 +705bonus pay for amazing work on #OSS 00010000493 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000494 +705bonus pay for amazing work on #OSS 00010000494 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000495 +705bonus pay for amazing work on #OSS 00010000495 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000496 +705bonus pay for amazing work on #OSS 00010000496 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000497 +705bonus pay for amazing work on #OSS 00010000497 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000498 +705bonus pay for amazing work on #OSS 00010000498 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000499 +705bonus pay for amazing work on #OSS 00010000499 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000500 +705bonus pay for amazing work on #OSS 00010000500 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000501 +705bonus pay for amazing work on #OSS 00010000501 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000502 +705bonus pay for amazing work on #OSS 00010000502 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000503 +705bonus pay for amazing work on #OSS 00010000503 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000504 +705bonus pay for amazing work on #OSS 00010000504 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000505 +705bonus pay for amazing work on #OSS 00010000505 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000506 +705bonus pay for amazing work on #OSS 00010000506 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000507 +705bonus pay for amazing work on #OSS 00010000507 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000508 +705bonus pay for amazing work on #OSS 00010000508 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000509 +705bonus pay for amazing work on #OSS 00010000509 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000510 +705bonus pay for amazing work on #OSS 00010000510 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000511 +705bonus pay for amazing work on #OSS 00010000511 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000512 +705bonus pay for amazing work on #OSS 00010000512 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000513 +705bonus pay for amazing work on #OSS 00010000513 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000514 +705bonus pay for amazing work on #OSS 00010000514 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000515 +705bonus pay for amazing work on #OSS 00010000515 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000516 +705bonus pay for amazing work on #OSS 00010000516 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000517 +705bonus pay for amazing work on #OSS 00010000517 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000518 +705bonus pay for amazing work on #OSS 00010000518 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000519 +705bonus pay for amazing work on #OSS 00010000519 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000520 +705bonus pay for amazing work on #OSS 00010000520 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000521 +705bonus pay for amazing work on #OSS 00010000521 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000522 +705bonus pay for amazing work on #OSS 00010000522 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000523 +705bonus pay for amazing work on #OSS 00010000523 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000524 +705bonus pay for amazing work on #OSS 00010000524 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000525 +705bonus pay for amazing work on #OSS 00010000525 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000526 +705bonus pay for amazing work on #OSS 00010000526 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000527 +705bonus pay for amazing work on #OSS 00010000527 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000528 +705bonus pay for amazing work on #OSS 00010000528 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000529 +705bonus pay for amazing work on #OSS 00010000529 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000530 +705bonus pay for amazing work on #OSS 00010000530 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000531 +705bonus pay for amazing work on #OSS 00010000531 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000532 +705bonus pay for amazing work on #OSS 00010000532 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000533 +705bonus pay for amazing work on #OSS 00010000533 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000534 +705bonus pay for amazing work on #OSS 00010000534 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000535 +705bonus pay for amazing work on #OSS 00010000535 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000536 +705bonus pay for amazing work on #OSS 00010000536 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000537 +705bonus pay for amazing work on #OSS 00010000537 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000538 +705bonus pay for amazing work on #OSS 00010000538 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000539 +705bonus pay for amazing work on #OSS 00010000539 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000540 +705bonus pay for amazing work on #OSS 00010000540 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000541 +705bonus pay for amazing work on #OSS 00010000541 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000542 +705bonus pay for amazing work on #OSS 00010000542 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000543 +705bonus pay for amazing work on #OSS 00010000543 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000544 +705bonus pay for amazing work on #OSS 00010000544 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000545 +705bonus pay for amazing work on #OSS 00010000545 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000546 +705bonus pay for amazing work on #OSS 00010000546 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000547 +705bonus pay for amazing work on #OSS 00010000547 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000548 +705bonus pay for amazing work on #OSS 00010000548 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000549 +705bonus pay for amazing work on #OSS 00010000549 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000550 +705bonus pay for amazing work on #OSS 00010000550 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000551 +705bonus pay for amazing work on #OSS 00010000551 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000552 +705bonus pay for amazing work on #OSS 00010000552 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000553 +705bonus pay for amazing work on #OSS 00010000553 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000554 +705bonus pay for amazing work on #OSS 00010000554 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000555 +705bonus pay for amazing work on #OSS 00010000555 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000556 +705bonus pay for amazing work on #OSS 00010000556 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000557 +705bonus pay for amazing work on #OSS 00010000557 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000558 +705bonus pay for amazing work on #OSS 00010000558 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000559 +705bonus pay for amazing work on #OSS 00010000559 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000560 +705bonus pay for amazing work on #OSS 00010000560 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000561 +705bonus pay for amazing work on #OSS 00010000561 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000562 +705bonus pay for amazing work on #OSS 00010000562 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000563 +705bonus pay for amazing work on #OSS 00010000563 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000564 +705bonus pay for amazing work on #OSS 00010000564 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000565 +705bonus pay for amazing work on #OSS 00010000565 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000566 +705bonus pay for amazing work on #OSS 00010000566 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000567 +705bonus pay for amazing work on #OSS 00010000567 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000568 +705bonus pay for amazing work on #OSS 00010000568 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000569 +705bonus pay for amazing work on #OSS 00010000569 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000570 +705bonus pay for amazing work on #OSS 00010000570 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000571 +705bonus pay for amazing work on #OSS 00010000571 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000572 +705bonus pay for amazing work on #OSS 00010000572 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000573 +705bonus pay for amazing work on #OSS 00010000573 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000574 +705bonus pay for amazing work on #OSS 00010000574 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000575 +705bonus pay for amazing work on #OSS 00010000575 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000576 +705bonus pay for amazing work on #OSS 00010000576 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000577 +705bonus pay for amazing work on #OSS 00010000577 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000578 +705bonus pay for amazing work on #OSS 00010000578 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000579 +705bonus pay for amazing work on #OSS 00010000579 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000580 +705bonus pay for amazing work on #OSS 00010000580 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000581 +705bonus pay for amazing work on #OSS 00010000581 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000582 +705bonus pay for amazing work on #OSS 00010000582 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000583 +705bonus pay for amazing work on #OSS 00010000583 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000584 +705bonus pay for amazing work on #OSS 00010000584 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000585 +705bonus pay for amazing work on #OSS 00010000585 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000586 +705bonus pay for amazing work on #OSS 00010000586 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000587 +705bonus pay for amazing work on #OSS 00010000587 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000588 +705bonus pay for amazing work on #OSS 00010000588 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000589 +705bonus pay for amazing work on #OSS 00010000589 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000590 +705bonus pay for amazing work on #OSS 00010000590 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000591 +705bonus pay for amazing work on #OSS 00010000591 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000592 +705bonus pay for amazing work on #OSS 00010000592 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000593 +705bonus pay for amazing work on #OSS 00010000593 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000594 +705bonus pay for amazing work on #OSS 00010000594 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000595 +705bonus pay for amazing work on #OSS 00010000595 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000596 +705bonus pay for amazing work on #OSS 00010000596 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000597 +705bonus pay for amazing work on #OSS 00010000597 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000598 +705bonus pay for amazing work on #OSS 00010000598 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000599 +705bonus pay for amazing work on #OSS 00010000599 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000600 +705bonus pay for amazing work on #OSS 00010000600 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000601 +705bonus pay for amazing work on #OSS 00010000601 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000602 +705bonus pay for amazing work on #OSS 00010000602 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000603 +705bonus pay for amazing work on #OSS 00010000603 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000604 +705bonus pay for amazing work on #OSS 00010000604 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000605 +705bonus pay for amazing work on #OSS 00010000605 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000606 +705bonus pay for amazing work on #OSS 00010000606 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000607 +705bonus pay for amazing work on #OSS 00010000607 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000608 +705bonus pay for amazing work on #OSS 00010000608 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000609 +705bonus pay for amazing work on #OSS 00010000609 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000610 +705bonus pay for amazing work on #OSS 00010000610 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000611 +705bonus pay for amazing work on #OSS 00010000611 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000612 +705bonus pay for amazing work on #OSS 00010000612 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000613 +705bonus pay for amazing work on #OSS 00010000613 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000614 +705bonus pay for amazing work on #OSS 00010000614 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000615 +705bonus pay for amazing work on #OSS 00010000615 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000616 +705bonus pay for amazing work on #OSS 00010000616 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000617 +705bonus pay for amazing work on #OSS 00010000617 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000618 +705bonus pay for amazing work on #OSS 00010000618 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000619 +705bonus pay for amazing work on #OSS 00010000619 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000620 +705bonus pay for amazing work on #OSS 00010000620 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000621 +705bonus pay for amazing work on #OSS 00010000621 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000622 +705bonus pay for amazing work on #OSS 00010000622 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000623 +705bonus pay for amazing work on #OSS 00010000623 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000624 +705bonus pay for amazing work on #OSS 00010000624 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000625 +705bonus pay for amazing work on #OSS 00010000625 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000626 +705bonus pay for amazing work on #OSS 00010000626 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000627 +705bonus pay for amazing work on #OSS 00010000627 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000628 +705bonus pay for amazing work on #OSS 00010000628 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000629 +705bonus pay for amazing work on #OSS 00010000629 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000630 +705bonus pay for amazing work on #OSS 00010000630 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000631 +705bonus pay for amazing work on #OSS 00010000631 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000632 +705bonus pay for amazing work on #OSS 00010000632 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000633 +705bonus pay for amazing work on #OSS 00010000633 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000634 +705bonus pay for amazing work on #OSS 00010000634 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000635 +705bonus pay for amazing work on #OSS 00010000635 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000636 +705bonus pay for amazing work on #OSS 00010000636 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000637 +705bonus pay for amazing work on #OSS 00010000637 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000638 +705bonus pay for amazing work on #OSS 00010000638 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000639 +705bonus pay for amazing work on #OSS 00010000639 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000640 +705bonus pay for amazing work on #OSS 00010000640 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000641 +705bonus pay for amazing work on #OSS 00010000641 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000642 +705bonus pay for amazing work on #OSS 00010000642 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000643 +705bonus pay for amazing work on #OSS 00010000643 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000644 +705bonus pay for amazing work on #OSS 00010000644 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000645 +705bonus pay for amazing work on #OSS 00010000645 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000646 +705bonus pay for amazing work on #OSS 00010000646 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000647 +705bonus pay for amazing work on #OSS 00010000647 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000648 +705bonus pay for amazing work on #OSS 00010000648 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000649 +705bonus pay for amazing work on #OSS 00010000649 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000650 +705bonus pay for amazing work on #OSS 00010000650 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000651 +705bonus pay for amazing work on #OSS 00010000651 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000652 +705bonus pay for amazing work on #OSS 00010000652 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000653 +705bonus pay for amazing work on #OSS 00010000653 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000654 +705bonus pay for amazing work on #OSS 00010000654 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000655 +705bonus pay for amazing work on #OSS 00010000655 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000656 +705bonus pay for amazing work on #OSS 00010000656 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000657 +705bonus pay for amazing work on #OSS 00010000657 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000658 +705bonus pay for amazing work on #OSS 00010000658 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000659 +705bonus pay for amazing work on #OSS 00010000659 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000660 +705bonus pay for amazing work on #OSS 00010000660 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000661 +705bonus pay for amazing work on #OSS 00010000661 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000662 +705bonus pay for amazing work on #OSS 00010000662 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000663 +705bonus pay for amazing work on #OSS 00010000663 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000664 +705bonus pay for amazing work on #OSS 00010000664 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000665 +705bonus pay for amazing work on #OSS 00010000665 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000666 +705bonus pay for amazing work on #OSS 00010000666 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000667 +705bonus pay for amazing work on #OSS 00010000667 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000668 +705bonus pay for amazing work on #OSS 00010000668 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000669 +705bonus pay for amazing work on #OSS 00010000669 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000670 +705bonus pay for amazing work on #OSS 00010000670 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000671 +705bonus pay for amazing work on #OSS 00010000671 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000672 +705bonus pay for amazing work on #OSS 00010000672 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000673 +705bonus pay for amazing work on #OSS 00010000673 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000674 +705bonus pay for amazing work on #OSS 00010000674 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000675 +705bonus pay for amazing work on #OSS 00010000675 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000676 +705bonus pay for amazing work on #OSS 00010000676 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000677 +705bonus pay for amazing work on #OSS 00010000677 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000678 +705bonus pay for amazing work on #OSS 00010000678 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000679 +705bonus pay for amazing work on #OSS 00010000679 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000680 +705bonus pay for amazing work on #OSS 00010000680 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000681 +705bonus pay for amazing work on #OSS 00010000681 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000682 +705bonus pay for amazing work on #OSS 00010000682 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000683 +705bonus pay for amazing work on #OSS 00010000683 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000684 +705bonus pay for amazing work on #OSS 00010000684 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000685 +705bonus pay for amazing work on #OSS 00010000685 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000686 +705bonus pay for amazing work on #OSS 00010000686 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000687 +705bonus pay for amazing work on #OSS 00010000687 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000688 +705bonus pay for amazing work on #OSS 00010000688 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000689 +705bonus pay for amazing work on #OSS 00010000689 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000690 +705bonus pay for amazing work on #OSS 00010000690 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000691 +705bonus pay for amazing work on #OSS 00010000691 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000692 +705bonus pay for amazing work on #OSS 00010000692 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000693 +705bonus pay for amazing work on #OSS 00010000693 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000694 +705bonus pay for amazing work on #OSS 00010000694 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000695 +705bonus pay for amazing work on #OSS 00010000695 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000696 +705bonus pay for amazing work on #OSS 00010000696 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000697 +705bonus pay for amazing work on #OSS 00010000697 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000698 +705bonus pay for amazing work on #OSS 00010000698 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000699 +705bonus pay for amazing work on #OSS 00010000699 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000700 +705bonus pay for amazing work on #OSS 00010000700 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000701 +705bonus pay for amazing work on #OSS 00010000701 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000702 +705bonus pay for amazing work on #OSS 00010000702 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000703 +705bonus pay for amazing work on #OSS 00010000703 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000704 +705bonus pay for amazing work on #OSS 00010000704 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000705 +705bonus pay for amazing work on #OSS 00010000705 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000706 +705bonus pay for amazing work on #OSS 00010000706 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000707 +705bonus pay for amazing work on #OSS 00010000707 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000708 +705bonus pay for amazing work on #OSS 00010000708 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000709 +705bonus pay for amazing work on #OSS 00010000709 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000710 +705bonus pay for amazing work on #OSS 00010000710 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000711 +705bonus pay for amazing work on #OSS 00010000711 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000712 +705bonus pay for amazing work on #OSS 00010000712 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000713 +705bonus pay for amazing work on #OSS 00010000713 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000714 +705bonus pay for amazing work on #OSS 00010000714 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000715 +705bonus pay for amazing work on #OSS 00010000715 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000716 +705bonus pay for amazing work on #OSS 00010000716 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000717 +705bonus pay for amazing work on #OSS 00010000717 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000718 +705bonus pay for amazing work on #OSS 00010000718 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000719 +705bonus pay for amazing work on #OSS 00010000719 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000720 +705bonus pay for amazing work on #OSS 00010000720 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000721 +705bonus pay for amazing work on #OSS 00010000721 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000722 +705bonus pay for amazing work on #OSS 00010000722 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000723 +705bonus pay for amazing work on #OSS 00010000723 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000724 +705bonus pay for amazing work on #OSS 00010000724 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000725 +705bonus pay for amazing work on #OSS 00010000725 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000726 +705bonus pay for amazing work on #OSS 00010000726 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000727 +705bonus pay for amazing work on #OSS 00010000727 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000728 +705bonus pay for amazing work on #OSS 00010000728 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000729 +705bonus pay for amazing work on #OSS 00010000729 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000730 +705bonus pay for amazing work on #OSS 00010000730 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000731 +705bonus pay for amazing work on #OSS 00010000731 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000732 +705bonus pay for amazing work on #OSS 00010000732 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000733 +705bonus pay for amazing work on #OSS 00010000733 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000734 +705bonus pay for amazing work on #OSS 00010000734 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000735 +705bonus pay for amazing work on #OSS 00010000735 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000736 +705bonus pay for amazing work on #OSS 00010000736 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000737 +705bonus pay for amazing work on #OSS 00010000737 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000738 +705bonus pay for amazing work on #OSS 00010000738 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000739 +705bonus pay for amazing work on #OSS 00010000739 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000740 +705bonus pay for amazing work on #OSS 00010000740 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000741 +705bonus pay for amazing work on #OSS 00010000741 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000742 +705bonus pay for amazing work on #OSS 00010000742 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000743 +705bonus pay for amazing work on #OSS 00010000743 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000744 +705bonus pay for amazing work on #OSS 00010000744 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000745 +705bonus pay for amazing work on #OSS 00010000745 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000746 +705bonus pay for amazing work on #OSS 00010000746 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000747 +705bonus pay for amazing work on #OSS 00010000747 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000748 +705bonus pay for amazing work on #OSS 00010000748 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000749 +705bonus pay for amazing work on #OSS 00010000749 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000750 +705bonus pay for amazing work on #OSS 00010000750 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000751 +705bonus pay for amazing work on #OSS 00010000751 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000752 +705bonus pay for amazing work on #OSS 00010000752 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000753 +705bonus pay for amazing work on #OSS 00010000753 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000754 +705bonus pay for amazing work on #OSS 00010000754 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000755 +705bonus pay for amazing work on #OSS 00010000755 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000756 +705bonus pay for amazing work on #OSS 00010000756 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000757 +705bonus pay for amazing work on #OSS 00010000757 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000758 +705bonus pay for amazing work on #OSS 00010000758 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000759 +705bonus pay for amazing work on #OSS 00010000759 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000760 +705bonus pay for amazing work on #OSS 00010000760 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000761 +705bonus pay for amazing work on #OSS 00010000761 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000762 +705bonus pay for amazing work on #OSS 00010000762 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000763 +705bonus pay for amazing work on #OSS 00010000763 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000764 +705bonus pay for amazing work on #OSS 00010000764 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000765 +705bonus pay for amazing work on #OSS 00010000765 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000766 +705bonus pay for amazing work on #OSS 00010000766 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000767 +705bonus pay for amazing work on #OSS 00010000767 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000768 +705bonus pay for amazing work on #OSS 00010000768 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000769 +705bonus pay for amazing work on #OSS 00010000769 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000770 +705bonus pay for amazing work on #OSS 00010000770 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000771 +705bonus pay for amazing work on #OSS 00010000771 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000772 +705bonus pay for amazing work on #OSS 00010000772 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000773 +705bonus pay for amazing work on #OSS 00010000773 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000774 +705bonus pay for amazing work on #OSS 00010000774 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000775 +705bonus pay for amazing work on #OSS 00010000775 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000776 +705bonus pay for amazing work on #OSS 00010000776 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000777 +705bonus pay for amazing work on #OSS 00010000777 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000778 +705bonus pay for amazing work on #OSS 00010000778 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000779 +705bonus pay for amazing work on #OSS 00010000779 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000780 +705bonus pay for amazing work on #OSS 00010000780 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000781 +705bonus pay for amazing work on #OSS 00010000781 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000782 +705bonus pay for amazing work on #OSS 00010000782 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000783 +705bonus pay for amazing work on #OSS 00010000783 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000784 +705bonus pay for amazing work on #OSS 00010000784 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000785 +705bonus pay for amazing work on #OSS 00010000785 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000786 +705bonus pay for amazing work on #OSS 00010000786 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000787 +705bonus pay for amazing work on #OSS 00010000787 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000788 +705bonus pay for amazing work on #OSS 00010000788 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000789 +705bonus pay for amazing work on #OSS 00010000789 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000790 +705bonus pay for amazing work on #OSS 00010000790 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000791 +705bonus pay for amazing work on #OSS 00010000791 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000792 +705bonus pay for amazing work on #OSS 00010000792 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000793 +705bonus pay for amazing work on #OSS 00010000793 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000794 +705bonus pay for amazing work on #OSS 00010000794 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000795 +705bonus pay for amazing work on #OSS 00010000795 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000796 +705bonus pay for amazing work on #OSS 00010000796 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000797 +705bonus pay for amazing work on #OSS 00010000797 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000798 +705bonus pay for amazing work on #OSS 00010000798 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000799 +705bonus pay for amazing work on #OSS 00010000799 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000800 +705bonus pay for amazing work on #OSS 00010000800 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000801 +705bonus pay for amazing work on #OSS 00010000801 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000802 +705bonus pay for amazing work on #OSS 00010000802 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000803 +705bonus pay for amazing work on #OSS 00010000803 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000804 +705bonus pay for amazing work on #OSS 00010000804 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000805 +705bonus pay for amazing work on #OSS 00010000805 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000806 +705bonus pay for amazing work on #OSS 00010000806 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000807 +705bonus pay for amazing work on #OSS 00010000807 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000808 +705bonus pay for amazing work on #OSS 00010000808 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000809 +705bonus pay for amazing work on #OSS 00010000809 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000810 +705bonus pay for amazing work on #OSS 00010000810 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000811 +705bonus pay for amazing work on #OSS 00010000811 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000812 +705bonus pay for amazing work on #OSS 00010000812 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000813 +705bonus pay for amazing work on #OSS 00010000813 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000814 +705bonus pay for amazing work on #OSS 00010000814 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000815 +705bonus pay for amazing work on #OSS 00010000815 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000816 +705bonus pay for amazing work on #OSS 00010000816 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000817 +705bonus pay for amazing work on #OSS 00010000817 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000818 +705bonus pay for amazing work on #OSS 00010000818 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000819 +705bonus pay for amazing work on #OSS 00010000819 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000820 +705bonus pay for amazing work on #OSS 00010000820 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000821 +705bonus pay for amazing work on #OSS 00010000821 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000822 +705bonus pay for amazing work on #OSS 00010000822 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000823 +705bonus pay for amazing work on #OSS 00010000823 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000824 +705bonus pay for amazing work on #OSS 00010000824 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000825 +705bonus pay for amazing work on #OSS 00010000825 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000826 +705bonus pay for amazing work on #OSS 00010000826 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000827 +705bonus pay for amazing work on #OSS 00010000827 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000828 +705bonus pay for amazing work on #OSS 00010000828 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000829 +705bonus pay for amazing work on #OSS 00010000829 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000830 +705bonus pay for amazing work on #OSS 00010000830 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000831 +705bonus pay for amazing work on #OSS 00010000831 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000832 +705bonus pay for amazing work on #OSS 00010000832 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000833 +705bonus pay for amazing work on #OSS 00010000833 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000834 +705bonus pay for amazing work on #OSS 00010000834 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000835 +705bonus pay for amazing work on #OSS 00010000835 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000836 +705bonus pay for amazing work on #OSS 00010000836 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000837 +705bonus pay for amazing work on #OSS 00010000837 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000838 +705bonus pay for amazing work on #OSS 00010000838 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000839 +705bonus pay for amazing work on #OSS 00010000839 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000840 +705bonus pay for amazing work on #OSS 00010000840 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000841 +705bonus pay for amazing work on #OSS 00010000841 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000842 +705bonus pay for amazing work on #OSS 00010000842 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000843 +705bonus pay for amazing work on #OSS 00010000843 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000844 +705bonus pay for amazing work on #OSS 00010000844 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000845 +705bonus pay for amazing work on #OSS 00010000845 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000846 +705bonus pay for amazing work on #OSS 00010000846 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000847 +705bonus pay for amazing work on #OSS 00010000847 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000848 +705bonus pay for amazing work on #OSS 00010000848 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000849 +705bonus pay for amazing work on #OSS 00010000849 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000850 +705bonus pay for amazing work on #OSS 00010000850 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000851 +705bonus pay for amazing work on #OSS 00010000851 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000852 +705bonus pay for amazing work on #OSS 00010000852 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000853 +705bonus pay for amazing work on #OSS 00010000853 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000854 +705bonus pay for amazing work on #OSS 00010000854 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000855 +705bonus pay for amazing work on #OSS 00010000855 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000856 +705bonus pay for amazing work on #OSS 00010000856 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000857 +705bonus pay for amazing work on #OSS 00010000857 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000858 +705bonus pay for amazing work on #OSS 00010000858 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000859 +705bonus pay for amazing work on #OSS 00010000859 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000860 +705bonus pay for amazing work on #OSS 00010000860 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000861 +705bonus pay for amazing work on #OSS 00010000861 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000862 +705bonus pay for amazing work on #OSS 00010000862 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000863 +705bonus pay for amazing work on #OSS 00010000863 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000864 +705bonus pay for amazing work on #OSS 00010000864 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000865 +705bonus pay for amazing work on #OSS 00010000865 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000866 +705bonus pay for amazing work on #OSS 00010000866 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000867 +705bonus pay for amazing work on #OSS 00010000867 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000868 +705bonus pay for amazing work on #OSS 00010000868 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000869 +705bonus pay for amazing work on #OSS 00010000869 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000870 +705bonus pay for amazing work on #OSS 00010000870 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000871 +705bonus pay for amazing work on #OSS 00010000871 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000872 +705bonus pay for amazing work on #OSS 00010000872 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000873 +705bonus pay for amazing work on #OSS 00010000873 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000874 +705bonus pay for amazing work on #OSS 00010000874 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000875 +705bonus pay for amazing work on #OSS 00010000875 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000876 +705bonus pay for amazing work on #OSS 00010000876 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000877 +705bonus pay for amazing work on #OSS 00010000877 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000878 +705bonus pay for amazing work on #OSS 00010000878 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000879 +705bonus pay for amazing work on #OSS 00010000879 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000880 +705bonus pay for amazing work on #OSS 00010000880 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000881 +705bonus pay for amazing work on #OSS 00010000881 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000882 +705bonus pay for amazing work on #OSS 00010000882 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000883 +705bonus pay for amazing work on #OSS 00010000883 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000884 +705bonus pay for amazing work on #OSS 00010000884 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000885 +705bonus pay for amazing work on #OSS 00010000885 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000886 +705bonus pay for amazing work on #OSS 00010000886 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000887 +705bonus pay for amazing work on #OSS 00010000887 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000888 +705bonus pay for amazing work on #OSS 00010000888 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000889 +705bonus pay for amazing work on #OSS 00010000889 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000890 +705bonus pay for amazing work on #OSS 00010000890 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000891 +705bonus pay for amazing work on #OSS 00010000891 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000892 +705bonus pay for amazing work on #OSS 00010000892 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000893 +705bonus pay for amazing work on #OSS 00010000893 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000894 +705bonus pay for amazing work on #OSS 00010000894 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000895 +705bonus pay for amazing work on #OSS 00010000895 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000896 +705bonus pay for amazing work on #OSS 00010000896 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000897 +705bonus pay for amazing work on #OSS 00010000897 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000898 +705bonus pay for amazing work on #OSS 00010000898 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000899 +705bonus pay for amazing work on #OSS 00010000899 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000900 +705bonus pay for amazing work on #OSS 00010000900 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000901 +705bonus pay for amazing work on #OSS 00010000901 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000902 +705bonus pay for amazing work on #OSS 00010000902 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000903 +705bonus pay for amazing work on #OSS 00010000903 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000904 +705bonus pay for amazing work on #OSS 00010000904 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000905 +705bonus pay for amazing work on #OSS 00010000905 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000906 +705bonus pay for amazing work on #OSS 00010000906 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000907 +705bonus pay for amazing work on #OSS 00010000907 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000908 +705bonus pay for amazing work on #OSS 00010000908 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000909 +705bonus pay for amazing work on #OSS 00010000909 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000910 +705bonus pay for amazing work on #OSS 00010000910 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000911 +705bonus pay for amazing work on #OSS 00010000911 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000912 +705bonus pay for amazing work on #OSS 00010000912 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000913 +705bonus pay for amazing work on #OSS 00010000913 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000914 +705bonus pay for amazing work on #OSS 00010000914 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000915 +705bonus pay for amazing work on #OSS 00010000915 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000916 +705bonus pay for amazing work on #OSS 00010000916 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000917 +705bonus pay for amazing work on #OSS 00010000917 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000918 +705bonus pay for amazing work on #OSS 00010000918 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000919 +705bonus pay for amazing work on #OSS 00010000919 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000920 +705bonus pay for amazing work on #OSS 00010000920 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000921 +705bonus pay for amazing work on #OSS 00010000921 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000922 +705bonus pay for amazing work on #OSS 00010000922 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000923 +705bonus pay for amazing work on #OSS 00010000923 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000924 +705bonus pay for amazing work on #OSS 00010000924 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000925 +705bonus pay for amazing work on #OSS 00010000925 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000926 +705bonus pay for amazing work on #OSS 00010000926 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000927 +705bonus pay for amazing work on #OSS 00010000927 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000928 +705bonus pay for amazing work on #OSS 00010000928 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000929 +705bonus pay for amazing work on #OSS 00010000929 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000930 +705bonus pay for amazing work on #OSS 00010000930 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000931 +705bonus pay for amazing work on #OSS 00010000931 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000932 +705bonus pay for amazing work on #OSS 00010000932 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000933 +705bonus pay for amazing work on #OSS 00010000933 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000934 +705bonus pay for amazing work on #OSS 00010000934 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000935 +705bonus pay for amazing work on #OSS 00010000935 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000936 +705bonus pay for amazing work on #OSS 00010000936 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000937 +705bonus pay for amazing work on #OSS 00010000937 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000938 +705bonus pay for amazing work on #OSS 00010000938 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000939 +705bonus pay for amazing work on #OSS 00010000939 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000940 +705bonus pay for amazing work on #OSS 00010000940 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000941 +705bonus pay for amazing work on #OSS 00010000941 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000942 +705bonus pay for amazing work on #OSS 00010000942 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000943 +705bonus pay for amazing work on #OSS 00010000943 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000944 +705bonus pay for amazing work on #OSS 00010000944 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000945 +705bonus pay for amazing work on #OSS 00010000945 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000946 +705bonus pay for amazing work on #OSS 00010000946 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000947 +705bonus pay for amazing work on #OSS 00010000947 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000948 +705bonus pay for amazing work on #OSS 00010000948 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000949 +705bonus pay for amazing work on #OSS 00010000949 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000950 +705bonus pay for amazing work on #OSS 00010000950 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000951 +705bonus pay for amazing work on #OSS 00010000951 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000952 +705bonus pay for amazing work on #OSS 00010000952 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000953 +705bonus pay for amazing work on #OSS 00010000953 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000954 +705bonus pay for amazing work on #OSS 00010000954 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000955 +705bonus pay for amazing work on #OSS 00010000955 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000956 +705bonus pay for amazing work on #OSS 00010000956 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000957 +705bonus pay for amazing work on #OSS 00010000957 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000958 +705bonus pay for amazing work on #OSS 00010000958 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000959 +705bonus pay for amazing work on #OSS 00010000959 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000960 +705bonus pay for amazing work on #OSS 00010000960 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000961 +705bonus pay for amazing work on #OSS 00010000961 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000962 +705bonus pay for amazing work on #OSS 00010000962 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000963 +705bonus pay for amazing work on #OSS 00010000963 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000964 +705bonus pay for amazing work on #OSS 00010000964 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000965 +705bonus pay for amazing work on #OSS 00010000965 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000966 +705bonus pay for amazing work on #OSS 00010000966 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000967 +705bonus pay for amazing work on #OSS 00010000967 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000968 +705bonus pay for amazing work on #OSS 00010000968 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000969 +705bonus pay for amazing work on #OSS 00010000969 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000970 +705bonus pay for amazing work on #OSS 00010000970 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000971 +705bonus pay for amazing work on #OSS 00010000971 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000972 +705bonus pay for amazing work on #OSS 00010000972 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000973 +705bonus pay for amazing work on #OSS 00010000973 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000974 +705bonus pay for amazing work on #OSS 00010000974 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000975 +705bonus pay for amazing work on #OSS 00010000975 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000976 +705bonus pay for amazing work on #OSS 00010000976 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000977 +705bonus pay for amazing work on #OSS 00010000977 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000978 +705bonus pay for amazing work on #OSS 00010000978 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000979 +705bonus pay for amazing work on #OSS 00010000979 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000980 +705bonus pay for amazing work on #OSS 00010000980 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000981 +705bonus pay for amazing work on #OSS 00010000981 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000982 +705bonus pay for amazing work on #OSS 00010000982 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000983 +705bonus pay for amazing work on #OSS 00010000983 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000984 +705bonus pay for amazing work on #OSS 00010000984 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000985 +705bonus pay for amazing work on #OSS 00010000985 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000986 +705bonus pay for amazing work on #OSS 00010000986 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000987 +705bonus pay for amazing work on #OSS 00010000987 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000988 +705bonus pay for amazing work on #OSS 00010000988 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000989 +705bonus pay for amazing work on #OSS 00010000989 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000990 +705bonus pay for amazing work on #OSS 00010000990 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000991 +705bonus pay for amazing work on #OSS 00010000991 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000992 +705bonus pay for amazing work on #OSS 00010000992 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000993 +705bonus pay for amazing work on #OSS 00010000993 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000994 +705bonus pay for amazing work on #OSS 00010000994 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000995 +705bonus pay for amazing work on #OSS 00010000995 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000996 +705bonus pay for amazing work on #OSS 00010000996 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000997 +705bonus pay for amazing work on #OSS 00010000997 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000998 +705bonus pay for amazing work on #OSS 00010000998 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000999 +705bonus pay for amazing work on #OSS 00010000999 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001000 +705bonus pay for amazing work on #OSS 00010001000 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001001 +705bonus pay for amazing work on #OSS 00010001001 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001002 +705bonus pay for amazing work on #OSS 00010001002 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001003 +705bonus pay for amazing work on #OSS 00010001003 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001004 +705bonus pay for amazing work on #OSS 00010001004 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001005 +705bonus pay for amazing work on #OSS 00010001005 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001006 +705bonus pay for amazing work on #OSS 00010001006 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001007 +705bonus pay for amazing work on #OSS 00010001007 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001008 +705bonus pay for amazing work on #OSS 00010001008 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001009 +705bonus pay for amazing work on #OSS 00010001009 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001010 +705bonus pay for amazing work on #OSS 00010001010 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001011 +705bonus pay for amazing work on #OSS 00010001011 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001012 +705bonus pay for amazing work on #OSS 00010001012 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001013 +705bonus pay for amazing work on #OSS 00010001013 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001014 +705bonus pay for amazing work on #OSS 00010001014 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001015 +705bonus pay for amazing work on #OSS 00010001015 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001016 +705bonus pay for amazing work on #OSS 00010001016 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001017 +705bonus pay for amazing work on #OSS 00010001017 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001018 +705bonus pay for amazing work on #OSS 00010001018 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001019 +705bonus pay for amazing work on #OSS 00010001019 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001020 +705bonus pay for amazing work on #OSS 00010001020 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001021 +705bonus pay for amazing work on #OSS 00010001021 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001022 +705bonus pay for amazing work on #OSS 00010001022 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001023 +705bonus pay for amazing work on #OSS 00010001023 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001024 +705bonus pay for amazing work on #OSS 00010001024 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001025 +705bonus pay for amazing work on #OSS 00010001025 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001026 +705bonus pay for amazing work on #OSS 00010001026 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001027 +705bonus pay for amazing work on #OSS 00010001027 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001028 +705bonus pay for amazing work on #OSS 00010001028 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001029 +705bonus pay for amazing work on #OSS 00010001029 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001030 +705bonus pay for amazing work on #OSS 00010001030 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001031 +705bonus pay for amazing work on #OSS 00010001031 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001032 +705bonus pay for amazing work on #OSS 00010001032 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001033 +705bonus pay for amazing work on #OSS 00010001033 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001034 +705bonus pay for amazing work on #OSS 00010001034 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001035 +705bonus pay for amazing work on #OSS 00010001035 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001036 +705bonus pay for amazing work on #OSS 00010001036 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001037 +705bonus pay for amazing work on #OSS 00010001037 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001038 +705bonus pay for amazing work on #OSS 00010001038 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001039 +705bonus pay for amazing work on #OSS 00010001039 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001040 +705bonus pay for amazing work on #OSS 00010001040 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001041 +705bonus pay for amazing work on #OSS 00010001041 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001042 +705bonus pay for amazing work on #OSS 00010001042 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001043 +705bonus pay for amazing work on #OSS 00010001043 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001044 +705bonus pay for amazing work on #OSS 00010001044 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001045 +705bonus pay for amazing work on #OSS 00010001045 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001046 +705bonus pay for amazing work on #OSS 00010001046 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001047 +705bonus pay for amazing work on #OSS 00010001047 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001048 +705bonus pay for amazing work on #OSS 00010001048 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001049 +705bonus pay for amazing work on #OSS 00010001049 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001050 +705bonus pay for amazing work on #OSS 00010001050 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001051 +705bonus pay for amazing work on #OSS 00010001051 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001052 +705bonus pay for amazing work on #OSS 00010001052 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001053 +705bonus pay for amazing work on #OSS 00010001053 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001054 +705bonus pay for amazing work on #OSS 00010001054 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001055 +705bonus pay for amazing work on #OSS 00010001055 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001056 +705bonus pay for amazing work on #OSS 00010001056 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001057 +705bonus pay for amazing work on #OSS 00010001057 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001058 +705bonus pay for amazing work on #OSS 00010001058 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001059 +705bonus pay for amazing work on #OSS 00010001059 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001060 +705bonus pay for amazing work on #OSS 00010001060 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001061 +705bonus pay for amazing work on #OSS 00010001061 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001062 +705bonus pay for amazing work on #OSS 00010001062 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001063 +705bonus pay for amazing work on #OSS 00010001063 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001064 +705bonus pay for amazing work on #OSS 00010001064 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001065 +705bonus pay for amazing work on #OSS 00010001065 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001066 +705bonus pay for amazing work on #OSS 00010001066 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001067 +705bonus pay for amazing work on #OSS 00010001067 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001068 +705bonus pay for amazing work on #OSS 00010001068 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001069 +705bonus pay for amazing work on #OSS 00010001069 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001070 +705bonus pay for amazing work on #OSS 00010001070 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001071 +705bonus pay for amazing work on #OSS 00010001071 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001072 +705bonus pay for amazing work on #OSS 00010001072 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001073 +705bonus pay for amazing work on #OSS 00010001073 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001074 +705bonus pay for amazing work on #OSS 00010001074 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001075 +705bonus pay for amazing work on #OSS 00010001075 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001076 +705bonus pay for amazing work on #OSS 00010001076 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001077 +705bonus pay for amazing work on #OSS 00010001077 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001078 +705bonus pay for amazing work on #OSS 00010001078 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001079 +705bonus pay for amazing work on #OSS 00010001079 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001080 +705bonus pay for amazing work on #OSS 00010001080 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001081 +705bonus pay for amazing work on #OSS 00010001081 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001082 +705bonus pay for amazing work on #OSS 00010001082 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001083 +705bonus pay for amazing work on #OSS 00010001083 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001084 +705bonus pay for amazing work on #OSS 00010001084 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001085 +705bonus pay for amazing work on #OSS 00010001085 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001086 +705bonus pay for amazing work on #OSS 00010001086 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001087 +705bonus pay for amazing work on #OSS 00010001087 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001088 +705bonus pay for amazing work on #OSS 00010001088 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001089 +705bonus pay for amazing work on #OSS 00010001089 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001090 +705bonus pay for amazing work on #OSS 00010001090 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001091 +705bonus pay for amazing work on #OSS 00010001091 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001092 +705bonus pay for amazing work on #OSS 00010001092 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001093 +705bonus pay for amazing work on #OSS 00010001093 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001094 +705bonus pay for amazing work on #OSS 00010001094 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001095 +705bonus pay for amazing work on #OSS 00010001095 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001096 +705bonus pay for amazing work on #OSS 00010001096 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001097 +705bonus pay for amazing work on #OSS 00010001097 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001098 +705bonus pay for amazing work on #OSS 00010001098 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001099 +705bonus pay for amazing work on #OSS 00010001099 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001100 +705bonus pay for amazing work on #OSS 00010001100 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001101 +705bonus pay for amazing work on #OSS 00010001101 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001102 +705bonus pay for amazing work on #OSS 00010001102 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001103 +705bonus pay for amazing work on #OSS 00010001103 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001104 +705bonus pay for amazing work on #OSS 00010001104 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001105 +705bonus pay for amazing work on #OSS 00010001105 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001106 +705bonus pay for amazing work on #OSS 00010001106 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001107 +705bonus pay for amazing work on #OSS 00010001107 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001108 +705bonus pay for amazing work on #OSS 00010001108 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001109 +705bonus pay for amazing work on #OSS 00010001109 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001110 +705bonus pay for amazing work on #OSS 00010001110 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001111 +705bonus pay for amazing work on #OSS 00010001111 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001112 +705bonus pay for amazing work on #OSS 00010001112 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001113 +705bonus pay for amazing work on #OSS 00010001113 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001114 +705bonus pay for amazing work on #OSS 00010001114 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001115 +705bonus pay for amazing work on #OSS 00010001115 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001116 +705bonus pay for amazing work on #OSS 00010001116 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001117 +705bonus pay for amazing work on #OSS 00010001117 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001118 +705bonus pay for amazing work on #OSS 00010001118 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001119 +705bonus pay for amazing work on #OSS 00010001119 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001120 +705bonus pay for amazing work on #OSS 00010001120 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001121 +705bonus pay for amazing work on #OSS 00010001121 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001122 +705bonus pay for amazing work on #OSS 00010001122 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001123 +705bonus pay for amazing work on #OSS 00010001123 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001124 +705bonus pay for amazing work on #OSS 00010001124 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001125 +705bonus pay for amazing work on #OSS 00010001125 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001126 +705bonus pay for amazing work on #OSS 00010001126 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001127 +705bonus pay for amazing work on #OSS 00010001127 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001128 +705bonus pay for amazing work on #OSS 00010001128 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001129 +705bonus pay for amazing work on #OSS 00010001129 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001130 +705bonus pay for amazing work on #OSS 00010001130 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001131 +705bonus pay for amazing work on #OSS 00010001131 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001132 +705bonus pay for amazing work on #OSS 00010001132 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001133 +705bonus pay for amazing work on #OSS 00010001133 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001134 +705bonus pay for amazing work on #OSS 00010001134 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001135 +705bonus pay for amazing work on #OSS 00010001135 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001136 +705bonus pay for amazing work on #OSS 00010001136 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001137 +705bonus pay for amazing work on #OSS 00010001137 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001138 +705bonus pay for amazing work on #OSS 00010001138 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001139 +705bonus pay for amazing work on #OSS 00010001139 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001140 +705bonus pay for amazing work on #OSS 00010001140 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001141 +705bonus pay for amazing work on #OSS 00010001141 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001142 +705bonus pay for amazing work on #OSS 00010001142 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001143 +705bonus pay for amazing work on #OSS 00010001143 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001144 +705bonus pay for amazing work on #OSS 00010001144 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001145 +705bonus pay for amazing work on #OSS 00010001145 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001146 +705bonus pay for amazing work on #OSS 00010001146 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001147 +705bonus pay for amazing work on #OSS 00010001147 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001148 +705bonus pay for amazing work on #OSS 00010001148 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001149 +705bonus pay for amazing work on #OSS 00010001149 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001150 +705bonus pay for amazing work on #OSS 00010001150 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001151 +705bonus pay for amazing work on #OSS 00010001151 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001152 +705bonus pay for amazing work on #OSS 00010001152 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001153 +705bonus pay for amazing work on #OSS 00010001153 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001154 +705bonus pay for amazing work on #OSS 00010001154 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001155 +705bonus pay for amazing work on #OSS 00010001155 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001156 +705bonus pay for amazing work on #OSS 00010001156 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001157 +705bonus pay for amazing work on #OSS 00010001157 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001158 +705bonus pay for amazing work on #OSS 00010001158 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001159 +705bonus pay for amazing work on #OSS 00010001159 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001160 +705bonus pay for amazing work on #OSS 00010001160 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001161 +705bonus pay for amazing work on #OSS 00010001161 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001162 +705bonus pay for amazing work on #OSS 00010001162 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001163 +705bonus pay for amazing work on #OSS 00010001163 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001164 +705bonus pay for amazing work on #OSS 00010001164 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001165 +705bonus pay for amazing work on #OSS 00010001165 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001166 +705bonus pay for amazing work on #OSS 00010001166 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001167 +705bonus pay for amazing work on #OSS 00010001167 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001168 +705bonus pay for amazing work on #OSS 00010001168 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001169 +705bonus pay for amazing work on #OSS 00010001169 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001170 +705bonus pay for amazing work on #OSS 00010001170 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001171 +705bonus pay for amazing work on #OSS 00010001171 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001172 +705bonus pay for amazing work on #OSS 00010001172 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001173 +705bonus pay for amazing work on #OSS 00010001173 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001174 +705bonus pay for amazing work on #OSS 00010001174 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001175 +705bonus pay for amazing work on #OSS 00010001175 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001176 +705bonus pay for amazing work on #OSS 00010001176 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001177 +705bonus pay for amazing work on #OSS 00010001177 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001178 +705bonus pay for amazing work on #OSS 00010001178 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001179 +705bonus pay for amazing work on #OSS 00010001179 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001180 +705bonus pay for amazing work on #OSS 00010001180 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001181 +705bonus pay for amazing work on #OSS 00010001181 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001182 +705bonus pay for amazing work on #OSS 00010001182 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001183 +705bonus pay for amazing work on #OSS 00010001183 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001184 +705bonus pay for amazing work on #OSS 00010001184 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001185 +705bonus pay for amazing work on #OSS 00010001185 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001186 +705bonus pay for amazing work on #OSS 00010001186 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001187 +705bonus pay for amazing work on #OSS 00010001187 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001188 +705bonus pay for amazing work on #OSS 00010001188 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001189 +705bonus pay for amazing work on #OSS 00010001189 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001190 +705bonus pay for amazing work on #OSS 00010001190 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001191 +705bonus pay for amazing work on #OSS 00010001191 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001192 +705bonus pay for amazing work on #OSS 00010001192 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001193 +705bonus pay for amazing work on #OSS 00010001193 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001194 +705bonus pay for amazing work on #OSS 00010001194 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001195 +705bonus pay for amazing work on #OSS 00010001195 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001196 +705bonus pay for amazing work on #OSS 00010001196 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001197 +705bonus pay for amazing work on #OSS 00010001197 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001198 +705bonus pay for amazing work on #OSS 00010001198 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001199 +705bonus pay for amazing work on #OSS 00010001199 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001200 +705bonus pay for amazing work on #OSS 00010001200 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001201 +705bonus pay for amazing work on #OSS 00010001201 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001202 +705bonus pay for amazing work on #OSS 00010001202 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001203 +705bonus pay for amazing work on #OSS 00010001203 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001204 +705bonus pay for amazing work on #OSS 00010001204 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001205 +705bonus pay for amazing work on #OSS 00010001205 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001206 +705bonus pay for amazing work on #OSS 00010001206 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001207 +705bonus pay for amazing work on #OSS 00010001207 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001208 +705bonus pay for amazing work on #OSS 00010001208 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001209 +705bonus pay for amazing work on #OSS 00010001209 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001210 +705bonus pay for amazing work on #OSS 00010001210 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001211 +705bonus pay for amazing work on #OSS 00010001211 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001212 +705bonus pay for amazing work on #OSS 00010001212 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001213 +705bonus pay for amazing work on #OSS 00010001213 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001214 +705bonus pay for amazing work on #OSS 00010001214 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001215 +705bonus pay for amazing work on #OSS 00010001215 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001216 +705bonus pay for amazing work on #OSS 00010001216 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001217 +705bonus pay for amazing work on #OSS 00010001217 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001218 +705bonus pay for amazing work on #OSS 00010001218 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001219 +705bonus pay for amazing work on #OSS 00010001219 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001220 +705bonus pay for amazing work on #OSS 00010001220 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001221 +705bonus pay for amazing work on #OSS 00010001221 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001222 +705bonus pay for amazing work on #OSS 00010001222 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001223 +705bonus pay for amazing work on #OSS 00010001223 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001224 +705bonus pay for amazing work on #OSS 00010001224 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001225 +705bonus pay for amazing work on #OSS 00010001225 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001226 +705bonus pay for amazing work on #OSS 00010001226 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001227 +705bonus pay for amazing work on #OSS 00010001227 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001228 +705bonus pay for amazing work on #OSS 00010001228 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001229 +705bonus pay for amazing work on #OSS 00010001229 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001230 +705bonus pay for amazing work on #OSS 00010001230 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001231 +705bonus pay for amazing work on #OSS 00010001231 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001232 +705bonus pay for amazing work on #OSS 00010001232 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001233 +705bonus pay for amazing work on #OSS 00010001233 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001234 +705bonus pay for amazing work on #OSS 00010001234 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001235 +705bonus pay for amazing work on #OSS 00010001235 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001236 +705bonus pay for amazing work on #OSS 00010001236 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001237 +705bonus pay for amazing work on #OSS 00010001237 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001238 +705bonus pay for amazing work on #OSS 00010001238 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001239 +705bonus pay for amazing work on #OSS 00010001239 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001240 +705bonus pay for amazing work on #OSS 00010001240 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001241 +705bonus pay for amazing work on #OSS 00010001241 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001242 +705bonus pay for amazing work on #OSS 00010001242 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001243 +705bonus pay for amazing work on #OSS 00010001243 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001244 +705bonus pay for amazing work on #OSS 00010001244 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001245 +705bonus pay for amazing work on #OSS 00010001245 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001246 +705bonus pay for amazing work on #OSS 00010001246 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001247 +705bonus pay for amazing work on #OSS 00010001247 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001248 +705bonus pay for amazing work on #OSS 00010001248 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001249 +705bonus pay for amazing work on #OSS 00010001249 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001250 +705bonus pay for amazing work on #OSS 00010001250 +82000025008922512500000000000000000125000000121042882 121042880000001 +5200Wells Fargo 121042882 PPDTrans. Des 190301 1121042880000002 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000001 +705bonus pay for amazing work on #OSS 00010000001 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000002 +705bonus pay for amazing work on #OSS 00010000002 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000003 +705bonus pay for amazing work on #OSS 00010000003 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000004 +705bonus pay for amazing work on #OSS 00010000004 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000005 +705bonus pay for amazing work on #OSS 00010000005 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000006 +705bonus pay for amazing work on #OSS 00010000006 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000007 +705bonus pay for amazing work on #OSS 00010000007 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000008 +705bonus pay for amazing work on #OSS 00010000008 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000009 +705bonus pay for amazing work on #OSS 00010000009 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000010 +705bonus pay for amazing work on #OSS 00010000010 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000011 +705bonus pay for amazing work on #OSS 00010000011 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000012 +705bonus pay for amazing work on #OSS 00010000012 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000013 +705bonus pay for amazing work on #OSS 00010000013 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000014 +705bonus pay for amazing work on #OSS 00010000014 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000015 +705bonus pay for amazing work on #OSS 00010000015 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000016 +705bonus pay for amazing work on #OSS 00010000016 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000017 +705bonus pay for amazing work on #OSS 00010000017 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000018 +705bonus pay for amazing work on #OSS 00010000018 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000019 +705bonus pay for amazing work on #OSS 00010000019 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000020 +705bonus pay for amazing work on #OSS 00010000020 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000021 +705bonus pay for amazing work on #OSS 00010000021 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000022 +705bonus pay for amazing work on #OSS 00010000022 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000023 +705bonus pay for amazing work on #OSS 00010000023 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000024 +705bonus pay for amazing work on #OSS 00010000024 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000025 +705bonus pay for amazing work on #OSS 00010000025 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000026 +705bonus pay for amazing work on #OSS 00010000026 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000027 +705bonus pay for amazing work on #OSS 00010000027 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000028 +705bonus pay for amazing work on #OSS 00010000028 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000029 +705bonus pay for amazing work on #OSS 00010000029 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000030 +705bonus pay for amazing work on #OSS 00010000030 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000031 +705bonus pay for amazing work on #OSS 00010000031 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000032 +705bonus pay for amazing work on #OSS 00010000032 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000033 +705bonus pay for amazing work on #OSS 00010000033 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000034 +705bonus pay for amazing work on #OSS 00010000034 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000035 +705bonus pay for amazing work on #OSS 00010000035 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000036 +705bonus pay for amazing work on #OSS 00010000036 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000037 +705bonus pay for amazing work on #OSS 00010000037 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000038 +705bonus pay for amazing work on #OSS 00010000038 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000039 +705bonus pay for amazing work on #OSS 00010000039 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000040 +705bonus pay for amazing work on #OSS 00010000040 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000041 +705bonus pay for amazing work on #OSS 00010000041 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000042 +705bonus pay for amazing work on #OSS 00010000042 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000043 +705bonus pay for amazing work on #OSS 00010000043 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000044 +705bonus pay for amazing work on #OSS 00010000044 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000045 +705bonus pay for amazing work on #OSS 00010000045 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000046 +705bonus pay for amazing work on #OSS 00010000046 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000047 +705bonus pay for amazing work on #OSS 00010000047 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000048 +705bonus pay for amazing work on #OSS 00010000048 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000049 +705bonus pay for amazing work on #OSS 00010000049 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000050 +705bonus pay for amazing work on #OSS 00010000050 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000051 +705bonus pay for amazing work on #OSS 00010000051 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000052 +705bonus pay for amazing work on #OSS 00010000052 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000053 +705bonus pay for amazing work on #OSS 00010000053 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000054 +705bonus pay for amazing work on #OSS 00010000054 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000055 +705bonus pay for amazing work on #OSS 00010000055 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000056 +705bonus pay for amazing work on #OSS 00010000056 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000057 +705bonus pay for amazing work on #OSS 00010000057 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000058 +705bonus pay for amazing work on #OSS 00010000058 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000059 +705bonus pay for amazing work on #OSS 00010000059 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000060 +705bonus pay for amazing work on #OSS 00010000060 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000061 +705bonus pay for amazing work on #OSS 00010000061 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000062 +705bonus pay for amazing work on #OSS 00010000062 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000063 +705bonus pay for amazing work on #OSS 00010000063 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000064 +705bonus pay for amazing work on #OSS 00010000064 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000065 +705bonus pay for amazing work on #OSS 00010000065 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000066 +705bonus pay for amazing work on #OSS 00010000066 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000067 +705bonus pay for amazing work on #OSS 00010000067 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000068 +705bonus pay for amazing work on #OSS 00010000068 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000069 +705bonus pay for amazing work on #OSS 00010000069 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000070 +705bonus pay for amazing work on #OSS 00010000070 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000071 +705bonus pay for amazing work on #OSS 00010000071 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000072 +705bonus pay for amazing work on #OSS 00010000072 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000073 +705bonus pay for amazing work on #OSS 00010000073 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000074 +705bonus pay for amazing work on #OSS 00010000074 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000075 +705bonus pay for amazing work on #OSS 00010000075 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000076 +705bonus pay for amazing work on #OSS 00010000076 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000077 +705bonus pay for amazing work on #OSS 00010000077 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000078 +705bonus pay for amazing work on #OSS 00010000078 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000079 +705bonus pay for amazing work on #OSS 00010000079 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000080 +705bonus pay for amazing work on #OSS 00010000080 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000081 +705bonus pay for amazing work on #OSS 00010000081 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000082 +705bonus pay for amazing work on #OSS 00010000082 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000083 +705bonus pay for amazing work on #OSS 00010000083 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000084 +705bonus pay for amazing work on #OSS 00010000084 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000085 +705bonus pay for amazing work on #OSS 00010000085 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000086 +705bonus pay for amazing work on #OSS 00010000086 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000087 +705bonus pay for amazing work on #OSS 00010000087 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000088 +705bonus pay for amazing work on #OSS 00010000088 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000089 +705bonus pay for amazing work on #OSS 00010000089 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000090 +705bonus pay for amazing work on #OSS 00010000090 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000091 +705bonus pay for amazing work on #OSS 00010000091 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000092 +705bonus pay for amazing work on #OSS 00010000092 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000093 +705bonus pay for amazing work on #OSS 00010000093 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000094 +705bonus pay for amazing work on #OSS 00010000094 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000095 +705bonus pay for amazing work on #OSS 00010000095 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000096 +705bonus pay for amazing work on #OSS 00010000096 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000097 +705bonus pay for amazing work on #OSS 00010000097 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000098 +705bonus pay for amazing work on #OSS 00010000098 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000099 +705bonus pay for amazing work on #OSS 00010000099 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000100 +705bonus pay for amazing work on #OSS 00010000100 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000101 +705bonus pay for amazing work on #OSS 00010000101 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000102 +705bonus pay for amazing work on #OSS 00010000102 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000103 +705bonus pay for amazing work on #OSS 00010000103 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000104 +705bonus pay for amazing work on #OSS 00010000104 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000105 +705bonus pay for amazing work on #OSS 00010000105 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000106 +705bonus pay for amazing work on #OSS 00010000106 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000107 +705bonus pay for amazing work on #OSS 00010000107 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000108 +705bonus pay for amazing work on #OSS 00010000108 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000109 +705bonus pay for amazing work on #OSS 00010000109 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000110 +705bonus pay for amazing work on #OSS 00010000110 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000111 +705bonus pay for amazing work on #OSS 00010000111 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000112 +705bonus pay for amazing work on #OSS 00010000112 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000113 +705bonus pay for amazing work on #OSS 00010000113 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000114 +705bonus pay for amazing work on #OSS 00010000114 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000115 +705bonus pay for amazing work on #OSS 00010000115 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000116 +705bonus pay for amazing work on #OSS 00010000116 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000117 +705bonus pay for amazing work on #OSS 00010000117 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000118 +705bonus pay for amazing work on #OSS 00010000118 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000119 +705bonus pay for amazing work on #OSS 00010000119 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000120 +705bonus pay for amazing work on #OSS 00010000120 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000121 +705bonus pay for amazing work on #OSS 00010000121 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000122 +705bonus pay for amazing work on #OSS 00010000122 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000123 +705bonus pay for amazing work on #OSS 00010000123 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000124 +705bonus pay for amazing work on #OSS 00010000124 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000125 +705bonus pay for amazing work on #OSS 00010000125 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000126 +705bonus pay for amazing work on #OSS 00010000126 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000127 +705bonus pay for amazing work on #OSS 00010000127 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000128 +705bonus pay for amazing work on #OSS 00010000128 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000129 +705bonus pay for amazing work on #OSS 00010000129 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000130 +705bonus pay for amazing work on #OSS 00010000130 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000131 +705bonus pay for amazing work on #OSS 00010000131 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000132 +705bonus pay for amazing work on #OSS 00010000132 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000133 +705bonus pay for amazing work on #OSS 00010000133 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000134 +705bonus pay for amazing work on #OSS 00010000134 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000135 +705bonus pay for amazing work on #OSS 00010000135 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000136 +705bonus pay for amazing work on #OSS 00010000136 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000137 +705bonus pay for amazing work on #OSS 00010000137 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000138 +705bonus pay for amazing work on #OSS 00010000138 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000139 +705bonus pay for amazing work on #OSS 00010000139 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000140 +705bonus pay for amazing work on #OSS 00010000140 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000141 +705bonus pay for amazing work on #OSS 00010000141 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000142 +705bonus pay for amazing work on #OSS 00010000142 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000143 +705bonus pay for amazing work on #OSS 00010000143 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000144 +705bonus pay for amazing work on #OSS 00010000144 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000145 +705bonus pay for amazing work on #OSS 00010000145 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000146 +705bonus pay for amazing work on #OSS 00010000146 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000147 +705bonus pay for amazing work on #OSS 00010000147 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000148 +705bonus pay for amazing work on #OSS 00010000148 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000149 +705bonus pay for amazing work on #OSS 00010000149 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000150 +705bonus pay for amazing work on #OSS 00010000150 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000151 +705bonus pay for amazing work on #OSS 00010000151 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000152 +705bonus pay for amazing work on #OSS 00010000152 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000153 +705bonus pay for amazing work on #OSS 00010000153 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000154 +705bonus pay for amazing work on #OSS 00010000154 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000155 +705bonus pay for amazing work on #OSS 00010000155 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000156 +705bonus pay for amazing work on #OSS 00010000156 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000157 +705bonus pay for amazing work on #OSS 00010000157 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000158 +705bonus pay for amazing work on #OSS 00010000158 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000159 +705bonus pay for amazing work on #OSS 00010000159 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000160 +705bonus pay for amazing work on #OSS 00010000160 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000161 +705bonus pay for amazing work on #OSS 00010000161 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000162 +705bonus pay for amazing work on #OSS 00010000162 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000163 +705bonus pay for amazing work on #OSS 00010000163 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000164 +705bonus pay for amazing work on #OSS 00010000164 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000165 +705bonus pay for amazing work on #OSS 00010000165 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000166 +705bonus pay for amazing work on #OSS 00010000166 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000167 +705bonus pay for amazing work on #OSS 00010000167 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000168 +705bonus pay for amazing work on #OSS 00010000168 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000169 +705bonus pay for amazing work on #OSS 00010000169 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000170 +705bonus pay for amazing work on #OSS 00010000170 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000171 +705bonus pay for amazing work on #OSS 00010000171 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000172 +705bonus pay for amazing work on #OSS 00010000172 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000173 +705bonus pay for amazing work on #OSS 00010000173 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000174 +705bonus pay for amazing work on #OSS 00010000174 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000175 +705bonus pay for amazing work on #OSS 00010000175 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000176 +705bonus pay for amazing work on #OSS 00010000176 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000177 +705bonus pay for amazing work on #OSS 00010000177 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000178 +705bonus pay for amazing work on #OSS 00010000178 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000179 +705bonus pay for amazing work on #OSS 00010000179 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000180 +705bonus pay for amazing work on #OSS 00010000180 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000181 +705bonus pay for amazing work on #OSS 00010000181 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000182 +705bonus pay for amazing work on #OSS 00010000182 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000183 +705bonus pay for amazing work on #OSS 00010000183 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000184 +705bonus pay for amazing work on #OSS 00010000184 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000185 +705bonus pay for amazing work on #OSS 00010000185 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000186 +705bonus pay for amazing work on #OSS 00010000186 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000187 +705bonus pay for amazing work on #OSS 00010000187 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000188 +705bonus pay for amazing work on #OSS 00010000188 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000189 +705bonus pay for amazing work on #OSS 00010000189 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000190 +705bonus pay for amazing work on #OSS 00010000190 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000191 +705bonus pay for amazing work on #OSS 00010000191 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000192 +705bonus pay for amazing work on #OSS 00010000192 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000193 +705bonus pay for amazing work on #OSS 00010000193 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000194 +705bonus pay for amazing work on #OSS 00010000194 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000195 +705bonus pay for amazing work on #OSS 00010000195 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000196 +705bonus pay for amazing work on #OSS 00010000196 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000197 +705bonus pay for amazing work on #OSS 00010000197 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000198 +705bonus pay for amazing work on #OSS 00010000198 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000199 +705bonus pay for amazing work on #OSS 00010000199 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000200 +705bonus pay for amazing work on #OSS 00010000200 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000201 +705bonus pay for amazing work on #OSS 00010000201 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000202 +705bonus pay for amazing work on #OSS 00010000202 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000203 +705bonus pay for amazing work on #OSS 00010000203 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000204 +705bonus pay for amazing work on #OSS 00010000204 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000205 +705bonus pay for amazing work on #OSS 00010000205 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000206 +705bonus pay for amazing work on #OSS 00010000206 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000207 +705bonus pay for amazing work on #OSS 00010000207 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000208 +705bonus pay for amazing work on #OSS 00010000208 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000209 +705bonus pay for amazing work on #OSS 00010000209 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000210 +705bonus pay for amazing work on #OSS 00010000210 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000211 +705bonus pay for amazing work on #OSS 00010000211 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000212 +705bonus pay for amazing work on #OSS 00010000212 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000213 +705bonus pay for amazing work on #OSS 00010000213 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000214 +705bonus pay for amazing work on #OSS 00010000214 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000215 +705bonus pay for amazing work on #OSS 00010000215 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000216 +705bonus pay for amazing work on #OSS 00010000216 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000217 +705bonus pay for amazing work on #OSS 00010000217 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000218 +705bonus pay for amazing work on #OSS 00010000218 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000219 +705bonus pay for amazing work on #OSS 00010000219 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000220 +705bonus pay for amazing work on #OSS 00010000220 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000221 +705bonus pay for amazing work on #OSS 00010000221 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000222 +705bonus pay for amazing work on #OSS 00010000222 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000223 +705bonus pay for amazing work on #OSS 00010000223 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000224 +705bonus pay for amazing work on #OSS 00010000224 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000225 +705bonus pay for amazing work on #OSS 00010000225 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000226 +705bonus pay for amazing work on #OSS 00010000226 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000227 +705bonus pay for amazing work on #OSS 00010000227 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000228 +705bonus pay for amazing work on #OSS 00010000228 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000229 +705bonus pay for amazing work on #OSS 00010000229 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000230 +705bonus pay for amazing work on #OSS 00010000230 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000231 +705bonus pay for amazing work on #OSS 00010000231 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000232 +705bonus pay for amazing work on #OSS 00010000232 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000233 +705bonus pay for amazing work on #OSS 00010000233 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000234 +705bonus pay for amazing work on #OSS 00010000234 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000235 +705bonus pay for amazing work on #OSS 00010000235 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000236 +705bonus pay for amazing work on #OSS 00010000236 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000237 +705bonus pay for amazing work on #OSS 00010000237 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000238 +705bonus pay for amazing work on #OSS 00010000238 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000239 +705bonus pay for amazing work on #OSS 00010000239 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000240 +705bonus pay for amazing work on #OSS 00010000240 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000241 +705bonus pay for amazing work on #OSS 00010000241 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000242 +705bonus pay for amazing work on #OSS 00010000242 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000243 +705bonus pay for amazing work on #OSS 00010000243 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000244 +705bonus pay for amazing work on #OSS 00010000244 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000245 +705bonus pay for amazing work on #OSS 00010000245 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000246 +705bonus pay for amazing work on #OSS 00010000246 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000247 +705bonus pay for amazing work on #OSS 00010000247 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000248 +705bonus pay for amazing work on #OSS 00010000248 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000249 +705bonus pay for amazing work on #OSS 00010000249 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000250 +705bonus pay for amazing work on #OSS 00010000250 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000251 +705bonus pay for amazing work on #OSS 00010000251 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000252 +705bonus pay for amazing work on #OSS 00010000252 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000253 +705bonus pay for amazing work on #OSS 00010000253 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000254 +705bonus pay for amazing work on #OSS 00010000254 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000255 +705bonus pay for amazing work on #OSS 00010000255 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000256 +705bonus pay for amazing work on #OSS 00010000256 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000257 +705bonus pay for amazing work on #OSS 00010000257 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000258 +705bonus pay for amazing work on #OSS 00010000258 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000259 +705bonus pay for amazing work on #OSS 00010000259 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000260 +705bonus pay for amazing work on #OSS 00010000260 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000261 +705bonus pay for amazing work on #OSS 00010000261 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000262 +705bonus pay for amazing work on #OSS 00010000262 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000263 +705bonus pay for amazing work on #OSS 00010000263 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000264 +705bonus pay for amazing work on #OSS 00010000264 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000265 +705bonus pay for amazing work on #OSS 00010000265 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000266 +705bonus pay for amazing work on #OSS 00010000266 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000267 +705bonus pay for amazing work on #OSS 00010000267 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000268 +705bonus pay for amazing work on #OSS 00010000268 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000269 +705bonus pay for amazing work on #OSS 00010000269 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000270 +705bonus pay for amazing work on #OSS 00010000270 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000271 +705bonus pay for amazing work on #OSS 00010000271 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000272 +705bonus pay for amazing work on #OSS 00010000272 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000273 +705bonus pay for amazing work on #OSS 00010000273 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000274 +705bonus pay for amazing work on #OSS 00010000274 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000275 +705bonus pay for amazing work on #OSS 00010000275 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000276 +705bonus pay for amazing work on #OSS 00010000276 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000277 +705bonus pay for amazing work on #OSS 00010000277 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000278 +705bonus pay for amazing work on #OSS 00010000278 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000279 +705bonus pay for amazing work on #OSS 00010000279 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000280 +705bonus pay for amazing work on #OSS 00010000280 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000281 +705bonus pay for amazing work on #OSS 00010000281 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000282 +705bonus pay for amazing work on #OSS 00010000282 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000283 +705bonus pay for amazing work on #OSS 00010000283 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000284 +705bonus pay for amazing work on #OSS 00010000284 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000285 +705bonus pay for amazing work on #OSS 00010000285 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000286 +705bonus pay for amazing work on #OSS 00010000286 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000287 +705bonus pay for amazing work on #OSS 00010000287 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000288 +705bonus pay for amazing work on #OSS 00010000288 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000289 +705bonus pay for amazing work on #OSS 00010000289 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000290 +705bonus pay for amazing work on #OSS 00010000290 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000291 +705bonus pay for amazing work on #OSS 00010000291 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000292 +705bonus pay for amazing work on #OSS 00010000292 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000293 +705bonus pay for amazing work on #OSS 00010000293 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000294 +705bonus pay for amazing work on #OSS 00010000294 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000295 +705bonus pay for amazing work on #OSS 00010000295 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000296 +705bonus pay for amazing work on #OSS 00010000296 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000297 +705bonus pay for amazing work on #OSS 00010000297 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000298 +705bonus pay for amazing work on #OSS 00010000298 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000299 +705bonus pay for amazing work on #OSS 00010000299 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000300 +705bonus pay for amazing work on #OSS 00010000300 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000301 +705bonus pay for amazing work on #OSS 00010000301 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000302 +705bonus pay for amazing work on #OSS 00010000302 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000303 +705bonus pay for amazing work on #OSS 00010000303 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000304 +705bonus pay for amazing work on #OSS 00010000304 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000305 +705bonus pay for amazing work on #OSS 00010000305 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000306 +705bonus pay for amazing work on #OSS 00010000306 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000307 +705bonus pay for amazing work on #OSS 00010000307 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000308 +705bonus pay for amazing work on #OSS 00010000308 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000309 +705bonus pay for amazing work on #OSS 00010000309 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000310 +705bonus pay for amazing work on #OSS 00010000310 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000311 +705bonus pay for amazing work on #OSS 00010000311 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000312 +705bonus pay for amazing work on #OSS 00010000312 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000313 +705bonus pay for amazing work on #OSS 00010000313 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000314 +705bonus pay for amazing work on #OSS 00010000314 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000315 +705bonus pay for amazing work on #OSS 00010000315 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000316 +705bonus pay for amazing work on #OSS 00010000316 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000317 +705bonus pay for amazing work on #OSS 00010000317 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000318 +705bonus pay for amazing work on #OSS 00010000318 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000319 +705bonus pay for amazing work on #OSS 00010000319 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000320 +705bonus pay for amazing work on #OSS 00010000320 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000321 +705bonus pay for amazing work on #OSS 00010000321 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000322 +705bonus pay for amazing work on #OSS 00010000322 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000323 +705bonus pay for amazing work on #OSS 00010000323 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000324 +705bonus pay for amazing work on #OSS 00010000324 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000325 +705bonus pay for amazing work on #OSS 00010000325 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000326 +705bonus pay for amazing work on #OSS 00010000326 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000327 +705bonus pay for amazing work on #OSS 00010000327 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000328 +705bonus pay for amazing work on #OSS 00010000328 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000329 +705bonus pay for amazing work on #OSS 00010000329 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000330 +705bonus pay for amazing work on #OSS 00010000330 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000331 +705bonus pay for amazing work on #OSS 00010000331 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000332 +705bonus pay for amazing work on #OSS 00010000332 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000333 +705bonus pay for amazing work on #OSS 00010000333 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000334 +705bonus pay for amazing work on #OSS 00010000334 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000335 +705bonus pay for amazing work on #OSS 00010000335 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000336 +705bonus pay for amazing work on #OSS 00010000336 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000337 +705bonus pay for amazing work on #OSS 00010000337 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000338 +705bonus pay for amazing work on #OSS 00010000338 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000339 +705bonus pay for amazing work on #OSS 00010000339 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000340 +705bonus pay for amazing work on #OSS 00010000340 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000341 +705bonus pay for amazing work on #OSS 00010000341 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000342 +705bonus pay for amazing work on #OSS 00010000342 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000343 +705bonus pay for amazing work on #OSS 00010000343 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000344 +705bonus pay for amazing work on #OSS 00010000344 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000345 +705bonus pay for amazing work on #OSS 00010000345 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000346 +705bonus pay for amazing work on #OSS 00010000346 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000347 +705bonus pay for amazing work on #OSS 00010000347 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000348 +705bonus pay for amazing work on #OSS 00010000348 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000349 +705bonus pay for amazing work on #OSS 00010000349 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000350 +705bonus pay for amazing work on #OSS 00010000350 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000351 +705bonus pay for amazing work on #OSS 00010000351 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000352 +705bonus pay for amazing work on #OSS 00010000352 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000353 +705bonus pay for amazing work on #OSS 00010000353 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000354 +705bonus pay for amazing work on #OSS 00010000354 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000355 +705bonus pay for amazing work on #OSS 00010000355 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000356 +705bonus pay for amazing work on #OSS 00010000356 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000357 +705bonus pay for amazing work on #OSS 00010000357 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000358 +705bonus pay for amazing work on #OSS 00010000358 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000359 +705bonus pay for amazing work on #OSS 00010000359 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000360 +705bonus pay for amazing work on #OSS 00010000360 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000361 +705bonus pay for amazing work on #OSS 00010000361 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000362 +705bonus pay for amazing work on #OSS 00010000362 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000363 +705bonus pay for amazing work on #OSS 00010000363 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000364 +705bonus pay for amazing work on #OSS 00010000364 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000365 +705bonus pay for amazing work on #OSS 00010000365 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000366 +705bonus pay for amazing work on #OSS 00010000366 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000367 +705bonus pay for amazing work on #OSS 00010000367 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000368 +705bonus pay for amazing work on #OSS 00010000368 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000369 +705bonus pay for amazing work on #OSS 00010000369 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000370 +705bonus pay for amazing work on #OSS 00010000370 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000371 +705bonus pay for amazing work on #OSS 00010000371 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000372 +705bonus pay for amazing work on #OSS 00010000372 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000373 +705bonus pay for amazing work on #OSS 00010000373 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000374 +705bonus pay for amazing work on #OSS 00010000374 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000375 +705bonus pay for amazing work on #OSS 00010000375 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000376 +705bonus pay for amazing work on #OSS 00010000376 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000377 +705bonus pay for amazing work on #OSS 00010000377 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000378 +705bonus pay for amazing work on #OSS 00010000378 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000379 +705bonus pay for amazing work on #OSS 00010000379 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000380 +705bonus pay for amazing work on #OSS 00010000380 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000381 +705bonus pay for amazing work on #OSS 00010000381 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000382 +705bonus pay for amazing work on #OSS 00010000382 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000383 +705bonus pay for amazing work on #OSS 00010000383 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000384 +705bonus pay for amazing work on #OSS 00010000384 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000385 +705bonus pay for amazing work on #OSS 00010000385 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000386 +705bonus pay for amazing work on #OSS 00010000386 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000387 +705bonus pay for amazing work on #OSS 00010000387 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000388 +705bonus pay for amazing work on #OSS 00010000388 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000389 +705bonus pay for amazing work on #OSS 00010000389 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000390 +705bonus pay for amazing work on #OSS 00010000390 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000391 +705bonus pay for amazing work on #OSS 00010000391 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000392 +705bonus pay for amazing work on #OSS 00010000392 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000393 +705bonus pay for amazing work on #OSS 00010000393 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000394 +705bonus pay for amazing work on #OSS 00010000394 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000395 +705bonus pay for amazing work on #OSS 00010000395 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000396 +705bonus pay for amazing work on #OSS 00010000396 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000397 +705bonus pay for amazing work on #OSS 00010000397 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000398 +705bonus pay for amazing work on #OSS 00010000398 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000399 +705bonus pay for amazing work on #OSS 00010000399 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000400 +705bonus pay for amazing work on #OSS 00010000400 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000401 +705bonus pay for amazing work on #OSS 00010000401 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000402 +705bonus pay for amazing work on #OSS 00010000402 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000403 +705bonus pay for amazing work on #OSS 00010000403 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000404 +705bonus pay for amazing work on #OSS 00010000404 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000405 +705bonus pay for amazing work on #OSS 00010000405 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000406 +705bonus pay for amazing work on #OSS 00010000406 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000407 +705bonus pay for amazing work on #OSS 00010000407 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000408 +705bonus pay for amazing work on #OSS 00010000408 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000409 +705bonus pay for amazing work on #OSS 00010000409 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000410 +705bonus pay for amazing work on #OSS 00010000410 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000411 +705bonus pay for amazing work on #OSS 00010000411 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000412 +705bonus pay for amazing work on #OSS 00010000412 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000413 +705bonus pay for amazing work on #OSS 00010000413 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000414 +705bonus pay for amazing work on #OSS 00010000414 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000415 +705bonus pay for amazing work on #OSS 00010000415 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000416 +705bonus pay for amazing work on #OSS 00010000416 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000417 +705bonus pay for amazing work on #OSS 00010000417 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000418 +705bonus pay for amazing work on #OSS 00010000418 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000419 +705bonus pay for amazing work on #OSS 00010000419 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000420 +705bonus pay for amazing work on #OSS 00010000420 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000421 +705bonus pay for amazing work on #OSS 00010000421 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000422 +705bonus pay for amazing work on #OSS 00010000422 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000423 +705bonus pay for amazing work on #OSS 00010000423 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000424 +705bonus pay for amazing work on #OSS 00010000424 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000425 +705bonus pay for amazing work on #OSS 00010000425 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000426 +705bonus pay for amazing work on #OSS 00010000426 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000427 +705bonus pay for amazing work on #OSS 00010000427 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000428 +705bonus pay for amazing work on #OSS 00010000428 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000429 +705bonus pay for amazing work on #OSS 00010000429 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000430 +705bonus pay for amazing work on #OSS 00010000430 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000431 +705bonus pay for amazing work on #OSS 00010000431 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000432 +705bonus pay for amazing work on #OSS 00010000432 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000433 +705bonus pay for amazing work on #OSS 00010000433 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000434 +705bonus pay for amazing work on #OSS 00010000434 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000435 +705bonus pay for amazing work on #OSS 00010000435 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000436 +705bonus pay for amazing work on #OSS 00010000436 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000437 +705bonus pay for amazing work on #OSS 00010000437 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000438 +705bonus pay for amazing work on #OSS 00010000438 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000439 +705bonus pay for amazing work on #OSS 00010000439 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000440 +705bonus pay for amazing work on #OSS 00010000440 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000441 +705bonus pay for amazing work on #OSS 00010000441 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000442 +705bonus pay for amazing work on #OSS 00010000442 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000443 +705bonus pay for amazing work on #OSS 00010000443 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000444 +705bonus pay for amazing work on #OSS 00010000444 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000445 +705bonus pay for amazing work on #OSS 00010000445 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000446 +705bonus pay for amazing work on #OSS 00010000446 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000447 +705bonus pay for amazing work on #OSS 00010000447 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000448 +705bonus pay for amazing work on #OSS 00010000448 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000449 +705bonus pay for amazing work on #OSS 00010000449 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000450 +705bonus pay for amazing work on #OSS 00010000450 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000451 +705bonus pay for amazing work on #OSS 00010000451 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000452 +705bonus pay for amazing work on #OSS 00010000452 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000453 +705bonus pay for amazing work on #OSS 00010000453 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000454 +705bonus pay for amazing work on #OSS 00010000454 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000455 +705bonus pay for amazing work on #OSS 00010000455 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000456 +705bonus pay for amazing work on #OSS 00010000456 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000457 +705bonus pay for amazing work on #OSS 00010000457 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000458 +705bonus pay for amazing work on #OSS 00010000458 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000459 +705bonus pay for amazing work on #OSS 00010000459 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000460 +705bonus pay for amazing work on #OSS 00010000460 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000461 +705bonus pay for amazing work on #OSS 00010000461 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000462 +705bonus pay for amazing work on #OSS 00010000462 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000463 +705bonus pay for amazing work on #OSS 00010000463 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000464 +705bonus pay for amazing work on #OSS 00010000464 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000465 +705bonus pay for amazing work on #OSS 00010000465 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000466 +705bonus pay for amazing work on #OSS 00010000466 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000467 +705bonus pay for amazing work on #OSS 00010000467 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000468 +705bonus pay for amazing work on #OSS 00010000468 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000469 +705bonus pay for amazing work on #OSS 00010000469 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000470 +705bonus pay for amazing work on #OSS 00010000470 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000471 +705bonus pay for amazing work on #OSS 00010000471 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000472 +705bonus pay for amazing work on #OSS 00010000472 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000473 +705bonus pay for amazing work on #OSS 00010000473 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000474 +705bonus pay for amazing work on #OSS 00010000474 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000475 +705bonus pay for amazing work on #OSS 00010000475 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000476 +705bonus pay for amazing work on #OSS 00010000476 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000477 +705bonus pay for amazing work on #OSS 00010000477 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000478 +705bonus pay for amazing work on #OSS 00010000478 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000479 +705bonus pay for amazing work on #OSS 00010000479 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000480 +705bonus pay for amazing work on #OSS 00010000480 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000481 +705bonus pay for amazing work on #OSS 00010000481 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000482 +705bonus pay for amazing work on #OSS 00010000482 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000483 +705bonus pay for amazing work on #OSS 00010000483 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000484 +705bonus pay for amazing work on #OSS 00010000484 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000485 +705bonus pay for amazing work on #OSS 00010000485 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000486 +705bonus pay for amazing work on #OSS 00010000486 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000487 +705bonus pay for amazing work on #OSS 00010000487 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000488 +705bonus pay for amazing work on #OSS 00010000488 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000489 +705bonus pay for amazing work on #OSS 00010000489 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000490 +705bonus pay for amazing work on #OSS 00010000490 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000491 +705bonus pay for amazing work on #OSS 00010000491 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000492 +705bonus pay for amazing work on #OSS 00010000492 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000493 +705bonus pay for amazing work on #OSS 00010000493 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000494 +705bonus pay for amazing work on #OSS 00010000494 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000495 +705bonus pay for amazing work on #OSS 00010000495 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000496 +705bonus pay for amazing work on #OSS 00010000496 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000497 +705bonus pay for amazing work on #OSS 00010000497 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000498 +705bonus pay for amazing work on #OSS 00010000498 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000499 +705bonus pay for amazing work on #OSS 00010000499 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000500 +705bonus pay for amazing work on #OSS 00010000500 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000501 +705bonus pay for amazing work on #OSS 00010000501 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000502 +705bonus pay for amazing work on #OSS 00010000502 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000503 +705bonus pay for amazing work on #OSS 00010000503 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000504 +705bonus pay for amazing work on #OSS 00010000504 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000505 +705bonus pay for amazing work on #OSS 00010000505 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000506 +705bonus pay for amazing work on #OSS 00010000506 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000507 +705bonus pay for amazing work on #OSS 00010000507 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000508 +705bonus pay for amazing work on #OSS 00010000508 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000509 +705bonus pay for amazing work on #OSS 00010000509 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000510 +705bonus pay for amazing work on #OSS 00010000510 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000511 +705bonus pay for amazing work on #OSS 00010000511 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000512 +705bonus pay for amazing work on #OSS 00010000512 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000513 +705bonus pay for amazing work on #OSS 00010000513 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000514 +705bonus pay for amazing work on #OSS 00010000514 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000515 +705bonus pay for amazing work on #OSS 00010000515 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000516 +705bonus pay for amazing work on #OSS 00010000516 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000517 +705bonus pay for amazing work on #OSS 00010000517 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000518 +705bonus pay for amazing work on #OSS 00010000518 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000519 +705bonus pay for amazing work on #OSS 00010000519 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000520 +705bonus pay for amazing work on #OSS 00010000520 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000521 +705bonus pay for amazing work on #OSS 00010000521 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000522 +705bonus pay for amazing work on #OSS 00010000522 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000523 +705bonus pay for amazing work on #OSS 00010000523 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000524 +705bonus pay for amazing work on #OSS 00010000524 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000525 +705bonus pay for amazing work on #OSS 00010000525 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000526 +705bonus pay for amazing work on #OSS 00010000526 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000527 +705bonus pay for amazing work on #OSS 00010000527 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000528 +705bonus pay for amazing work on #OSS 00010000528 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000529 +705bonus pay for amazing work on #OSS 00010000529 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000530 +705bonus pay for amazing work on #OSS 00010000530 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000531 +705bonus pay for amazing work on #OSS 00010000531 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000532 +705bonus pay for amazing work on #OSS 00010000532 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000533 +705bonus pay for amazing work on #OSS 00010000533 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000534 +705bonus pay for amazing work on #OSS 00010000534 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000535 +705bonus pay for amazing work on #OSS 00010000535 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000536 +705bonus pay for amazing work on #OSS 00010000536 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000537 +705bonus pay for amazing work on #OSS 00010000537 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000538 +705bonus pay for amazing work on #OSS 00010000538 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000539 +705bonus pay for amazing work on #OSS 00010000539 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000540 +705bonus pay for amazing work on #OSS 00010000540 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000541 +705bonus pay for amazing work on #OSS 00010000541 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000542 +705bonus pay for amazing work on #OSS 00010000542 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000543 +705bonus pay for amazing work on #OSS 00010000543 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000544 +705bonus pay for amazing work on #OSS 00010000544 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000545 +705bonus pay for amazing work on #OSS 00010000545 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000546 +705bonus pay for amazing work on #OSS 00010000546 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000547 +705bonus pay for amazing work on #OSS 00010000547 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000548 +705bonus pay for amazing work on #OSS 00010000548 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000549 +705bonus pay for amazing work on #OSS 00010000549 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000550 +705bonus pay for amazing work on #OSS 00010000550 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000551 +705bonus pay for amazing work on #OSS 00010000551 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000552 +705bonus pay for amazing work on #OSS 00010000552 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000553 +705bonus pay for amazing work on #OSS 00010000553 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000554 +705bonus pay for amazing work on #OSS 00010000554 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000555 +705bonus pay for amazing work on #OSS 00010000555 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000556 +705bonus pay for amazing work on #OSS 00010000556 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000557 +705bonus pay for amazing work on #OSS 00010000557 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000558 +705bonus pay for amazing work on #OSS 00010000558 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000559 +705bonus pay for amazing work on #OSS 00010000559 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000560 +705bonus pay for amazing work on #OSS 00010000560 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000561 +705bonus pay for amazing work on #OSS 00010000561 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000562 +705bonus pay for amazing work on #OSS 00010000562 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000563 +705bonus pay for amazing work on #OSS 00010000563 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000564 +705bonus pay for amazing work on #OSS 00010000564 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000565 +705bonus pay for amazing work on #OSS 00010000565 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000566 +705bonus pay for amazing work on #OSS 00010000566 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000567 +705bonus pay for amazing work on #OSS 00010000567 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000568 +705bonus pay for amazing work on #OSS 00010000568 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000569 +705bonus pay for amazing work on #OSS 00010000569 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000570 +705bonus pay for amazing work on #OSS 00010000570 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000571 +705bonus pay for amazing work on #OSS 00010000571 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000572 +705bonus pay for amazing work on #OSS 00010000572 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000573 +705bonus pay for amazing work on #OSS 00010000573 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000574 +705bonus pay for amazing work on #OSS 00010000574 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000575 +705bonus pay for amazing work on #OSS 00010000575 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000576 +705bonus pay for amazing work on #OSS 00010000576 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000577 +705bonus pay for amazing work on #OSS 00010000577 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000578 +705bonus pay for amazing work on #OSS 00010000578 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000579 +705bonus pay for amazing work on #OSS 00010000579 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000580 +705bonus pay for amazing work on #OSS 00010000580 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000581 +705bonus pay for amazing work on #OSS 00010000581 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000582 +705bonus pay for amazing work on #OSS 00010000582 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000583 +705bonus pay for amazing work on #OSS 00010000583 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000584 +705bonus pay for amazing work on #OSS 00010000584 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000585 +705bonus pay for amazing work on #OSS 00010000585 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000586 +705bonus pay for amazing work on #OSS 00010000586 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000587 +705bonus pay for amazing work on #OSS 00010000587 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000588 +705bonus pay for amazing work on #OSS 00010000588 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000589 +705bonus pay for amazing work on #OSS 00010000589 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000590 +705bonus pay for amazing work on #OSS 00010000590 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000591 +705bonus pay for amazing work on #OSS 00010000591 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000592 +705bonus pay for amazing work on #OSS 00010000592 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000593 +705bonus pay for amazing work on #OSS 00010000593 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000594 +705bonus pay for amazing work on #OSS 00010000594 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000595 +705bonus pay for amazing work on #OSS 00010000595 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000596 +705bonus pay for amazing work on #OSS 00010000596 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000597 +705bonus pay for amazing work on #OSS 00010000597 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000598 +705bonus pay for amazing work on #OSS 00010000598 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000599 +705bonus pay for amazing work on #OSS 00010000599 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000600 +705bonus pay for amazing work on #OSS 00010000600 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000601 +705bonus pay for amazing work on #OSS 00010000601 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000602 +705bonus pay for amazing work on #OSS 00010000602 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000603 +705bonus pay for amazing work on #OSS 00010000603 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000604 +705bonus pay for amazing work on #OSS 00010000604 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000605 +705bonus pay for amazing work on #OSS 00010000605 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000606 +705bonus pay for amazing work on #OSS 00010000606 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000607 +705bonus pay for amazing work on #OSS 00010000607 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000608 +705bonus pay for amazing work on #OSS 00010000608 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000609 +705bonus pay for amazing work on #OSS 00010000609 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000610 +705bonus pay for amazing work on #OSS 00010000610 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000611 +705bonus pay for amazing work on #OSS 00010000611 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000612 +705bonus pay for amazing work on #OSS 00010000612 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000613 +705bonus pay for amazing work on #OSS 00010000613 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000614 +705bonus pay for amazing work on #OSS 00010000614 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000615 +705bonus pay for amazing work on #OSS 00010000615 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000616 +705bonus pay for amazing work on #OSS 00010000616 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000617 +705bonus pay for amazing work on #OSS 00010000617 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000618 +705bonus pay for amazing work on #OSS 00010000618 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000619 +705bonus pay for amazing work on #OSS 00010000619 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000620 +705bonus pay for amazing work on #OSS 00010000620 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000621 +705bonus pay for amazing work on #OSS 00010000621 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000622 +705bonus pay for amazing work on #OSS 00010000622 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000623 +705bonus pay for amazing work on #OSS 00010000623 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000624 +705bonus pay for amazing work on #OSS 00010000624 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000625 +705bonus pay for amazing work on #OSS 00010000625 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000626 +705bonus pay for amazing work on #OSS 00010000626 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000627 +705bonus pay for amazing work on #OSS 00010000627 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000628 +705bonus pay for amazing work on #OSS 00010000628 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000629 +705bonus pay for amazing work on #OSS 00010000629 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000630 +705bonus pay for amazing work on #OSS 00010000630 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000631 +705bonus pay for amazing work on #OSS 00010000631 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000632 +705bonus pay for amazing work on #OSS 00010000632 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000633 +705bonus pay for amazing work on #OSS 00010000633 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000634 +705bonus pay for amazing work on #OSS 00010000634 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000635 +705bonus pay for amazing work on #OSS 00010000635 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000636 +705bonus pay for amazing work on #OSS 00010000636 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000637 +705bonus pay for amazing work on #OSS 00010000637 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000638 +705bonus pay for amazing work on #OSS 00010000638 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000639 +705bonus pay for amazing work on #OSS 00010000639 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000640 +705bonus pay for amazing work on #OSS 00010000640 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000641 +705bonus pay for amazing work on #OSS 00010000641 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000642 +705bonus pay for amazing work on #OSS 00010000642 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000643 +705bonus pay for amazing work on #OSS 00010000643 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000644 +705bonus pay for amazing work on #OSS 00010000644 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000645 +705bonus pay for amazing work on #OSS 00010000645 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000646 +705bonus pay for amazing work on #OSS 00010000646 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000647 +705bonus pay for amazing work on #OSS 00010000647 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000648 +705bonus pay for amazing work on #OSS 00010000648 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000649 +705bonus pay for amazing work on #OSS 00010000649 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000650 +705bonus pay for amazing work on #OSS 00010000650 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000651 +705bonus pay for amazing work on #OSS 00010000651 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000652 +705bonus pay for amazing work on #OSS 00010000652 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000653 +705bonus pay for amazing work on #OSS 00010000653 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000654 +705bonus pay for amazing work on #OSS 00010000654 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000655 +705bonus pay for amazing work on #OSS 00010000655 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000656 +705bonus pay for amazing work on #OSS 00010000656 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000657 +705bonus pay for amazing work on #OSS 00010000657 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000658 +705bonus pay for amazing work on #OSS 00010000658 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000659 +705bonus pay for amazing work on #OSS 00010000659 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000660 +705bonus pay for amazing work on #OSS 00010000660 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000661 +705bonus pay for amazing work on #OSS 00010000661 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000662 +705bonus pay for amazing work on #OSS 00010000662 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000663 +705bonus pay for amazing work on #OSS 00010000663 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000664 +705bonus pay for amazing work on #OSS 00010000664 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000665 +705bonus pay for amazing work on #OSS 00010000665 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000666 +705bonus pay for amazing work on #OSS 00010000666 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000667 +705bonus pay for amazing work on #OSS 00010000667 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000668 +705bonus pay for amazing work on #OSS 00010000668 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000669 +705bonus pay for amazing work on #OSS 00010000669 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000670 +705bonus pay for amazing work on #OSS 00010000670 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000671 +705bonus pay for amazing work on #OSS 00010000671 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000672 +705bonus pay for amazing work on #OSS 00010000672 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000673 +705bonus pay for amazing work on #OSS 00010000673 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000674 +705bonus pay for amazing work on #OSS 00010000674 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000675 +705bonus pay for amazing work on #OSS 00010000675 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000676 +705bonus pay for amazing work on #OSS 00010000676 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000677 +705bonus pay for amazing work on #OSS 00010000677 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000678 +705bonus pay for amazing work on #OSS 00010000678 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000679 +705bonus pay for amazing work on #OSS 00010000679 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000680 +705bonus pay for amazing work on #OSS 00010000680 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000681 +705bonus pay for amazing work on #OSS 00010000681 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000682 +705bonus pay for amazing work on #OSS 00010000682 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000683 +705bonus pay for amazing work on #OSS 00010000683 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000684 +705bonus pay for amazing work on #OSS 00010000684 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000685 +705bonus pay for amazing work on #OSS 00010000685 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000686 +705bonus pay for amazing work on #OSS 00010000686 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000687 +705bonus pay for amazing work on #OSS 00010000687 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000688 +705bonus pay for amazing work on #OSS 00010000688 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000689 +705bonus pay for amazing work on #OSS 00010000689 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000690 +705bonus pay for amazing work on #OSS 00010000690 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000691 +705bonus pay for amazing work on #OSS 00010000691 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000692 +705bonus pay for amazing work on #OSS 00010000692 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000693 +705bonus pay for amazing work on #OSS 00010000693 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000694 +705bonus pay for amazing work on #OSS 00010000694 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000695 +705bonus pay for amazing work on #OSS 00010000695 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000696 +705bonus pay for amazing work on #OSS 00010000696 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000697 +705bonus pay for amazing work on #OSS 00010000697 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000698 +705bonus pay for amazing work on #OSS 00010000698 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000699 +705bonus pay for amazing work on #OSS 00010000699 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000700 +705bonus pay for amazing work on #OSS 00010000700 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000701 +705bonus pay for amazing work on #OSS 00010000701 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000702 +705bonus pay for amazing work on #OSS 00010000702 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000703 +705bonus pay for amazing work on #OSS 00010000703 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000704 +705bonus pay for amazing work on #OSS 00010000704 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000705 +705bonus pay for amazing work on #OSS 00010000705 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000706 +705bonus pay for amazing work on #OSS 00010000706 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000707 +705bonus pay for amazing work on #OSS 00010000707 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000708 +705bonus pay for amazing work on #OSS 00010000708 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000709 +705bonus pay for amazing work on #OSS 00010000709 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000710 +705bonus pay for amazing work on #OSS 00010000710 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000711 +705bonus pay for amazing work on #OSS 00010000711 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000712 +705bonus pay for amazing work on #OSS 00010000712 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000713 +705bonus pay for amazing work on #OSS 00010000713 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000714 +705bonus pay for amazing work on #OSS 00010000714 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000715 +705bonus pay for amazing work on #OSS 00010000715 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000716 +705bonus pay for amazing work on #OSS 00010000716 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000717 +705bonus pay for amazing work on #OSS 00010000717 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000718 +705bonus pay for amazing work on #OSS 00010000718 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000719 +705bonus pay for amazing work on #OSS 00010000719 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000720 +705bonus pay for amazing work on #OSS 00010000720 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000721 +705bonus pay for amazing work on #OSS 00010000721 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000722 +705bonus pay for amazing work on #OSS 00010000722 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000723 +705bonus pay for amazing work on #OSS 00010000723 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000724 +705bonus pay for amazing work on #OSS 00010000724 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000725 +705bonus pay for amazing work on #OSS 00010000725 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000726 +705bonus pay for amazing work on #OSS 00010000726 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000727 +705bonus pay for amazing work on #OSS 00010000727 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000728 +705bonus pay for amazing work on #OSS 00010000728 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000729 +705bonus pay for amazing work on #OSS 00010000729 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000730 +705bonus pay for amazing work on #OSS 00010000730 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000731 +705bonus pay for amazing work on #OSS 00010000731 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000732 +705bonus pay for amazing work on #OSS 00010000732 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000733 +705bonus pay for amazing work on #OSS 00010000733 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000734 +705bonus pay for amazing work on #OSS 00010000734 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000735 +705bonus pay for amazing work on #OSS 00010000735 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000736 +705bonus pay for amazing work on #OSS 00010000736 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000737 +705bonus pay for amazing work on #OSS 00010000737 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000738 +705bonus pay for amazing work on #OSS 00010000738 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000739 +705bonus pay for amazing work on #OSS 00010000739 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000740 +705bonus pay for amazing work on #OSS 00010000740 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000741 +705bonus pay for amazing work on #OSS 00010000741 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000742 +705bonus pay for amazing work on #OSS 00010000742 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000743 +705bonus pay for amazing work on #OSS 00010000743 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000744 +705bonus pay for amazing work on #OSS 00010000744 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000745 +705bonus pay for amazing work on #OSS 00010000745 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000746 +705bonus pay for amazing work on #OSS 00010000746 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000747 +705bonus pay for amazing work on #OSS 00010000747 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000748 +705bonus pay for amazing work on #OSS 00010000748 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000749 +705bonus pay for amazing work on #OSS 00010000749 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000750 +705bonus pay for amazing work on #OSS 00010000750 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000751 +705bonus pay for amazing work on #OSS 00010000751 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000752 +705bonus pay for amazing work on #OSS 00010000752 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000753 +705bonus pay for amazing work on #OSS 00010000753 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000754 +705bonus pay for amazing work on #OSS 00010000754 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000755 +705bonus pay for amazing work on #OSS 00010000755 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000756 +705bonus pay for amazing work on #OSS 00010000756 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000757 +705bonus pay for amazing work on #OSS 00010000757 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000758 +705bonus pay for amazing work on #OSS 00010000758 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000759 +705bonus pay for amazing work on #OSS 00010000759 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000760 +705bonus pay for amazing work on #OSS 00010000760 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000761 +705bonus pay for amazing work on #OSS 00010000761 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000762 +705bonus pay for amazing work on #OSS 00010000762 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000763 +705bonus pay for amazing work on #OSS 00010000763 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000764 +705bonus pay for amazing work on #OSS 00010000764 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000765 +705bonus pay for amazing work on #OSS 00010000765 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000766 +705bonus pay for amazing work on #OSS 00010000766 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000767 +705bonus pay for amazing work on #OSS 00010000767 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000768 +705bonus pay for amazing work on #OSS 00010000768 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000769 +705bonus pay for amazing work on #OSS 00010000769 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000770 +705bonus pay for amazing work on #OSS 00010000770 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000771 +705bonus pay for amazing work on #OSS 00010000771 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000772 +705bonus pay for amazing work on #OSS 00010000772 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000773 +705bonus pay for amazing work on #OSS 00010000773 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000774 +705bonus pay for amazing work on #OSS 00010000774 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000775 +705bonus pay for amazing work on #OSS 00010000775 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000776 +705bonus pay for amazing work on #OSS 00010000776 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000777 +705bonus pay for amazing work on #OSS 00010000777 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000778 +705bonus pay for amazing work on #OSS 00010000778 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000779 +705bonus pay for amazing work on #OSS 00010000779 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000780 +705bonus pay for amazing work on #OSS 00010000780 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000781 +705bonus pay for amazing work on #OSS 00010000781 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000782 +705bonus pay for amazing work on #OSS 00010000782 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000783 +705bonus pay for amazing work on #OSS 00010000783 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000784 +705bonus pay for amazing work on #OSS 00010000784 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000785 +705bonus pay for amazing work on #OSS 00010000785 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000786 +705bonus pay for amazing work on #OSS 00010000786 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000787 +705bonus pay for amazing work on #OSS 00010000787 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000788 +705bonus pay for amazing work on #OSS 00010000788 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000789 +705bonus pay for amazing work on #OSS 00010000789 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000790 +705bonus pay for amazing work on #OSS 00010000790 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000791 +705bonus pay for amazing work on #OSS 00010000791 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000792 +705bonus pay for amazing work on #OSS 00010000792 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000793 +705bonus pay for amazing work on #OSS 00010000793 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000794 +705bonus pay for amazing work on #OSS 00010000794 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000795 +705bonus pay for amazing work on #OSS 00010000795 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000796 +705bonus pay for amazing work on #OSS 00010000796 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000797 +705bonus pay for amazing work on #OSS 00010000797 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000798 +705bonus pay for amazing work on #OSS 00010000798 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000799 +705bonus pay for amazing work on #OSS 00010000799 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000800 +705bonus pay for amazing work on #OSS 00010000800 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000801 +705bonus pay for amazing work on #OSS 00010000801 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000802 +705bonus pay for amazing work on #OSS 00010000802 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000803 +705bonus pay for amazing work on #OSS 00010000803 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000804 +705bonus pay for amazing work on #OSS 00010000804 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000805 +705bonus pay for amazing work on #OSS 00010000805 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000806 +705bonus pay for amazing work on #OSS 00010000806 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000807 +705bonus pay for amazing work on #OSS 00010000807 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000808 +705bonus pay for amazing work on #OSS 00010000808 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000809 +705bonus pay for amazing work on #OSS 00010000809 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000810 +705bonus pay for amazing work on #OSS 00010000810 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000811 +705bonus pay for amazing work on #OSS 00010000811 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000812 +705bonus pay for amazing work on #OSS 00010000812 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000813 +705bonus pay for amazing work on #OSS 00010000813 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000814 +705bonus pay for amazing work on #OSS 00010000814 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000815 +705bonus pay for amazing work on #OSS 00010000815 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000816 +705bonus pay for amazing work on #OSS 00010000816 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000817 +705bonus pay for amazing work on #OSS 00010000817 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000818 +705bonus pay for amazing work on #OSS 00010000818 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000819 +705bonus pay for amazing work on #OSS 00010000819 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000820 +705bonus pay for amazing work on #OSS 00010000820 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000821 +705bonus pay for amazing work on #OSS 00010000821 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000822 +705bonus pay for amazing work on #OSS 00010000822 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000823 +705bonus pay for amazing work on #OSS 00010000823 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000824 +705bonus pay for amazing work on #OSS 00010000824 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000825 +705bonus pay for amazing work on #OSS 00010000825 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000826 +705bonus pay for amazing work on #OSS 00010000826 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000827 +705bonus pay for amazing work on #OSS 00010000827 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000828 +705bonus pay for amazing work on #OSS 00010000828 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000829 +705bonus pay for amazing work on #OSS 00010000829 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000830 +705bonus pay for amazing work on #OSS 00010000830 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000831 +705bonus pay for amazing work on #OSS 00010000831 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000832 +705bonus pay for amazing work on #OSS 00010000832 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000833 +705bonus pay for amazing work on #OSS 00010000833 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000834 +705bonus pay for amazing work on #OSS 00010000834 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000835 +705bonus pay for amazing work on #OSS 00010000835 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000836 +705bonus pay for amazing work on #OSS 00010000836 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000837 +705bonus pay for amazing work on #OSS 00010000837 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000838 +705bonus pay for amazing work on #OSS 00010000838 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000839 +705bonus pay for amazing work on #OSS 00010000839 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000840 +705bonus pay for amazing work on #OSS 00010000840 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000841 +705bonus pay for amazing work on #OSS 00010000841 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000842 +705bonus pay for amazing work on #OSS 00010000842 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000843 +705bonus pay for amazing work on #OSS 00010000843 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000844 +705bonus pay for amazing work on #OSS 00010000844 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000845 +705bonus pay for amazing work on #OSS 00010000845 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000846 +705bonus pay for amazing work on #OSS 00010000846 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000847 +705bonus pay for amazing work on #OSS 00010000847 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000848 +705bonus pay for amazing work on #OSS 00010000848 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000849 +705bonus pay for amazing work on #OSS 00010000849 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000850 +705bonus pay for amazing work on #OSS 00010000850 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000851 +705bonus pay for amazing work on #OSS 00010000851 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000852 +705bonus pay for amazing work on #OSS 00010000852 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000853 +705bonus pay for amazing work on #OSS 00010000853 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000854 +705bonus pay for amazing work on #OSS 00010000854 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000855 +705bonus pay for amazing work on #OSS 00010000855 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000856 +705bonus pay for amazing work on #OSS 00010000856 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000857 +705bonus pay for amazing work on #OSS 00010000857 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000858 +705bonus pay for amazing work on #OSS 00010000858 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000859 +705bonus pay for amazing work on #OSS 00010000859 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000860 +705bonus pay for amazing work on #OSS 00010000860 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000861 +705bonus pay for amazing work on #OSS 00010000861 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000862 +705bonus pay for amazing work on #OSS 00010000862 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000863 +705bonus pay for amazing work on #OSS 00010000863 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000864 +705bonus pay for amazing work on #OSS 00010000864 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000865 +705bonus pay for amazing work on #OSS 00010000865 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000866 +705bonus pay for amazing work on #OSS 00010000866 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000867 +705bonus pay for amazing work on #OSS 00010000867 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000868 +705bonus pay for amazing work on #OSS 00010000868 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000869 +705bonus pay for amazing work on #OSS 00010000869 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000870 +705bonus pay for amazing work on #OSS 00010000870 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000871 +705bonus pay for amazing work on #OSS 00010000871 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000872 +705bonus pay for amazing work on #OSS 00010000872 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000873 +705bonus pay for amazing work on #OSS 00010000873 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000874 +705bonus pay for amazing work on #OSS 00010000874 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000875 +705bonus pay for amazing work on #OSS 00010000875 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000876 +705bonus pay for amazing work on #OSS 00010000876 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000877 +705bonus pay for amazing work on #OSS 00010000877 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000878 +705bonus pay for amazing work on #OSS 00010000878 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000879 +705bonus pay for amazing work on #OSS 00010000879 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000880 +705bonus pay for amazing work on #OSS 00010000880 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000881 +705bonus pay for amazing work on #OSS 00010000881 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000882 +705bonus pay for amazing work on #OSS 00010000882 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000883 +705bonus pay for amazing work on #OSS 00010000883 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000884 +705bonus pay for amazing work on #OSS 00010000884 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000885 +705bonus pay for amazing work on #OSS 00010000885 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000886 +705bonus pay for amazing work on #OSS 00010000886 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000887 +705bonus pay for amazing work on #OSS 00010000887 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000888 +705bonus pay for amazing work on #OSS 00010000888 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000889 +705bonus pay for amazing work on #OSS 00010000889 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000890 +705bonus pay for amazing work on #OSS 00010000890 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000891 +705bonus pay for amazing work on #OSS 00010000891 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000892 +705bonus pay for amazing work on #OSS 00010000892 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000893 +705bonus pay for amazing work on #OSS 00010000893 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000894 +705bonus pay for amazing work on #OSS 00010000894 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000895 +705bonus pay for amazing work on #OSS 00010000895 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000896 +705bonus pay for amazing work on #OSS 00010000896 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000897 +705bonus pay for amazing work on #OSS 00010000897 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000898 +705bonus pay for amazing work on #OSS 00010000898 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000899 +705bonus pay for amazing work on #OSS 00010000899 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000900 +705bonus pay for amazing work on #OSS 00010000900 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000901 +705bonus pay for amazing work on #OSS 00010000901 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000902 +705bonus pay for amazing work on #OSS 00010000902 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000903 +705bonus pay for amazing work on #OSS 00010000903 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000904 +705bonus pay for amazing work on #OSS 00010000904 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000905 +705bonus pay for amazing work on #OSS 00010000905 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000906 +705bonus pay for amazing work on #OSS 00010000906 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000907 +705bonus pay for amazing work on #OSS 00010000907 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000908 +705bonus pay for amazing work on #OSS 00010000908 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000909 +705bonus pay for amazing work on #OSS 00010000909 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000910 +705bonus pay for amazing work on #OSS 00010000910 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000911 +705bonus pay for amazing work on #OSS 00010000911 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000912 +705bonus pay for amazing work on #OSS 00010000912 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000913 +705bonus pay for amazing work on #OSS 00010000913 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000914 +705bonus pay for amazing work on #OSS 00010000914 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000915 +705bonus pay for amazing work on #OSS 00010000915 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000916 +705bonus pay for amazing work on #OSS 00010000916 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000917 +705bonus pay for amazing work on #OSS 00010000917 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000918 +705bonus pay for amazing work on #OSS 00010000918 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000919 +705bonus pay for amazing work on #OSS 00010000919 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000920 +705bonus pay for amazing work on #OSS 00010000920 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000921 +705bonus pay for amazing work on #OSS 00010000921 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000922 +705bonus pay for amazing work on #OSS 00010000922 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000923 +705bonus pay for amazing work on #OSS 00010000923 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000924 +705bonus pay for amazing work on #OSS 00010000924 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000925 +705bonus pay for amazing work on #OSS 00010000925 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000926 +705bonus pay for amazing work on #OSS 00010000926 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000927 +705bonus pay for amazing work on #OSS 00010000927 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000928 +705bonus pay for amazing work on #OSS 00010000928 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000929 +705bonus pay for amazing work on #OSS 00010000929 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000930 +705bonus pay for amazing work on #OSS 00010000930 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000931 +705bonus pay for amazing work on #OSS 00010000931 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000932 +705bonus pay for amazing work on #OSS 00010000932 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000933 +705bonus pay for amazing work on #OSS 00010000933 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000934 +705bonus pay for amazing work on #OSS 00010000934 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000935 +705bonus pay for amazing work on #OSS 00010000935 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000936 +705bonus pay for amazing work on #OSS 00010000936 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000937 +705bonus pay for amazing work on #OSS 00010000937 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000938 +705bonus pay for amazing work on #OSS 00010000938 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000939 +705bonus pay for amazing work on #OSS 00010000939 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000940 +705bonus pay for amazing work on #OSS 00010000940 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000941 +705bonus pay for amazing work on #OSS 00010000941 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000942 +705bonus pay for amazing work on #OSS 00010000942 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000943 +705bonus pay for amazing work on #OSS 00010000943 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000944 +705bonus pay for amazing work on #OSS 00010000944 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000945 +705bonus pay for amazing work on #OSS 00010000945 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000946 +705bonus pay for amazing work on #OSS 00010000946 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000947 +705bonus pay for amazing work on #OSS 00010000947 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000948 +705bonus pay for amazing work on #OSS 00010000948 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000949 +705bonus pay for amazing work on #OSS 00010000949 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000950 +705bonus pay for amazing work on #OSS 00010000950 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000951 +705bonus pay for amazing work on #OSS 00010000951 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000952 +705bonus pay for amazing work on #OSS 00010000952 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000953 +705bonus pay for amazing work on #OSS 00010000953 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000954 +705bonus pay for amazing work on #OSS 00010000954 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000955 +705bonus pay for amazing work on #OSS 00010000955 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000956 +705bonus pay for amazing work on #OSS 00010000956 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000957 +705bonus pay for amazing work on #OSS 00010000957 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000958 +705bonus pay for amazing work on #OSS 00010000958 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000959 +705bonus pay for amazing work on #OSS 00010000959 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000960 +705bonus pay for amazing work on #OSS 00010000960 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000961 +705bonus pay for amazing work on #OSS 00010000961 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000962 +705bonus pay for amazing work on #OSS 00010000962 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000963 +705bonus pay for amazing work on #OSS 00010000963 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000964 +705bonus pay for amazing work on #OSS 00010000964 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000965 +705bonus pay for amazing work on #OSS 00010000965 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000966 +705bonus pay for amazing work on #OSS 00010000966 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000967 +705bonus pay for amazing work on #OSS 00010000967 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000968 +705bonus pay for amazing work on #OSS 00010000968 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000969 +705bonus pay for amazing work on #OSS 00010000969 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000970 +705bonus pay for amazing work on #OSS 00010000970 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000971 +705bonus pay for amazing work on #OSS 00010000971 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000972 +705bonus pay for amazing work on #OSS 00010000972 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000973 +705bonus pay for amazing work on #OSS 00010000973 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000974 +705bonus pay for amazing work on #OSS 00010000974 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000975 +705bonus pay for amazing work on #OSS 00010000975 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000976 +705bonus pay for amazing work on #OSS 00010000976 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000977 +705bonus pay for amazing work on #OSS 00010000977 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000978 +705bonus pay for amazing work on #OSS 00010000978 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000979 +705bonus pay for amazing work on #OSS 00010000979 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000980 +705bonus pay for amazing work on #OSS 00010000980 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000981 +705bonus pay for amazing work on #OSS 00010000981 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000982 +705bonus pay for amazing work on #OSS 00010000982 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000983 +705bonus pay for amazing work on #OSS 00010000983 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000984 +705bonus pay for amazing work on #OSS 00010000984 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000985 +705bonus pay for amazing work on #OSS 00010000985 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000986 +705bonus pay for amazing work on #OSS 00010000986 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000987 +705bonus pay for amazing work on #OSS 00010000987 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000988 +705bonus pay for amazing work on #OSS 00010000988 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000989 +705bonus pay for amazing work on #OSS 00010000989 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000990 +705bonus pay for amazing work on #OSS 00010000990 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000991 +705bonus pay for amazing work on #OSS 00010000991 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000992 +705bonus pay for amazing work on #OSS 00010000992 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000993 +705bonus pay for amazing work on #OSS 00010000993 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000994 +705bonus pay for amazing work on #OSS 00010000994 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000995 +705bonus pay for amazing work on #OSS 00010000995 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000996 +705bonus pay for amazing work on #OSS 00010000996 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000997 +705bonus pay for amazing work on #OSS 00010000997 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000998 +705bonus pay for amazing work on #OSS 00010000998 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000999 +705bonus pay for amazing work on #OSS 00010000999 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001000 +705bonus pay for amazing work on #OSS 00010001000 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001001 +705bonus pay for amazing work on #OSS 00010001001 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001002 +705bonus pay for amazing work on #OSS 00010001002 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001003 +705bonus pay for amazing work on #OSS 00010001003 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001004 +705bonus pay for amazing work on #OSS 00010001004 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001005 +705bonus pay for amazing work on #OSS 00010001005 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001006 +705bonus pay for amazing work on #OSS 00010001006 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001007 +705bonus pay for amazing work on #OSS 00010001007 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001008 +705bonus pay for amazing work on #OSS 00010001008 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001009 +705bonus pay for amazing work on #OSS 00010001009 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001010 +705bonus pay for amazing work on #OSS 00010001010 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001011 +705bonus pay for amazing work on #OSS 00010001011 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001012 +705bonus pay for amazing work on #OSS 00010001012 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001013 +705bonus pay for amazing work on #OSS 00010001013 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001014 +705bonus pay for amazing work on #OSS 00010001014 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001015 +705bonus pay for amazing work on #OSS 00010001015 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001016 +705bonus pay for amazing work on #OSS 00010001016 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001017 +705bonus pay for amazing work on #OSS 00010001017 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001018 +705bonus pay for amazing work on #OSS 00010001018 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001019 +705bonus pay for amazing work on #OSS 00010001019 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001020 +705bonus pay for amazing work on #OSS 00010001020 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001021 +705bonus pay for amazing work on #OSS 00010001021 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001022 +705bonus pay for amazing work on #OSS 00010001022 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001023 +705bonus pay for amazing work on #OSS 00010001023 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001024 +705bonus pay for amazing work on #OSS 00010001024 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001025 +705bonus pay for amazing work on #OSS 00010001025 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001026 +705bonus pay for amazing work on #OSS 00010001026 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001027 +705bonus pay for amazing work on #OSS 00010001027 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001028 +705bonus pay for amazing work on #OSS 00010001028 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001029 +705bonus pay for amazing work on #OSS 00010001029 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001030 +705bonus pay for amazing work on #OSS 00010001030 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001031 +705bonus pay for amazing work on #OSS 00010001031 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001032 +705bonus pay for amazing work on #OSS 00010001032 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001033 +705bonus pay for amazing work on #OSS 00010001033 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001034 +705bonus pay for amazing work on #OSS 00010001034 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001035 +705bonus pay for amazing work on #OSS 00010001035 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001036 +705bonus pay for amazing work on #OSS 00010001036 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001037 +705bonus pay for amazing work on #OSS 00010001037 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001038 +705bonus pay for amazing work on #OSS 00010001038 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001039 +705bonus pay for amazing work on #OSS 00010001039 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001040 +705bonus pay for amazing work on #OSS 00010001040 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001041 +705bonus pay for amazing work on #OSS 00010001041 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001042 +705bonus pay for amazing work on #OSS 00010001042 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001043 +705bonus pay for amazing work on #OSS 00010001043 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001044 +705bonus pay for amazing work on #OSS 00010001044 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001045 +705bonus pay for amazing work on #OSS 00010001045 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001046 +705bonus pay for amazing work on #OSS 00010001046 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001047 +705bonus pay for amazing work on #OSS 00010001047 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001048 +705bonus pay for amazing work on #OSS 00010001048 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001049 +705bonus pay for amazing work on #OSS 00010001049 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001050 +705bonus pay for amazing work on #OSS 00010001050 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001051 +705bonus pay for amazing work on #OSS 00010001051 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001052 +705bonus pay for amazing work on #OSS 00010001052 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001053 +705bonus pay for amazing work on #OSS 00010001053 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001054 +705bonus pay for amazing work on #OSS 00010001054 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001055 +705bonus pay for amazing work on #OSS 00010001055 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001056 +705bonus pay for amazing work on #OSS 00010001056 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001057 +705bonus pay for amazing work on #OSS 00010001057 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001058 +705bonus pay for amazing work on #OSS 00010001058 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001059 +705bonus pay for amazing work on #OSS 00010001059 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001060 +705bonus pay for amazing work on #OSS 00010001060 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001061 +705bonus pay for amazing work on #OSS 00010001061 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001062 +705bonus pay for amazing work on #OSS 00010001062 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001063 +705bonus pay for amazing work on #OSS 00010001063 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001064 +705bonus pay for amazing work on #OSS 00010001064 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001065 +705bonus pay for amazing work on #OSS 00010001065 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001066 +705bonus pay for amazing work on #OSS 00010001066 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001067 +705bonus pay for amazing work on #OSS 00010001067 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001068 +705bonus pay for amazing work on #OSS 00010001068 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001069 +705bonus pay for amazing work on #OSS 00010001069 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001070 +705bonus pay for amazing work on #OSS 00010001070 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001071 +705bonus pay for amazing work on #OSS 00010001071 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001072 +705bonus pay for amazing work on #OSS 00010001072 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001073 +705bonus pay for amazing work on #OSS 00010001073 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001074 +705bonus pay for amazing work on #OSS 00010001074 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001075 +705bonus pay for amazing work on #OSS 00010001075 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001076 +705bonus pay for amazing work on #OSS 00010001076 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001077 +705bonus pay for amazing work on #OSS 00010001077 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001078 +705bonus pay for amazing work on #OSS 00010001078 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001079 +705bonus pay for amazing work on #OSS 00010001079 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001080 +705bonus pay for amazing work on #OSS 00010001080 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001081 +705bonus pay for amazing work on #OSS 00010001081 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001082 +705bonus pay for amazing work on #OSS 00010001082 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001083 +705bonus pay for amazing work on #OSS 00010001083 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001084 +705bonus pay for amazing work on #OSS 00010001084 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001085 +705bonus pay for amazing work on #OSS 00010001085 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001086 +705bonus pay for amazing work on #OSS 00010001086 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001087 +705bonus pay for amazing work on #OSS 00010001087 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001088 +705bonus pay for amazing work on #OSS 00010001088 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001089 +705bonus pay for amazing work on #OSS 00010001089 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001090 +705bonus pay for amazing work on #OSS 00010001090 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001091 +705bonus pay for amazing work on #OSS 00010001091 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001092 +705bonus pay for amazing work on #OSS 00010001092 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001093 +705bonus pay for amazing work on #OSS 00010001093 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001094 +705bonus pay for amazing work on #OSS 00010001094 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001095 +705bonus pay for amazing work on #OSS 00010001095 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001096 +705bonus pay for amazing work on #OSS 00010001096 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001097 +705bonus pay for amazing work on #OSS 00010001097 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001098 +705bonus pay for amazing work on #OSS 00010001098 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001099 +705bonus pay for amazing work on #OSS 00010001099 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001100 +705bonus pay for amazing work on #OSS 00010001100 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001101 +705bonus pay for amazing work on #OSS 00010001101 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001102 +705bonus pay for amazing work on #OSS 00010001102 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001103 +705bonus pay for amazing work on #OSS 00010001103 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001104 +705bonus pay for amazing work on #OSS 00010001104 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001105 +705bonus pay for amazing work on #OSS 00010001105 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001106 +705bonus pay for amazing work on #OSS 00010001106 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001107 +705bonus pay for amazing work on #OSS 00010001107 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001108 +705bonus pay for amazing work on #OSS 00010001108 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001109 +705bonus pay for amazing work on #OSS 00010001109 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001110 +705bonus pay for amazing work on #OSS 00010001110 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001111 +705bonus pay for amazing work on #OSS 00010001111 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001112 +705bonus pay for amazing work on #OSS 00010001112 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001113 +705bonus pay for amazing work on #OSS 00010001113 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001114 +705bonus pay for amazing work on #OSS 00010001114 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001115 +705bonus pay for amazing work on #OSS 00010001115 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001116 +705bonus pay for amazing work on #OSS 00010001116 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001117 +705bonus pay for amazing work on #OSS 00010001117 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001118 +705bonus pay for amazing work on #OSS 00010001118 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001119 +705bonus pay for amazing work on #OSS 00010001119 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001120 +705bonus pay for amazing work on #OSS 00010001120 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001121 +705bonus pay for amazing work on #OSS 00010001121 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001122 +705bonus pay for amazing work on #OSS 00010001122 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001123 +705bonus pay for amazing work on #OSS 00010001123 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001124 +705bonus pay for amazing work on #OSS 00010001124 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001125 +705bonus pay for amazing work on #OSS 00010001125 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001126 +705bonus pay for amazing work on #OSS 00010001126 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001127 +705bonus pay for amazing work on #OSS 00010001127 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001128 +705bonus pay for amazing work on #OSS 00010001128 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001129 +705bonus pay for amazing work on #OSS 00010001129 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001130 +705bonus pay for amazing work on #OSS 00010001130 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001131 +705bonus pay for amazing work on #OSS 00010001131 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001132 +705bonus pay for amazing work on #OSS 00010001132 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001133 +705bonus pay for amazing work on #OSS 00010001133 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001134 +705bonus pay for amazing work on #OSS 00010001134 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001135 +705bonus pay for amazing work on #OSS 00010001135 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001136 +705bonus pay for amazing work on #OSS 00010001136 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001137 +705bonus pay for amazing work on #OSS 00010001137 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001138 +705bonus pay for amazing work on #OSS 00010001138 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001139 +705bonus pay for amazing work on #OSS 00010001139 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001140 +705bonus pay for amazing work on #OSS 00010001140 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001141 +705bonus pay for amazing work on #OSS 00010001141 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001142 +705bonus pay for amazing work on #OSS 00010001142 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001143 +705bonus pay for amazing work on #OSS 00010001143 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001144 +705bonus pay for amazing work on #OSS 00010001144 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001145 +705bonus pay for amazing work on #OSS 00010001145 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001146 +705bonus pay for amazing work on #OSS 00010001146 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001147 +705bonus pay for amazing work on #OSS 00010001147 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001148 +705bonus pay for amazing work on #OSS 00010001148 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001149 +705bonus pay for amazing work on #OSS 00010001149 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001150 +705bonus pay for amazing work on #OSS 00010001150 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001151 +705bonus pay for amazing work on #OSS 00010001151 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001152 +705bonus pay for amazing work on #OSS 00010001152 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001153 +705bonus pay for amazing work on #OSS 00010001153 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001154 +705bonus pay for amazing work on #OSS 00010001154 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001155 +705bonus pay for amazing work on #OSS 00010001155 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001156 +705bonus pay for amazing work on #OSS 00010001156 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001157 +705bonus pay for amazing work on #OSS 00010001157 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001158 +705bonus pay for amazing work on #OSS 00010001158 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001159 +705bonus pay for amazing work on #OSS 00010001159 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001160 +705bonus pay for amazing work on #OSS 00010001160 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001161 +705bonus pay for amazing work on #OSS 00010001161 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001162 +705bonus pay for amazing work on #OSS 00010001162 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001163 +705bonus pay for amazing work on #OSS 00010001163 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001164 +705bonus pay for amazing work on #OSS 00010001164 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001165 +705bonus pay for amazing work on #OSS 00010001165 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001166 +705bonus pay for amazing work on #OSS 00010001166 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001167 +705bonus pay for amazing work on #OSS 00010001167 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001168 +705bonus pay for amazing work on #OSS 00010001168 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001169 +705bonus pay for amazing work on #OSS 00010001169 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001170 +705bonus pay for amazing work on #OSS 00010001170 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001171 +705bonus pay for amazing work on #OSS 00010001171 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001172 +705bonus pay for amazing work on #OSS 00010001172 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001173 +705bonus pay for amazing work on #OSS 00010001173 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001174 +705bonus pay for amazing work on #OSS 00010001174 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001175 +705bonus pay for amazing work on #OSS 00010001175 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001176 +705bonus pay for amazing work on #OSS 00010001176 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001177 +705bonus pay for amazing work on #OSS 00010001177 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001178 +705bonus pay for amazing work on #OSS 00010001178 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001179 +705bonus pay for amazing work on #OSS 00010001179 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001180 +705bonus pay for amazing work on #OSS 00010001180 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001181 +705bonus pay for amazing work on #OSS 00010001181 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001182 +705bonus pay for amazing work on #OSS 00010001182 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001183 +705bonus pay for amazing work on #OSS 00010001183 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001184 +705bonus pay for amazing work on #OSS 00010001184 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001185 +705bonus pay for amazing work on #OSS 00010001185 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001186 +705bonus pay for amazing work on #OSS 00010001186 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001187 +705bonus pay for amazing work on #OSS 00010001187 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001188 +705bonus pay for amazing work on #OSS 00010001188 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001189 +705bonus pay for amazing work on #OSS 00010001189 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001190 +705bonus pay for amazing work on #OSS 00010001190 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001191 +705bonus pay for amazing work on #OSS 00010001191 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001192 +705bonus pay for amazing work on #OSS 00010001192 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001193 +705bonus pay for amazing work on #OSS 00010001193 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001194 +705bonus pay for amazing work on #OSS 00010001194 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001195 +705bonus pay for amazing work on #OSS 00010001195 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001196 +705bonus pay for amazing work on #OSS 00010001196 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001197 +705bonus pay for amazing work on #OSS 00010001197 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001198 +705bonus pay for amazing work on #OSS 00010001198 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001199 +705bonus pay for amazing work on #OSS 00010001199 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001200 +705bonus pay for amazing work on #OSS 00010001200 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001201 +705bonus pay for amazing work on #OSS 00010001201 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001202 +705bonus pay for amazing work on #OSS 00010001202 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001203 +705bonus pay for amazing work on #OSS 00010001203 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001204 +705bonus pay for amazing work on #OSS 00010001204 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001205 +705bonus pay for amazing work on #OSS 00010001205 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001206 +705bonus pay for amazing work on #OSS 00010001206 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001207 +705bonus pay for amazing work on #OSS 00010001207 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001208 +705bonus pay for amazing work on #OSS 00010001208 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001209 +705bonus pay for amazing work on #OSS 00010001209 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001210 +705bonus pay for amazing work on #OSS 00010001210 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001211 +705bonus pay for amazing work on #OSS 00010001211 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001212 +705bonus pay for amazing work on #OSS 00010001212 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001213 +705bonus pay for amazing work on #OSS 00010001213 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001214 +705bonus pay for amazing work on #OSS 00010001214 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001215 +705bonus pay for amazing work on #OSS 00010001215 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001216 +705bonus pay for amazing work on #OSS 00010001216 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001217 +705bonus pay for amazing work on #OSS 00010001217 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001218 +705bonus pay for amazing work on #OSS 00010001218 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001219 +705bonus pay for amazing work on #OSS 00010001219 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001220 +705bonus pay for amazing work on #OSS 00010001220 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001221 +705bonus pay for amazing work on #OSS 00010001221 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001222 +705bonus pay for amazing work on #OSS 00010001222 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001223 +705bonus pay for amazing work on #OSS 00010001223 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001224 +705bonus pay for amazing work on #OSS 00010001224 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001225 +705bonus pay for amazing work on #OSS 00010001225 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001226 +705bonus pay for amazing work on #OSS 00010001226 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001227 +705bonus pay for amazing work on #OSS 00010001227 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001228 +705bonus pay for amazing work on #OSS 00010001228 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001229 +705bonus pay for amazing work on #OSS 00010001229 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001230 +705bonus pay for amazing work on #OSS 00010001230 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001231 +705bonus pay for amazing work on #OSS 00010001231 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001232 +705bonus pay for amazing work on #OSS 00010001232 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001233 +705bonus pay for amazing work on #OSS 00010001233 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001234 +705bonus pay for amazing work on #OSS 00010001234 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001235 +705bonus pay for amazing work on #OSS 00010001235 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001236 +705bonus pay for amazing work on #OSS 00010001236 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001237 +705bonus pay for amazing work on #OSS 00010001237 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001238 +705bonus pay for amazing work on #OSS 00010001238 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001239 +705bonus pay for amazing work on #OSS 00010001239 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001240 +705bonus pay for amazing work on #OSS 00010001240 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001241 +705bonus pay for amazing work on #OSS 00010001241 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001242 +705bonus pay for amazing work on #OSS 00010001242 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001243 +705bonus pay for amazing work on #OSS 00010001243 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001244 +705bonus pay for amazing work on #OSS 00010001244 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001245 +705bonus pay for amazing work on #OSS 00010001245 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001246 +705bonus pay for amazing work on #OSS 00010001246 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001247 +705bonus pay for amazing work on #OSS 00010001247 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001248 +705bonus pay for amazing work on #OSS 00010001248 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001249 +705bonus pay for amazing work on #OSS 00010001249 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001250 +705bonus pay for amazing work on #OSS 00010001250 +82000025008922512500000000000000000125000000121042882 121042880000002 +5200Wells Fargo 121042882 PPDTrans. Des 190301 1121042880000003 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000001 +705bonus pay for amazing work on #OSS 00010000001 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000002 +705bonus pay for amazing work on #OSS 00010000002 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000003 +705bonus pay for amazing work on #OSS 00010000003 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000004 +705bonus pay for amazing work on #OSS 00010000004 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000005 +705bonus pay for amazing work on #OSS 00010000005 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000006 +705bonus pay for amazing work on #OSS 00010000006 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000007 +705bonus pay for amazing work on #OSS 00010000007 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000008 +705bonus pay for amazing work on #OSS 00010000008 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000009 +705bonus pay for amazing work on #OSS 00010000009 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000010 +705bonus pay for amazing work on #OSS 00010000010 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000011 +705bonus pay for amazing work on #OSS 00010000011 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000012 +705bonus pay for amazing work on #OSS 00010000012 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000013 +705bonus pay for amazing work on #OSS 00010000013 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000014 +705bonus pay for amazing work on #OSS 00010000014 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000015 +705bonus pay for amazing work on #OSS 00010000015 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000016 +705bonus pay for amazing work on #OSS 00010000016 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000017 +705bonus pay for amazing work on #OSS 00010000017 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000018 +705bonus pay for amazing work on #OSS 00010000018 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000019 +705bonus pay for amazing work on #OSS 00010000019 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000020 +705bonus pay for amazing work on #OSS 00010000020 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000021 +705bonus pay for amazing work on #OSS 00010000021 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000022 +705bonus pay for amazing work on #OSS 00010000022 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000023 +705bonus pay for amazing work on #OSS 00010000023 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000024 +705bonus pay for amazing work on #OSS 00010000024 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000025 +705bonus pay for amazing work on #OSS 00010000025 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000026 +705bonus pay for amazing work on #OSS 00010000026 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000027 +705bonus pay for amazing work on #OSS 00010000027 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000028 +705bonus pay for amazing work on #OSS 00010000028 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000029 +705bonus pay for amazing work on #OSS 00010000029 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000030 +705bonus pay for amazing work on #OSS 00010000030 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000031 +705bonus pay for amazing work on #OSS 00010000031 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000032 +705bonus pay for amazing work on #OSS 00010000032 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000033 +705bonus pay for amazing work on #OSS 00010000033 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000034 +705bonus pay for amazing work on #OSS 00010000034 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000035 +705bonus pay for amazing work on #OSS 00010000035 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000036 +705bonus pay for amazing work on #OSS 00010000036 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000037 +705bonus pay for amazing work on #OSS 00010000037 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000038 +705bonus pay for amazing work on #OSS 00010000038 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000039 +705bonus pay for amazing work on #OSS 00010000039 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000040 +705bonus pay for amazing work on #OSS 00010000040 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000041 +705bonus pay for amazing work on #OSS 00010000041 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000042 +705bonus pay for amazing work on #OSS 00010000042 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000043 +705bonus pay for amazing work on #OSS 00010000043 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000044 +705bonus pay for amazing work on #OSS 00010000044 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000045 +705bonus pay for amazing work on #OSS 00010000045 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000046 +705bonus pay for amazing work on #OSS 00010000046 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000047 +705bonus pay for amazing work on #OSS 00010000047 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000048 +705bonus pay for amazing work on #OSS 00010000048 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000049 +705bonus pay for amazing work on #OSS 00010000049 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000050 +705bonus pay for amazing work on #OSS 00010000050 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000051 +705bonus pay for amazing work on #OSS 00010000051 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000052 +705bonus pay for amazing work on #OSS 00010000052 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000053 +705bonus pay for amazing work on #OSS 00010000053 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000054 +705bonus pay for amazing work on #OSS 00010000054 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000055 +705bonus pay for amazing work on #OSS 00010000055 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000056 +705bonus pay for amazing work on #OSS 00010000056 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000057 +705bonus pay for amazing work on #OSS 00010000057 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000058 +705bonus pay for amazing work on #OSS 00010000058 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000059 +705bonus pay for amazing work on #OSS 00010000059 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000060 +705bonus pay for amazing work on #OSS 00010000060 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000061 +705bonus pay for amazing work on #OSS 00010000061 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000062 +705bonus pay for amazing work on #OSS 00010000062 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000063 +705bonus pay for amazing work on #OSS 00010000063 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000064 +705bonus pay for amazing work on #OSS 00010000064 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000065 +705bonus pay for amazing work on #OSS 00010000065 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000066 +705bonus pay for amazing work on #OSS 00010000066 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000067 +705bonus pay for amazing work on #OSS 00010000067 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000068 +705bonus pay for amazing work on #OSS 00010000068 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000069 +705bonus pay for amazing work on #OSS 00010000069 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000070 +705bonus pay for amazing work on #OSS 00010000070 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000071 +705bonus pay for amazing work on #OSS 00010000071 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000072 +705bonus pay for amazing work on #OSS 00010000072 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000073 +705bonus pay for amazing work on #OSS 00010000073 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000074 +705bonus pay for amazing work on #OSS 00010000074 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000075 +705bonus pay for amazing work on #OSS 00010000075 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000076 +705bonus pay for amazing work on #OSS 00010000076 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000077 +705bonus pay for amazing work on #OSS 00010000077 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000078 +705bonus pay for amazing work on #OSS 00010000078 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000079 +705bonus pay for amazing work on #OSS 00010000079 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000080 +705bonus pay for amazing work on #OSS 00010000080 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000081 +705bonus pay for amazing work on #OSS 00010000081 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000082 +705bonus pay for amazing work on #OSS 00010000082 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000083 +705bonus pay for amazing work on #OSS 00010000083 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000084 +705bonus pay for amazing work on #OSS 00010000084 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000085 +705bonus pay for amazing work on #OSS 00010000085 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000086 +705bonus pay for amazing work on #OSS 00010000086 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000087 +705bonus pay for amazing work on #OSS 00010000087 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000088 +705bonus pay for amazing work on #OSS 00010000088 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000089 +705bonus pay for amazing work on #OSS 00010000089 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000090 +705bonus pay for amazing work on #OSS 00010000090 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000091 +705bonus pay for amazing work on #OSS 00010000091 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000092 +705bonus pay for amazing work on #OSS 00010000092 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000093 +705bonus pay for amazing work on #OSS 00010000093 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000094 +705bonus pay for amazing work on #OSS 00010000094 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000095 +705bonus pay for amazing work on #OSS 00010000095 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000096 +705bonus pay for amazing work on #OSS 00010000096 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000097 +705bonus pay for amazing work on #OSS 00010000097 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000098 +705bonus pay for amazing work on #OSS 00010000098 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000099 +705bonus pay for amazing work on #OSS 00010000099 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000100 +705bonus pay for amazing work on #OSS 00010000100 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000101 +705bonus pay for amazing work on #OSS 00010000101 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000102 +705bonus pay for amazing work on #OSS 00010000102 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000103 +705bonus pay for amazing work on #OSS 00010000103 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000104 +705bonus pay for amazing work on #OSS 00010000104 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000105 +705bonus pay for amazing work on #OSS 00010000105 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000106 +705bonus pay for amazing work on #OSS 00010000106 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000107 +705bonus pay for amazing work on #OSS 00010000107 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000108 +705bonus pay for amazing work on #OSS 00010000108 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000109 +705bonus pay for amazing work on #OSS 00010000109 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000110 +705bonus pay for amazing work on #OSS 00010000110 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000111 +705bonus pay for amazing work on #OSS 00010000111 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000112 +705bonus pay for amazing work on #OSS 00010000112 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000113 +705bonus pay for amazing work on #OSS 00010000113 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000114 +705bonus pay for amazing work on #OSS 00010000114 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000115 +705bonus pay for amazing work on #OSS 00010000115 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000116 +705bonus pay for amazing work on #OSS 00010000116 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000117 +705bonus pay for amazing work on #OSS 00010000117 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000118 +705bonus pay for amazing work on #OSS 00010000118 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000119 +705bonus pay for amazing work on #OSS 00010000119 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000120 +705bonus pay for amazing work on #OSS 00010000120 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000121 +705bonus pay for amazing work on #OSS 00010000121 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000122 +705bonus pay for amazing work on #OSS 00010000122 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000123 +705bonus pay for amazing work on #OSS 00010000123 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000124 +705bonus pay for amazing work on #OSS 00010000124 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000125 +705bonus pay for amazing work on #OSS 00010000125 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000126 +705bonus pay for amazing work on #OSS 00010000126 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000127 +705bonus pay for amazing work on #OSS 00010000127 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000128 +705bonus pay for amazing work on #OSS 00010000128 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000129 +705bonus pay for amazing work on #OSS 00010000129 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000130 +705bonus pay for amazing work on #OSS 00010000130 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000131 +705bonus pay for amazing work on #OSS 00010000131 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000132 +705bonus pay for amazing work on #OSS 00010000132 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000133 +705bonus pay for amazing work on #OSS 00010000133 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000134 +705bonus pay for amazing work on #OSS 00010000134 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000135 +705bonus pay for amazing work on #OSS 00010000135 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000136 +705bonus pay for amazing work on #OSS 00010000136 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000137 +705bonus pay for amazing work on #OSS 00010000137 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000138 +705bonus pay for amazing work on #OSS 00010000138 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000139 +705bonus pay for amazing work on #OSS 00010000139 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000140 +705bonus pay for amazing work on #OSS 00010000140 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000141 +705bonus pay for amazing work on #OSS 00010000141 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000142 +705bonus pay for amazing work on #OSS 00010000142 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000143 +705bonus pay for amazing work on #OSS 00010000143 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000144 +705bonus pay for amazing work on #OSS 00010000144 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000145 +705bonus pay for amazing work on #OSS 00010000145 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000146 +705bonus pay for amazing work on #OSS 00010000146 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000147 +705bonus pay for amazing work on #OSS 00010000147 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000148 +705bonus pay for amazing work on #OSS 00010000148 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000149 +705bonus pay for amazing work on #OSS 00010000149 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000150 +705bonus pay for amazing work on #OSS 00010000150 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000151 +705bonus pay for amazing work on #OSS 00010000151 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000152 +705bonus pay for amazing work on #OSS 00010000152 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000153 +705bonus pay for amazing work on #OSS 00010000153 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000154 +705bonus pay for amazing work on #OSS 00010000154 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000155 +705bonus pay for amazing work on #OSS 00010000155 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000156 +705bonus pay for amazing work on #OSS 00010000156 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000157 +705bonus pay for amazing work on #OSS 00010000157 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000158 +705bonus pay for amazing work on #OSS 00010000158 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000159 +705bonus pay for amazing work on #OSS 00010000159 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000160 +705bonus pay for amazing work on #OSS 00010000160 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000161 +705bonus pay for amazing work on #OSS 00010000161 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000162 +705bonus pay for amazing work on #OSS 00010000162 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000163 +705bonus pay for amazing work on #OSS 00010000163 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000164 +705bonus pay for amazing work on #OSS 00010000164 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000165 +705bonus pay for amazing work on #OSS 00010000165 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000166 +705bonus pay for amazing work on #OSS 00010000166 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000167 +705bonus pay for amazing work on #OSS 00010000167 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000168 +705bonus pay for amazing work on #OSS 00010000168 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000169 +705bonus pay for amazing work on #OSS 00010000169 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000170 +705bonus pay for amazing work on #OSS 00010000170 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000171 +705bonus pay for amazing work on #OSS 00010000171 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000172 +705bonus pay for amazing work on #OSS 00010000172 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000173 +705bonus pay for amazing work on #OSS 00010000173 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000174 +705bonus pay for amazing work on #OSS 00010000174 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000175 +705bonus pay for amazing work on #OSS 00010000175 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000176 +705bonus pay for amazing work on #OSS 00010000176 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000177 +705bonus pay for amazing work on #OSS 00010000177 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000178 +705bonus pay for amazing work on #OSS 00010000178 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000179 +705bonus pay for amazing work on #OSS 00010000179 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000180 +705bonus pay for amazing work on #OSS 00010000180 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000181 +705bonus pay for amazing work on #OSS 00010000181 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000182 +705bonus pay for amazing work on #OSS 00010000182 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000183 +705bonus pay for amazing work on #OSS 00010000183 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000184 +705bonus pay for amazing work on #OSS 00010000184 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000185 +705bonus pay for amazing work on #OSS 00010000185 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000186 +705bonus pay for amazing work on #OSS 00010000186 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000187 +705bonus pay for amazing work on #OSS 00010000187 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000188 +705bonus pay for amazing work on #OSS 00010000188 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000189 +705bonus pay for amazing work on #OSS 00010000189 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000190 +705bonus pay for amazing work on #OSS 00010000190 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000191 +705bonus pay for amazing work on #OSS 00010000191 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000192 +705bonus pay for amazing work on #OSS 00010000192 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000193 +705bonus pay for amazing work on #OSS 00010000193 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000194 +705bonus pay for amazing work on #OSS 00010000194 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000195 +705bonus pay for amazing work on #OSS 00010000195 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000196 +705bonus pay for amazing work on #OSS 00010000196 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000197 +705bonus pay for amazing work on #OSS 00010000197 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000198 +705bonus pay for amazing work on #OSS 00010000198 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000199 +705bonus pay for amazing work on #OSS 00010000199 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000200 +705bonus pay for amazing work on #OSS 00010000200 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000201 +705bonus pay for amazing work on #OSS 00010000201 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000202 +705bonus pay for amazing work on #OSS 00010000202 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000203 +705bonus pay for amazing work on #OSS 00010000203 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000204 +705bonus pay for amazing work on #OSS 00010000204 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000205 +705bonus pay for amazing work on #OSS 00010000205 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000206 +705bonus pay for amazing work on #OSS 00010000206 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000207 +705bonus pay for amazing work on #OSS 00010000207 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000208 +705bonus pay for amazing work on #OSS 00010000208 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000209 +705bonus pay for amazing work on #OSS 00010000209 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000210 +705bonus pay for amazing work on #OSS 00010000210 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000211 +705bonus pay for amazing work on #OSS 00010000211 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000212 +705bonus pay for amazing work on #OSS 00010000212 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000213 +705bonus pay for amazing work on #OSS 00010000213 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000214 +705bonus pay for amazing work on #OSS 00010000214 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000215 +705bonus pay for amazing work on #OSS 00010000215 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000216 +705bonus pay for amazing work on #OSS 00010000216 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000217 +705bonus pay for amazing work on #OSS 00010000217 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000218 +705bonus pay for amazing work on #OSS 00010000218 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000219 +705bonus pay for amazing work on #OSS 00010000219 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000220 +705bonus pay for amazing work on #OSS 00010000220 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000221 +705bonus pay for amazing work on #OSS 00010000221 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000222 +705bonus pay for amazing work on #OSS 00010000222 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000223 +705bonus pay for amazing work on #OSS 00010000223 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000224 +705bonus pay for amazing work on #OSS 00010000224 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000225 +705bonus pay for amazing work on #OSS 00010000225 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000226 +705bonus pay for amazing work on #OSS 00010000226 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000227 +705bonus pay for amazing work on #OSS 00010000227 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000228 +705bonus pay for amazing work on #OSS 00010000228 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000229 +705bonus pay for amazing work on #OSS 00010000229 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000230 +705bonus pay for amazing work on #OSS 00010000230 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000231 +705bonus pay for amazing work on #OSS 00010000231 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000232 +705bonus pay for amazing work on #OSS 00010000232 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000233 +705bonus pay for amazing work on #OSS 00010000233 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000234 +705bonus pay for amazing work on #OSS 00010000234 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000235 +705bonus pay for amazing work on #OSS 00010000235 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000236 +705bonus pay for amazing work on #OSS 00010000236 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000237 +705bonus pay for amazing work on #OSS 00010000237 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000238 +705bonus pay for amazing work on #OSS 00010000238 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000239 +705bonus pay for amazing work on #OSS 00010000239 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000240 +705bonus pay for amazing work on #OSS 00010000240 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000241 +705bonus pay for amazing work on #OSS 00010000241 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000242 +705bonus pay for amazing work on #OSS 00010000242 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000243 +705bonus pay for amazing work on #OSS 00010000243 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000244 +705bonus pay for amazing work on #OSS 00010000244 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000245 +705bonus pay for amazing work on #OSS 00010000245 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000246 +705bonus pay for amazing work on #OSS 00010000246 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000247 +705bonus pay for amazing work on #OSS 00010000247 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000248 +705bonus pay for amazing work on #OSS 00010000248 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000249 +705bonus pay for amazing work on #OSS 00010000249 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000250 +705bonus pay for amazing work on #OSS 00010000250 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000251 +705bonus pay for amazing work on #OSS 00010000251 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000252 +705bonus pay for amazing work on #OSS 00010000252 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000253 +705bonus pay for amazing work on #OSS 00010000253 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000254 +705bonus pay for amazing work on #OSS 00010000254 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000255 +705bonus pay for amazing work on #OSS 00010000255 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000256 +705bonus pay for amazing work on #OSS 00010000256 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000257 +705bonus pay for amazing work on #OSS 00010000257 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000258 +705bonus pay for amazing work on #OSS 00010000258 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000259 +705bonus pay for amazing work on #OSS 00010000259 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000260 +705bonus pay for amazing work on #OSS 00010000260 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000261 +705bonus pay for amazing work on #OSS 00010000261 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000262 +705bonus pay for amazing work on #OSS 00010000262 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000263 +705bonus pay for amazing work on #OSS 00010000263 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000264 +705bonus pay for amazing work on #OSS 00010000264 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000265 +705bonus pay for amazing work on #OSS 00010000265 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000266 +705bonus pay for amazing work on #OSS 00010000266 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000267 +705bonus pay for amazing work on #OSS 00010000267 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000268 +705bonus pay for amazing work on #OSS 00010000268 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000269 +705bonus pay for amazing work on #OSS 00010000269 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000270 +705bonus pay for amazing work on #OSS 00010000270 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000271 +705bonus pay for amazing work on #OSS 00010000271 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000272 +705bonus pay for amazing work on #OSS 00010000272 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000273 +705bonus pay for amazing work on #OSS 00010000273 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000274 +705bonus pay for amazing work on #OSS 00010000274 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000275 +705bonus pay for amazing work on #OSS 00010000275 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000276 +705bonus pay for amazing work on #OSS 00010000276 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000277 +705bonus pay for amazing work on #OSS 00010000277 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000278 +705bonus pay for amazing work on #OSS 00010000278 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000279 +705bonus pay for amazing work on #OSS 00010000279 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000280 +705bonus pay for amazing work on #OSS 00010000280 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000281 +705bonus pay for amazing work on #OSS 00010000281 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000282 +705bonus pay for amazing work on #OSS 00010000282 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000283 +705bonus pay for amazing work on #OSS 00010000283 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000284 +705bonus pay for amazing work on #OSS 00010000284 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000285 +705bonus pay for amazing work on #OSS 00010000285 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000286 +705bonus pay for amazing work on #OSS 00010000286 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000287 +705bonus pay for amazing work on #OSS 00010000287 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000288 +705bonus pay for amazing work on #OSS 00010000288 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000289 +705bonus pay for amazing work on #OSS 00010000289 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000290 +705bonus pay for amazing work on #OSS 00010000290 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000291 +705bonus pay for amazing work on #OSS 00010000291 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000292 +705bonus pay for amazing work on #OSS 00010000292 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000293 +705bonus pay for amazing work on #OSS 00010000293 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000294 +705bonus pay for amazing work on #OSS 00010000294 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000295 +705bonus pay for amazing work on #OSS 00010000295 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000296 +705bonus pay for amazing work on #OSS 00010000296 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000297 +705bonus pay for amazing work on #OSS 00010000297 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000298 +705bonus pay for amazing work on #OSS 00010000298 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000299 +705bonus pay for amazing work on #OSS 00010000299 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000300 +705bonus pay for amazing work on #OSS 00010000300 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000301 +705bonus pay for amazing work on #OSS 00010000301 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000302 +705bonus pay for amazing work on #OSS 00010000302 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000303 +705bonus pay for amazing work on #OSS 00010000303 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000304 +705bonus pay for amazing work on #OSS 00010000304 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000305 +705bonus pay for amazing work on #OSS 00010000305 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000306 +705bonus pay for amazing work on #OSS 00010000306 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000307 +705bonus pay for amazing work on #OSS 00010000307 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000308 +705bonus pay for amazing work on #OSS 00010000308 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000309 +705bonus pay for amazing work on #OSS 00010000309 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000310 +705bonus pay for amazing work on #OSS 00010000310 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000311 +705bonus pay for amazing work on #OSS 00010000311 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000312 +705bonus pay for amazing work on #OSS 00010000312 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000313 +705bonus pay for amazing work on #OSS 00010000313 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000314 +705bonus pay for amazing work on #OSS 00010000314 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000315 +705bonus pay for amazing work on #OSS 00010000315 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000316 +705bonus pay for amazing work on #OSS 00010000316 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000317 +705bonus pay for amazing work on #OSS 00010000317 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000318 +705bonus pay for amazing work on #OSS 00010000318 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000319 +705bonus pay for amazing work on #OSS 00010000319 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000320 +705bonus pay for amazing work on #OSS 00010000320 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000321 +705bonus pay for amazing work on #OSS 00010000321 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000322 +705bonus pay for amazing work on #OSS 00010000322 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000323 +705bonus pay for amazing work on #OSS 00010000323 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000324 +705bonus pay for amazing work on #OSS 00010000324 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000325 +705bonus pay for amazing work on #OSS 00010000325 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000326 +705bonus pay for amazing work on #OSS 00010000326 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000327 +705bonus pay for amazing work on #OSS 00010000327 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000328 +705bonus pay for amazing work on #OSS 00010000328 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000329 +705bonus pay for amazing work on #OSS 00010000329 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000330 +705bonus pay for amazing work on #OSS 00010000330 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000331 +705bonus pay for amazing work on #OSS 00010000331 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000332 +705bonus pay for amazing work on #OSS 00010000332 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000333 +705bonus pay for amazing work on #OSS 00010000333 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000334 +705bonus pay for amazing work on #OSS 00010000334 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000335 +705bonus pay for amazing work on #OSS 00010000335 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000336 +705bonus pay for amazing work on #OSS 00010000336 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000337 +705bonus pay for amazing work on #OSS 00010000337 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000338 +705bonus pay for amazing work on #OSS 00010000338 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000339 +705bonus pay for amazing work on #OSS 00010000339 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000340 +705bonus pay for amazing work on #OSS 00010000340 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000341 +705bonus pay for amazing work on #OSS 00010000341 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000342 +705bonus pay for amazing work on #OSS 00010000342 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000343 +705bonus pay for amazing work on #OSS 00010000343 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000344 +705bonus pay for amazing work on #OSS 00010000344 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000345 +705bonus pay for amazing work on #OSS 00010000345 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000346 +705bonus pay for amazing work on #OSS 00010000346 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000347 +705bonus pay for amazing work on #OSS 00010000347 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000348 +705bonus pay for amazing work on #OSS 00010000348 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000349 +705bonus pay for amazing work on #OSS 00010000349 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000350 +705bonus pay for amazing work on #OSS 00010000350 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000351 +705bonus pay for amazing work on #OSS 00010000351 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000352 +705bonus pay for amazing work on #OSS 00010000352 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000353 +705bonus pay for amazing work on #OSS 00010000353 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000354 +705bonus pay for amazing work on #OSS 00010000354 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000355 +705bonus pay for amazing work on #OSS 00010000355 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000356 +705bonus pay for amazing work on #OSS 00010000356 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000357 +705bonus pay for amazing work on #OSS 00010000357 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000358 +705bonus pay for amazing work on #OSS 00010000358 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000359 +705bonus pay for amazing work on #OSS 00010000359 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000360 +705bonus pay for amazing work on #OSS 00010000360 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000361 +705bonus pay for amazing work on #OSS 00010000361 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000362 +705bonus pay for amazing work on #OSS 00010000362 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000363 +705bonus pay for amazing work on #OSS 00010000363 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000364 +705bonus pay for amazing work on #OSS 00010000364 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000365 +705bonus pay for amazing work on #OSS 00010000365 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000366 +705bonus pay for amazing work on #OSS 00010000366 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000367 +705bonus pay for amazing work on #OSS 00010000367 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000368 +705bonus pay for amazing work on #OSS 00010000368 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000369 +705bonus pay for amazing work on #OSS 00010000369 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000370 +705bonus pay for amazing work on #OSS 00010000370 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000371 +705bonus pay for amazing work on #OSS 00010000371 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000372 +705bonus pay for amazing work on #OSS 00010000372 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000373 +705bonus pay for amazing work on #OSS 00010000373 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000374 +705bonus pay for amazing work on #OSS 00010000374 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000375 +705bonus pay for amazing work on #OSS 00010000375 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000376 +705bonus pay for amazing work on #OSS 00010000376 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000377 +705bonus pay for amazing work on #OSS 00010000377 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000378 +705bonus pay for amazing work on #OSS 00010000378 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000379 +705bonus pay for amazing work on #OSS 00010000379 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000380 +705bonus pay for amazing work on #OSS 00010000380 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000381 +705bonus pay for amazing work on #OSS 00010000381 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000382 +705bonus pay for amazing work on #OSS 00010000382 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000383 +705bonus pay for amazing work on #OSS 00010000383 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000384 +705bonus pay for amazing work on #OSS 00010000384 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000385 +705bonus pay for amazing work on #OSS 00010000385 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000386 +705bonus pay for amazing work on #OSS 00010000386 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000387 +705bonus pay for amazing work on #OSS 00010000387 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000388 +705bonus pay for amazing work on #OSS 00010000388 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000389 +705bonus pay for amazing work on #OSS 00010000389 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000390 +705bonus pay for amazing work on #OSS 00010000390 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000391 +705bonus pay for amazing work on #OSS 00010000391 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000392 +705bonus pay for amazing work on #OSS 00010000392 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000393 +705bonus pay for amazing work on #OSS 00010000393 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000394 +705bonus pay for amazing work on #OSS 00010000394 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000395 +705bonus pay for amazing work on #OSS 00010000395 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000396 +705bonus pay for amazing work on #OSS 00010000396 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000397 +705bonus pay for amazing work on #OSS 00010000397 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000398 +705bonus pay for amazing work on #OSS 00010000398 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000399 +705bonus pay for amazing work on #OSS 00010000399 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000400 +705bonus pay for amazing work on #OSS 00010000400 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000401 +705bonus pay for amazing work on #OSS 00010000401 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000402 +705bonus pay for amazing work on #OSS 00010000402 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000403 +705bonus pay for amazing work on #OSS 00010000403 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000404 +705bonus pay for amazing work on #OSS 00010000404 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000405 +705bonus pay for amazing work on #OSS 00010000405 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000406 +705bonus pay for amazing work on #OSS 00010000406 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000407 +705bonus pay for amazing work on #OSS 00010000407 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000408 +705bonus pay for amazing work on #OSS 00010000408 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000409 +705bonus pay for amazing work on #OSS 00010000409 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000410 +705bonus pay for amazing work on #OSS 00010000410 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000411 +705bonus pay for amazing work on #OSS 00010000411 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000412 +705bonus pay for amazing work on #OSS 00010000412 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000413 +705bonus pay for amazing work on #OSS 00010000413 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000414 +705bonus pay for amazing work on #OSS 00010000414 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000415 +705bonus pay for amazing work on #OSS 00010000415 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000416 +705bonus pay for amazing work on #OSS 00010000416 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000417 +705bonus pay for amazing work on #OSS 00010000417 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000418 +705bonus pay for amazing work on #OSS 00010000418 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000419 +705bonus pay for amazing work on #OSS 00010000419 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000420 +705bonus pay for amazing work on #OSS 00010000420 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000421 +705bonus pay for amazing work on #OSS 00010000421 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000422 +705bonus pay for amazing work on #OSS 00010000422 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000423 +705bonus pay for amazing work on #OSS 00010000423 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000424 +705bonus pay for amazing work on #OSS 00010000424 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000425 +705bonus pay for amazing work on #OSS 00010000425 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000426 +705bonus pay for amazing work on #OSS 00010000426 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000427 +705bonus pay for amazing work on #OSS 00010000427 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000428 +705bonus pay for amazing work on #OSS 00010000428 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000429 +705bonus pay for amazing work on #OSS 00010000429 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000430 +705bonus pay for amazing work on #OSS 00010000430 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000431 +705bonus pay for amazing work on #OSS 00010000431 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000432 +705bonus pay for amazing work on #OSS 00010000432 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000433 +705bonus pay for amazing work on #OSS 00010000433 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000434 +705bonus pay for amazing work on #OSS 00010000434 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000435 +705bonus pay for amazing work on #OSS 00010000435 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000436 +705bonus pay for amazing work on #OSS 00010000436 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000437 +705bonus pay for amazing work on #OSS 00010000437 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000438 +705bonus pay for amazing work on #OSS 00010000438 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000439 +705bonus pay for amazing work on #OSS 00010000439 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000440 +705bonus pay for amazing work on #OSS 00010000440 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000441 +705bonus pay for amazing work on #OSS 00010000441 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000442 +705bonus pay for amazing work on #OSS 00010000442 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000443 +705bonus pay for amazing work on #OSS 00010000443 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000444 +705bonus pay for amazing work on #OSS 00010000444 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000445 +705bonus pay for amazing work on #OSS 00010000445 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000446 +705bonus pay for amazing work on #OSS 00010000446 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000447 +705bonus pay for amazing work on #OSS 00010000447 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000448 +705bonus pay for amazing work on #OSS 00010000448 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000449 +705bonus pay for amazing work on #OSS 00010000449 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000450 +705bonus pay for amazing work on #OSS 00010000450 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000451 +705bonus pay for amazing work on #OSS 00010000451 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000452 +705bonus pay for amazing work on #OSS 00010000452 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000453 +705bonus pay for amazing work on #OSS 00010000453 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000454 +705bonus pay for amazing work on #OSS 00010000454 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000455 +705bonus pay for amazing work on #OSS 00010000455 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000456 +705bonus pay for amazing work on #OSS 00010000456 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000457 +705bonus pay for amazing work on #OSS 00010000457 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000458 +705bonus pay for amazing work on #OSS 00010000458 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000459 +705bonus pay for amazing work on #OSS 00010000459 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000460 +705bonus pay for amazing work on #OSS 00010000460 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000461 +705bonus pay for amazing work on #OSS 00010000461 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000462 +705bonus pay for amazing work on #OSS 00010000462 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000463 +705bonus pay for amazing work on #OSS 00010000463 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000464 +705bonus pay for amazing work on #OSS 00010000464 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000465 +705bonus pay for amazing work on #OSS 00010000465 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000466 +705bonus pay for amazing work on #OSS 00010000466 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000467 +705bonus pay for amazing work on #OSS 00010000467 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000468 +705bonus pay for amazing work on #OSS 00010000468 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000469 +705bonus pay for amazing work on #OSS 00010000469 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000470 +705bonus pay for amazing work on #OSS 00010000470 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000471 +705bonus pay for amazing work on #OSS 00010000471 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000472 +705bonus pay for amazing work on #OSS 00010000472 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000473 +705bonus pay for amazing work on #OSS 00010000473 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000474 +705bonus pay for amazing work on #OSS 00010000474 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000475 +705bonus pay for amazing work on #OSS 00010000475 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000476 +705bonus pay for amazing work on #OSS 00010000476 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000477 +705bonus pay for amazing work on #OSS 00010000477 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000478 +705bonus pay for amazing work on #OSS 00010000478 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000479 +705bonus pay for amazing work on #OSS 00010000479 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000480 +705bonus pay for amazing work on #OSS 00010000480 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000481 +705bonus pay for amazing work on #OSS 00010000481 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000482 +705bonus pay for amazing work on #OSS 00010000482 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000483 +705bonus pay for amazing work on #OSS 00010000483 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000484 +705bonus pay for amazing work on #OSS 00010000484 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000485 +705bonus pay for amazing work on #OSS 00010000485 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000486 +705bonus pay for amazing work on #OSS 00010000486 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000487 +705bonus pay for amazing work on #OSS 00010000487 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000488 +705bonus pay for amazing work on #OSS 00010000488 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000489 +705bonus pay for amazing work on #OSS 00010000489 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000490 +705bonus pay for amazing work on #OSS 00010000490 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000491 +705bonus pay for amazing work on #OSS 00010000491 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000492 +705bonus pay for amazing work on #OSS 00010000492 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000493 +705bonus pay for amazing work on #OSS 00010000493 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000494 +705bonus pay for amazing work on #OSS 00010000494 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000495 +705bonus pay for amazing work on #OSS 00010000495 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000496 +705bonus pay for amazing work on #OSS 00010000496 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000497 +705bonus pay for amazing work on #OSS 00010000497 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000498 +705bonus pay for amazing work on #OSS 00010000498 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000499 +705bonus pay for amazing work on #OSS 00010000499 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000500 +705bonus pay for amazing work on #OSS 00010000500 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000501 +705bonus pay for amazing work on #OSS 00010000501 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000502 +705bonus pay for amazing work on #OSS 00010000502 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000503 +705bonus pay for amazing work on #OSS 00010000503 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000504 +705bonus pay for amazing work on #OSS 00010000504 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000505 +705bonus pay for amazing work on #OSS 00010000505 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000506 +705bonus pay for amazing work on #OSS 00010000506 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000507 +705bonus pay for amazing work on #OSS 00010000507 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000508 +705bonus pay for amazing work on #OSS 00010000508 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000509 +705bonus pay for amazing work on #OSS 00010000509 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000510 +705bonus pay for amazing work on #OSS 00010000510 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000511 +705bonus pay for amazing work on #OSS 00010000511 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000512 +705bonus pay for amazing work on #OSS 00010000512 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000513 +705bonus pay for amazing work on #OSS 00010000513 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000514 +705bonus pay for amazing work on #OSS 00010000514 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000515 +705bonus pay for amazing work on #OSS 00010000515 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000516 +705bonus pay for amazing work on #OSS 00010000516 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000517 +705bonus pay for amazing work on #OSS 00010000517 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000518 +705bonus pay for amazing work on #OSS 00010000518 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000519 +705bonus pay for amazing work on #OSS 00010000519 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000520 +705bonus pay for amazing work on #OSS 00010000520 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000521 +705bonus pay for amazing work on #OSS 00010000521 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000522 +705bonus pay for amazing work on #OSS 00010000522 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000523 +705bonus pay for amazing work on #OSS 00010000523 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000524 +705bonus pay for amazing work on #OSS 00010000524 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000525 +705bonus pay for amazing work on #OSS 00010000525 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000526 +705bonus pay for amazing work on #OSS 00010000526 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000527 +705bonus pay for amazing work on #OSS 00010000527 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000528 +705bonus pay for amazing work on #OSS 00010000528 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000529 +705bonus pay for amazing work on #OSS 00010000529 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000530 +705bonus pay for amazing work on #OSS 00010000530 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000531 +705bonus pay for amazing work on #OSS 00010000531 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000532 +705bonus pay for amazing work on #OSS 00010000532 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000533 +705bonus pay for amazing work on #OSS 00010000533 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000534 +705bonus pay for amazing work on #OSS 00010000534 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000535 +705bonus pay for amazing work on #OSS 00010000535 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000536 +705bonus pay for amazing work on #OSS 00010000536 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000537 +705bonus pay for amazing work on #OSS 00010000537 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000538 +705bonus pay for amazing work on #OSS 00010000538 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000539 +705bonus pay for amazing work on #OSS 00010000539 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000540 +705bonus pay for amazing work on #OSS 00010000540 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000541 +705bonus pay for amazing work on #OSS 00010000541 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000542 +705bonus pay for amazing work on #OSS 00010000542 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000543 +705bonus pay for amazing work on #OSS 00010000543 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000544 +705bonus pay for amazing work on #OSS 00010000544 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000545 +705bonus pay for amazing work on #OSS 00010000545 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000546 +705bonus pay for amazing work on #OSS 00010000546 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000547 +705bonus pay for amazing work on #OSS 00010000547 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000548 +705bonus pay for amazing work on #OSS 00010000548 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000549 +705bonus pay for amazing work on #OSS 00010000549 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000550 +705bonus pay for amazing work on #OSS 00010000550 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000551 +705bonus pay for amazing work on #OSS 00010000551 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000552 +705bonus pay for amazing work on #OSS 00010000552 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000553 +705bonus pay for amazing work on #OSS 00010000553 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000554 +705bonus pay for amazing work on #OSS 00010000554 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000555 +705bonus pay for amazing work on #OSS 00010000555 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000556 +705bonus pay for amazing work on #OSS 00010000556 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000557 +705bonus pay for amazing work on #OSS 00010000557 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000558 +705bonus pay for amazing work on #OSS 00010000558 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000559 +705bonus pay for amazing work on #OSS 00010000559 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000560 +705bonus pay for amazing work on #OSS 00010000560 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000561 +705bonus pay for amazing work on #OSS 00010000561 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000562 +705bonus pay for amazing work on #OSS 00010000562 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000563 +705bonus pay for amazing work on #OSS 00010000563 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000564 +705bonus pay for amazing work on #OSS 00010000564 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000565 +705bonus pay for amazing work on #OSS 00010000565 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000566 +705bonus pay for amazing work on #OSS 00010000566 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000567 +705bonus pay for amazing work on #OSS 00010000567 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000568 +705bonus pay for amazing work on #OSS 00010000568 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000569 +705bonus pay for amazing work on #OSS 00010000569 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000570 +705bonus pay for amazing work on #OSS 00010000570 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000571 +705bonus pay for amazing work on #OSS 00010000571 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000572 +705bonus pay for amazing work on #OSS 00010000572 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000573 +705bonus pay for amazing work on #OSS 00010000573 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000574 +705bonus pay for amazing work on #OSS 00010000574 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000575 +705bonus pay for amazing work on #OSS 00010000575 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000576 +705bonus pay for amazing work on #OSS 00010000576 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000577 +705bonus pay for amazing work on #OSS 00010000577 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000578 +705bonus pay for amazing work on #OSS 00010000578 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000579 +705bonus pay for amazing work on #OSS 00010000579 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000580 +705bonus pay for amazing work on #OSS 00010000580 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000581 +705bonus pay for amazing work on #OSS 00010000581 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000582 +705bonus pay for amazing work on #OSS 00010000582 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000583 +705bonus pay for amazing work on #OSS 00010000583 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000584 +705bonus pay for amazing work on #OSS 00010000584 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000585 +705bonus pay for amazing work on #OSS 00010000585 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000586 +705bonus pay for amazing work on #OSS 00010000586 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000587 +705bonus pay for amazing work on #OSS 00010000587 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000588 +705bonus pay for amazing work on #OSS 00010000588 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000589 +705bonus pay for amazing work on #OSS 00010000589 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000590 +705bonus pay for amazing work on #OSS 00010000590 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000591 +705bonus pay for amazing work on #OSS 00010000591 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000592 +705bonus pay for amazing work on #OSS 00010000592 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000593 +705bonus pay for amazing work on #OSS 00010000593 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000594 +705bonus pay for amazing work on #OSS 00010000594 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000595 +705bonus pay for amazing work on #OSS 00010000595 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000596 +705bonus pay for amazing work on #OSS 00010000596 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000597 +705bonus pay for amazing work on #OSS 00010000597 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000598 +705bonus pay for amazing work on #OSS 00010000598 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000599 +705bonus pay for amazing work on #OSS 00010000599 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000600 +705bonus pay for amazing work on #OSS 00010000600 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000601 +705bonus pay for amazing work on #OSS 00010000601 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000602 +705bonus pay for amazing work on #OSS 00010000602 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000603 +705bonus pay for amazing work on #OSS 00010000603 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000604 +705bonus pay for amazing work on #OSS 00010000604 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000605 +705bonus pay for amazing work on #OSS 00010000605 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000606 +705bonus pay for amazing work on #OSS 00010000606 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000607 +705bonus pay for amazing work on #OSS 00010000607 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000608 +705bonus pay for amazing work on #OSS 00010000608 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000609 +705bonus pay for amazing work on #OSS 00010000609 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000610 +705bonus pay for amazing work on #OSS 00010000610 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000611 +705bonus pay for amazing work on #OSS 00010000611 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000612 +705bonus pay for amazing work on #OSS 00010000612 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000613 +705bonus pay for amazing work on #OSS 00010000613 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000614 +705bonus pay for amazing work on #OSS 00010000614 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000615 +705bonus pay for amazing work on #OSS 00010000615 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000616 +705bonus pay for amazing work on #OSS 00010000616 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000617 +705bonus pay for amazing work on #OSS 00010000617 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000618 +705bonus pay for amazing work on #OSS 00010000618 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000619 +705bonus pay for amazing work on #OSS 00010000619 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000620 +705bonus pay for amazing work on #OSS 00010000620 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000621 +705bonus pay for amazing work on #OSS 00010000621 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000622 +705bonus pay for amazing work on #OSS 00010000622 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000623 +705bonus pay for amazing work on #OSS 00010000623 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000624 +705bonus pay for amazing work on #OSS 00010000624 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000625 +705bonus pay for amazing work on #OSS 00010000625 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000626 +705bonus pay for amazing work on #OSS 00010000626 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000627 +705bonus pay for amazing work on #OSS 00010000627 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000628 +705bonus pay for amazing work on #OSS 00010000628 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000629 +705bonus pay for amazing work on #OSS 00010000629 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000630 +705bonus pay for amazing work on #OSS 00010000630 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000631 +705bonus pay for amazing work on #OSS 00010000631 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000632 +705bonus pay for amazing work on #OSS 00010000632 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000633 +705bonus pay for amazing work on #OSS 00010000633 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000634 +705bonus pay for amazing work on #OSS 00010000634 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000635 +705bonus pay for amazing work on #OSS 00010000635 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000636 +705bonus pay for amazing work on #OSS 00010000636 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000637 +705bonus pay for amazing work on #OSS 00010000637 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000638 +705bonus pay for amazing work on #OSS 00010000638 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000639 +705bonus pay for amazing work on #OSS 00010000639 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000640 +705bonus pay for amazing work on #OSS 00010000640 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000641 +705bonus pay for amazing work on #OSS 00010000641 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000642 +705bonus pay for amazing work on #OSS 00010000642 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000643 +705bonus pay for amazing work on #OSS 00010000643 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000644 +705bonus pay for amazing work on #OSS 00010000644 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000645 +705bonus pay for amazing work on #OSS 00010000645 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000646 +705bonus pay for amazing work on #OSS 00010000646 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000647 +705bonus pay for amazing work on #OSS 00010000647 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000648 +705bonus pay for amazing work on #OSS 00010000648 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000649 +705bonus pay for amazing work on #OSS 00010000649 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000650 +705bonus pay for amazing work on #OSS 00010000650 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000651 +705bonus pay for amazing work on #OSS 00010000651 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000652 +705bonus pay for amazing work on #OSS 00010000652 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000653 +705bonus pay for amazing work on #OSS 00010000653 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000654 +705bonus pay for amazing work on #OSS 00010000654 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000655 +705bonus pay for amazing work on #OSS 00010000655 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000656 +705bonus pay for amazing work on #OSS 00010000656 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000657 +705bonus pay for amazing work on #OSS 00010000657 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000658 +705bonus pay for amazing work on #OSS 00010000658 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000659 +705bonus pay for amazing work on #OSS 00010000659 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000660 +705bonus pay for amazing work on #OSS 00010000660 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000661 +705bonus pay for amazing work on #OSS 00010000661 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000662 +705bonus pay for amazing work on #OSS 00010000662 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000663 +705bonus pay for amazing work on #OSS 00010000663 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000664 +705bonus pay for amazing work on #OSS 00010000664 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000665 +705bonus pay for amazing work on #OSS 00010000665 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000666 +705bonus pay for amazing work on #OSS 00010000666 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000667 +705bonus pay for amazing work on #OSS 00010000667 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000668 +705bonus pay for amazing work on #OSS 00010000668 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000669 +705bonus pay for amazing work on #OSS 00010000669 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000670 +705bonus pay for amazing work on #OSS 00010000670 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000671 +705bonus pay for amazing work on #OSS 00010000671 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000672 +705bonus pay for amazing work on #OSS 00010000672 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000673 +705bonus pay for amazing work on #OSS 00010000673 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000674 +705bonus pay for amazing work on #OSS 00010000674 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000675 +705bonus pay for amazing work on #OSS 00010000675 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000676 +705bonus pay for amazing work on #OSS 00010000676 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000677 +705bonus pay for amazing work on #OSS 00010000677 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000678 +705bonus pay for amazing work on #OSS 00010000678 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000679 +705bonus pay for amazing work on #OSS 00010000679 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000680 +705bonus pay for amazing work on #OSS 00010000680 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000681 +705bonus pay for amazing work on #OSS 00010000681 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000682 +705bonus pay for amazing work on #OSS 00010000682 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000683 +705bonus pay for amazing work on #OSS 00010000683 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000684 +705bonus pay for amazing work on #OSS 00010000684 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000685 +705bonus pay for amazing work on #OSS 00010000685 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000686 +705bonus pay for amazing work on #OSS 00010000686 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000687 +705bonus pay for amazing work on #OSS 00010000687 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000688 +705bonus pay for amazing work on #OSS 00010000688 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000689 +705bonus pay for amazing work on #OSS 00010000689 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000690 +705bonus pay for amazing work on #OSS 00010000690 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000691 +705bonus pay for amazing work on #OSS 00010000691 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000692 +705bonus pay for amazing work on #OSS 00010000692 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000693 +705bonus pay for amazing work on #OSS 00010000693 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000694 +705bonus pay for amazing work on #OSS 00010000694 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000695 +705bonus pay for amazing work on #OSS 00010000695 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000696 +705bonus pay for amazing work on #OSS 00010000696 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000697 +705bonus pay for amazing work on #OSS 00010000697 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000698 +705bonus pay for amazing work on #OSS 00010000698 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000699 +705bonus pay for amazing work on #OSS 00010000699 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000700 +705bonus pay for amazing work on #OSS 00010000700 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000701 +705bonus pay for amazing work on #OSS 00010000701 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000702 +705bonus pay for amazing work on #OSS 00010000702 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000703 +705bonus pay for amazing work on #OSS 00010000703 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000704 +705bonus pay for amazing work on #OSS 00010000704 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000705 +705bonus pay for amazing work on #OSS 00010000705 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000706 +705bonus pay for amazing work on #OSS 00010000706 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000707 +705bonus pay for amazing work on #OSS 00010000707 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000708 +705bonus pay for amazing work on #OSS 00010000708 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000709 +705bonus pay for amazing work on #OSS 00010000709 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000710 +705bonus pay for amazing work on #OSS 00010000710 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000711 +705bonus pay for amazing work on #OSS 00010000711 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000712 +705bonus pay for amazing work on #OSS 00010000712 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000713 +705bonus pay for amazing work on #OSS 00010000713 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000714 +705bonus pay for amazing work on #OSS 00010000714 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000715 +705bonus pay for amazing work on #OSS 00010000715 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000716 +705bonus pay for amazing work on #OSS 00010000716 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000717 +705bonus pay for amazing work on #OSS 00010000717 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000718 +705bonus pay for amazing work on #OSS 00010000718 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000719 +705bonus pay for amazing work on #OSS 00010000719 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000720 +705bonus pay for amazing work on #OSS 00010000720 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000721 +705bonus pay for amazing work on #OSS 00010000721 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000722 +705bonus pay for amazing work on #OSS 00010000722 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000723 +705bonus pay for amazing work on #OSS 00010000723 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000724 +705bonus pay for amazing work on #OSS 00010000724 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000725 +705bonus pay for amazing work on #OSS 00010000725 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000726 +705bonus pay for amazing work on #OSS 00010000726 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000727 +705bonus pay for amazing work on #OSS 00010000727 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000728 +705bonus pay for amazing work on #OSS 00010000728 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000729 +705bonus pay for amazing work on #OSS 00010000729 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000730 +705bonus pay for amazing work on #OSS 00010000730 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000731 +705bonus pay for amazing work on #OSS 00010000731 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000732 +705bonus pay for amazing work on #OSS 00010000732 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000733 +705bonus pay for amazing work on #OSS 00010000733 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000734 +705bonus pay for amazing work on #OSS 00010000734 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000735 +705bonus pay for amazing work on #OSS 00010000735 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000736 +705bonus pay for amazing work on #OSS 00010000736 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000737 +705bonus pay for amazing work on #OSS 00010000737 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000738 +705bonus pay for amazing work on #OSS 00010000738 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000739 +705bonus pay for amazing work on #OSS 00010000739 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000740 +705bonus pay for amazing work on #OSS 00010000740 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000741 +705bonus pay for amazing work on #OSS 00010000741 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000742 +705bonus pay for amazing work on #OSS 00010000742 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000743 +705bonus pay for amazing work on #OSS 00010000743 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000744 +705bonus pay for amazing work on #OSS 00010000744 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000745 +705bonus pay for amazing work on #OSS 00010000745 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000746 +705bonus pay for amazing work on #OSS 00010000746 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000747 +705bonus pay for amazing work on #OSS 00010000747 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000748 +705bonus pay for amazing work on #OSS 00010000748 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000749 +705bonus pay for amazing work on #OSS 00010000749 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000750 +705bonus pay for amazing work on #OSS 00010000750 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000751 +705bonus pay for amazing work on #OSS 00010000751 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000752 +705bonus pay for amazing work on #OSS 00010000752 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000753 +705bonus pay for amazing work on #OSS 00010000753 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000754 +705bonus pay for amazing work on #OSS 00010000754 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000755 +705bonus pay for amazing work on #OSS 00010000755 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000756 +705bonus pay for amazing work on #OSS 00010000756 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000757 +705bonus pay for amazing work on #OSS 00010000757 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000758 +705bonus pay for amazing work on #OSS 00010000758 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000759 +705bonus pay for amazing work on #OSS 00010000759 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000760 +705bonus pay for amazing work on #OSS 00010000760 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000761 +705bonus pay for amazing work on #OSS 00010000761 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000762 +705bonus pay for amazing work on #OSS 00010000762 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000763 +705bonus pay for amazing work on #OSS 00010000763 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000764 +705bonus pay for amazing work on #OSS 00010000764 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000765 +705bonus pay for amazing work on #OSS 00010000765 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000766 +705bonus pay for amazing work on #OSS 00010000766 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000767 +705bonus pay for amazing work on #OSS 00010000767 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000768 +705bonus pay for amazing work on #OSS 00010000768 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000769 +705bonus pay for amazing work on #OSS 00010000769 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000770 +705bonus pay for amazing work on #OSS 00010000770 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000771 +705bonus pay for amazing work on #OSS 00010000771 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000772 +705bonus pay for amazing work on #OSS 00010000772 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000773 +705bonus pay for amazing work on #OSS 00010000773 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000774 +705bonus pay for amazing work on #OSS 00010000774 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000775 +705bonus pay for amazing work on #OSS 00010000775 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000776 +705bonus pay for amazing work on #OSS 00010000776 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000777 +705bonus pay for amazing work on #OSS 00010000777 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000778 +705bonus pay for amazing work on #OSS 00010000778 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000779 +705bonus pay for amazing work on #OSS 00010000779 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000780 +705bonus pay for amazing work on #OSS 00010000780 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000781 +705bonus pay for amazing work on #OSS 00010000781 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000782 +705bonus pay for amazing work on #OSS 00010000782 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000783 +705bonus pay for amazing work on #OSS 00010000783 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000784 +705bonus pay for amazing work on #OSS 00010000784 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000785 +705bonus pay for amazing work on #OSS 00010000785 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000786 +705bonus pay for amazing work on #OSS 00010000786 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000787 +705bonus pay for amazing work on #OSS 00010000787 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000788 +705bonus pay for amazing work on #OSS 00010000788 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000789 +705bonus pay for amazing work on #OSS 00010000789 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000790 +705bonus pay for amazing work on #OSS 00010000790 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000791 +705bonus pay for amazing work on #OSS 00010000791 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000792 +705bonus pay for amazing work on #OSS 00010000792 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000793 +705bonus pay for amazing work on #OSS 00010000793 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000794 +705bonus pay for amazing work on #OSS 00010000794 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000795 +705bonus pay for amazing work on #OSS 00010000795 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000796 +705bonus pay for amazing work on #OSS 00010000796 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000797 +705bonus pay for amazing work on #OSS 00010000797 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000798 +705bonus pay for amazing work on #OSS 00010000798 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000799 +705bonus pay for amazing work on #OSS 00010000799 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000800 +705bonus pay for amazing work on #OSS 00010000800 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000801 +705bonus pay for amazing work on #OSS 00010000801 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000802 +705bonus pay for amazing work on #OSS 00010000802 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000803 +705bonus pay for amazing work on #OSS 00010000803 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000804 +705bonus pay for amazing work on #OSS 00010000804 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000805 +705bonus pay for amazing work on #OSS 00010000805 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000806 +705bonus pay for amazing work on #OSS 00010000806 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000807 +705bonus pay for amazing work on #OSS 00010000807 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000808 +705bonus pay for amazing work on #OSS 00010000808 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000809 +705bonus pay for amazing work on #OSS 00010000809 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000810 +705bonus pay for amazing work on #OSS 00010000810 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000811 +705bonus pay for amazing work on #OSS 00010000811 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000812 +705bonus pay for amazing work on #OSS 00010000812 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000813 +705bonus pay for amazing work on #OSS 00010000813 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000814 +705bonus pay for amazing work on #OSS 00010000814 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000815 +705bonus pay for amazing work on #OSS 00010000815 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000816 +705bonus pay for amazing work on #OSS 00010000816 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000817 +705bonus pay for amazing work on #OSS 00010000817 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000818 +705bonus pay for amazing work on #OSS 00010000818 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000819 +705bonus pay for amazing work on #OSS 00010000819 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000820 +705bonus pay for amazing work on #OSS 00010000820 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000821 +705bonus pay for amazing work on #OSS 00010000821 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000822 +705bonus pay for amazing work on #OSS 00010000822 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000823 +705bonus pay for amazing work on #OSS 00010000823 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000824 +705bonus pay for amazing work on #OSS 00010000824 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000825 +705bonus pay for amazing work on #OSS 00010000825 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000826 +705bonus pay for amazing work on #OSS 00010000826 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000827 +705bonus pay for amazing work on #OSS 00010000827 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000828 +705bonus pay for amazing work on #OSS 00010000828 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000829 +705bonus pay for amazing work on #OSS 00010000829 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000830 +705bonus pay for amazing work on #OSS 00010000830 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000831 +705bonus pay for amazing work on #OSS 00010000831 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000832 +705bonus pay for amazing work on #OSS 00010000832 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000833 +705bonus pay for amazing work on #OSS 00010000833 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000834 +705bonus pay for amazing work on #OSS 00010000834 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000835 +705bonus pay for amazing work on #OSS 00010000835 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000836 +705bonus pay for amazing work on #OSS 00010000836 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000837 +705bonus pay for amazing work on #OSS 00010000837 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000838 +705bonus pay for amazing work on #OSS 00010000838 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000839 +705bonus pay for amazing work on #OSS 00010000839 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000840 +705bonus pay for amazing work on #OSS 00010000840 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000841 +705bonus pay for amazing work on #OSS 00010000841 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000842 +705bonus pay for amazing work on #OSS 00010000842 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000843 +705bonus pay for amazing work on #OSS 00010000843 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000844 +705bonus pay for amazing work on #OSS 00010000844 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000845 +705bonus pay for amazing work on #OSS 00010000845 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000846 +705bonus pay for amazing work on #OSS 00010000846 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000847 +705bonus pay for amazing work on #OSS 00010000847 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000848 +705bonus pay for amazing work on #OSS 00010000848 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000849 +705bonus pay for amazing work on #OSS 00010000849 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000850 +705bonus pay for amazing work on #OSS 00010000850 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000851 +705bonus pay for amazing work on #OSS 00010000851 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000852 +705bonus pay for amazing work on #OSS 00010000852 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000853 +705bonus pay for amazing work on #OSS 00010000853 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000854 +705bonus pay for amazing work on #OSS 00010000854 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000855 +705bonus pay for amazing work on #OSS 00010000855 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000856 +705bonus pay for amazing work on #OSS 00010000856 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000857 +705bonus pay for amazing work on #OSS 00010000857 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000858 +705bonus pay for amazing work on #OSS 00010000858 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000859 +705bonus pay for amazing work on #OSS 00010000859 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000860 +705bonus pay for amazing work on #OSS 00010000860 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000861 +705bonus pay for amazing work on #OSS 00010000861 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000862 +705bonus pay for amazing work on #OSS 00010000862 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000863 +705bonus pay for amazing work on #OSS 00010000863 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000864 +705bonus pay for amazing work on #OSS 00010000864 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000865 +705bonus pay for amazing work on #OSS 00010000865 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000866 +705bonus pay for amazing work on #OSS 00010000866 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000867 +705bonus pay for amazing work on #OSS 00010000867 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000868 +705bonus pay for amazing work on #OSS 00010000868 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000869 +705bonus pay for amazing work on #OSS 00010000869 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000870 +705bonus pay for amazing work on #OSS 00010000870 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000871 +705bonus pay for amazing work on #OSS 00010000871 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000872 +705bonus pay for amazing work on #OSS 00010000872 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000873 +705bonus pay for amazing work on #OSS 00010000873 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000874 +705bonus pay for amazing work on #OSS 00010000874 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000875 +705bonus pay for amazing work on #OSS 00010000875 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000876 +705bonus pay for amazing work on #OSS 00010000876 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000877 +705bonus pay for amazing work on #OSS 00010000877 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000878 +705bonus pay for amazing work on #OSS 00010000878 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000879 +705bonus pay for amazing work on #OSS 00010000879 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000880 +705bonus pay for amazing work on #OSS 00010000880 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000881 +705bonus pay for amazing work on #OSS 00010000881 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000882 +705bonus pay for amazing work on #OSS 00010000882 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000883 +705bonus pay for amazing work on #OSS 00010000883 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000884 +705bonus pay for amazing work on #OSS 00010000884 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000885 +705bonus pay for amazing work on #OSS 00010000885 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000886 +705bonus pay for amazing work on #OSS 00010000886 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000887 +705bonus pay for amazing work on #OSS 00010000887 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000888 +705bonus pay for amazing work on #OSS 00010000888 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000889 +705bonus pay for amazing work on #OSS 00010000889 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000890 +705bonus pay for amazing work on #OSS 00010000890 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000891 +705bonus pay for amazing work on #OSS 00010000891 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000892 +705bonus pay for amazing work on #OSS 00010000892 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000893 +705bonus pay for amazing work on #OSS 00010000893 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000894 +705bonus pay for amazing work on #OSS 00010000894 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000895 +705bonus pay for amazing work on #OSS 00010000895 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000896 +705bonus pay for amazing work on #OSS 00010000896 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000897 +705bonus pay for amazing work on #OSS 00010000897 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000898 +705bonus pay for amazing work on #OSS 00010000898 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000899 +705bonus pay for amazing work on #OSS 00010000899 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000900 +705bonus pay for amazing work on #OSS 00010000900 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000901 +705bonus pay for amazing work on #OSS 00010000901 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000902 +705bonus pay for amazing work on #OSS 00010000902 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000903 +705bonus pay for amazing work on #OSS 00010000903 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000904 +705bonus pay for amazing work on #OSS 00010000904 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000905 +705bonus pay for amazing work on #OSS 00010000905 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000906 +705bonus pay for amazing work on #OSS 00010000906 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000907 +705bonus pay for amazing work on #OSS 00010000907 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000908 +705bonus pay for amazing work on #OSS 00010000908 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000909 +705bonus pay for amazing work on #OSS 00010000909 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000910 +705bonus pay for amazing work on #OSS 00010000910 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000911 +705bonus pay for amazing work on #OSS 00010000911 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000912 +705bonus pay for amazing work on #OSS 00010000912 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000913 +705bonus pay for amazing work on #OSS 00010000913 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000914 +705bonus pay for amazing work on #OSS 00010000914 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000915 +705bonus pay for amazing work on #OSS 00010000915 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000916 +705bonus pay for amazing work on #OSS 00010000916 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000917 +705bonus pay for amazing work on #OSS 00010000917 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000918 +705bonus pay for amazing work on #OSS 00010000918 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000919 +705bonus pay for amazing work on #OSS 00010000919 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000920 +705bonus pay for amazing work on #OSS 00010000920 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000921 +705bonus pay for amazing work on #OSS 00010000921 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000922 +705bonus pay for amazing work on #OSS 00010000922 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000923 +705bonus pay for amazing work on #OSS 00010000923 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000924 +705bonus pay for amazing work on #OSS 00010000924 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000925 +705bonus pay for amazing work on #OSS 00010000925 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000926 +705bonus pay for amazing work on #OSS 00010000926 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000927 +705bonus pay for amazing work on #OSS 00010000927 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000928 +705bonus pay for amazing work on #OSS 00010000928 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000929 +705bonus pay for amazing work on #OSS 00010000929 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000930 +705bonus pay for amazing work on #OSS 00010000930 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000931 +705bonus pay for amazing work on #OSS 00010000931 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000932 +705bonus pay for amazing work on #OSS 00010000932 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000933 +705bonus pay for amazing work on #OSS 00010000933 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000934 +705bonus pay for amazing work on #OSS 00010000934 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000935 +705bonus pay for amazing work on #OSS 00010000935 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000936 +705bonus pay for amazing work on #OSS 00010000936 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000937 +705bonus pay for amazing work on #OSS 00010000937 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000938 +705bonus pay for amazing work on #OSS 00010000938 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000939 +705bonus pay for amazing work on #OSS 00010000939 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000940 +705bonus pay for amazing work on #OSS 00010000940 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000941 +705bonus pay for amazing work on #OSS 00010000941 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000942 +705bonus pay for amazing work on #OSS 00010000942 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000943 +705bonus pay for amazing work on #OSS 00010000943 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000944 +705bonus pay for amazing work on #OSS 00010000944 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000945 +705bonus pay for amazing work on #OSS 00010000945 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000946 +705bonus pay for amazing work on #OSS 00010000946 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000947 +705bonus pay for amazing work on #OSS 00010000947 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000948 +705bonus pay for amazing work on #OSS 00010000948 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000949 +705bonus pay for amazing work on #OSS 00010000949 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000950 +705bonus pay for amazing work on #OSS 00010000950 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000951 +705bonus pay for amazing work on #OSS 00010000951 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000952 +705bonus pay for amazing work on #OSS 00010000952 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000953 +705bonus pay for amazing work on #OSS 00010000953 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000954 +705bonus pay for amazing work on #OSS 00010000954 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000955 +705bonus pay for amazing work on #OSS 00010000955 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000956 +705bonus pay for amazing work on #OSS 00010000956 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000957 +705bonus pay for amazing work on #OSS 00010000957 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000958 +705bonus pay for amazing work on #OSS 00010000958 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000959 +705bonus pay for amazing work on #OSS 00010000959 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000960 +705bonus pay for amazing work on #OSS 00010000960 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000961 +705bonus pay for amazing work on #OSS 00010000961 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000962 +705bonus pay for amazing work on #OSS 00010000962 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000963 +705bonus pay for amazing work on #OSS 00010000963 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000964 +705bonus pay for amazing work on #OSS 00010000964 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000965 +705bonus pay for amazing work on #OSS 00010000965 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000966 +705bonus pay for amazing work on #OSS 00010000966 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000967 +705bonus pay for amazing work on #OSS 00010000967 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000968 +705bonus pay for amazing work on #OSS 00010000968 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000969 +705bonus pay for amazing work on #OSS 00010000969 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000970 +705bonus pay for amazing work on #OSS 00010000970 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000971 +705bonus pay for amazing work on #OSS 00010000971 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000972 +705bonus pay for amazing work on #OSS 00010000972 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000973 +705bonus pay for amazing work on #OSS 00010000973 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000974 +705bonus pay for amazing work on #OSS 00010000974 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000975 +705bonus pay for amazing work on #OSS 00010000975 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000976 +705bonus pay for amazing work on #OSS 00010000976 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000977 +705bonus pay for amazing work on #OSS 00010000977 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000978 +705bonus pay for amazing work on #OSS 00010000978 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000979 +705bonus pay for amazing work on #OSS 00010000979 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000980 +705bonus pay for amazing work on #OSS 00010000980 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000981 +705bonus pay for amazing work on #OSS 00010000981 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000982 +705bonus pay for amazing work on #OSS 00010000982 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000983 +705bonus pay for amazing work on #OSS 00010000983 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000984 +705bonus pay for amazing work on #OSS 00010000984 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000985 +705bonus pay for amazing work on #OSS 00010000985 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000986 +705bonus pay for amazing work on #OSS 00010000986 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000987 +705bonus pay for amazing work on #OSS 00010000987 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000988 +705bonus pay for amazing work on #OSS 00010000988 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000989 +705bonus pay for amazing work on #OSS 00010000989 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000990 +705bonus pay for amazing work on #OSS 00010000990 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000991 +705bonus pay for amazing work on #OSS 00010000991 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000992 +705bonus pay for amazing work on #OSS 00010000992 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000993 +705bonus pay for amazing work on #OSS 00010000993 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000994 +705bonus pay for amazing work on #OSS 00010000994 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000995 +705bonus pay for amazing work on #OSS 00010000995 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000996 +705bonus pay for amazing work on #OSS 00010000996 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000997 +705bonus pay for amazing work on #OSS 00010000997 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000998 +705bonus pay for amazing work on #OSS 00010000998 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000999 +705bonus pay for amazing work on #OSS 00010000999 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001000 +705bonus pay for amazing work on #OSS 00010001000 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001001 +705bonus pay for amazing work on #OSS 00010001001 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001002 +705bonus pay for amazing work on #OSS 00010001002 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001003 +705bonus pay for amazing work on #OSS 00010001003 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001004 +705bonus pay for amazing work on #OSS 00010001004 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001005 +705bonus pay for amazing work on #OSS 00010001005 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001006 +705bonus pay for amazing work on #OSS 00010001006 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001007 +705bonus pay for amazing work on #OSS 00010001007 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001008 +705bonus pay for amazing work on #OSS 00010001008 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001009 +705bonus pay for amazing work on #OSS 00010001009 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001010 +705bonus pay for amazing work on #OSS 00010001010 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001011 +705bonus pay for amazing work on #OSS 00010001011 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001012 +705bonus pay for amazing work on #OSS 00010001012 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001013 +705bonus pay for amazing work on #OSS 00010001013 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001014 +705bonus pay for amazing work on #OSS 00010001014 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001015 +705bonus pay for amazing work on #OSS 00010001015 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001016 +705bonus pay for amazing work on #OSS 00010001016 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001017 +705bonus pay for amazing work on #OSS 00010001017 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001018 +705bonus pay for amazing work on #OSS 00010001018 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001019 +705bonus pay for amazing work on #OSS 00010001019 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001020 +705bonus pay for amazing work on #OSS 00010001020 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001021 +705bonus pay for amazing work on #OSS 00010001021 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001022 +705bonus pay for amazing work on #OSS 00010001022 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001023 +705bonus pay for amazing work on #OSS 00010001023 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001024 +705bonus pay for amazing work on #OSS 00010001024 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001025 +705bonus pay for amazing work on #OSS 00010001025 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001026 +705bonus pay for amazing work on #OSS 00010001026 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001027 +705bonus pay for amazing work on #OSS 00010001027 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001028 +705bonus pay for amazing work on #OSS 00010001028 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001029 +705bonus pay for amazing work on #OSS 00010001029 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001030 +705bonus pay for amazing work on #OSS 00010001030 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001031 +705bonus pay for amazing work on #OSS 00010001031 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001032 +705bonus pay for amazing work on #OSS 00010001032 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001033 +705bonus pay for amazing work on #OSS 00010001033 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001034 +705bonus pay for amazing work on #OSS 00010001034 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001035 +705bonus pay for amazing work on #OSS 00010001035 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001036 +705bonus pay for amazing work on #OSS 00010001036 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001037 +705bonus pay for amazing work on #OSS 00010001037 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001038 +705bonus pay for amazing work on #OSS 00010001038 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001039 +705bonus pay for amazing work on #OSS 00010001039 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001040 +705bonus pay for amazing work on #OSS 00010001040 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001041 +705bonus pay for amazing work on #OSS 00010001041 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001042 +705bonus pay for amazing work on #OSS 00010001042 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001043 +705bonus pay for amazing work on #OSS 00010001043 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001044 +705bonus pay for amazing work on #OSS 00010001044 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001045 +705bonus pay for amazing work on #OSS 00010001045 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001046 +705bonus pay for amazing work on #OSS 00010001046 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001047 +705bonus pay for amazing work on #OSS 00010001047 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001048 +705bonus pay for amazing work on #OSS 00010001048 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001049 +705bonus pay for amazing work on #OSS 00010001049 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001050 +705bonus pay for amazing work on #OSS 00010001050 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001051 +705bonus pay for amazing work on #OSS 00010001051 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001052 +705bonus pay for amazing work on #OSS 00010001052 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001053 +705bonus pay for amazing work on #OSS 00010001053 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001054 +705bonus pay for amazing work on #OSS 00010001054 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001055 +705bonus pay for amazing work on #OSS 00010001055 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001056 +705bonus pay for amazing work on #OSS 00010001056 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001057 +705bonus pay for amazing work on #OSS 00010001057 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001058 +705bonus pay for amazing work on #OSS 00010001058 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001059 +705bonus pay for amazing work on #OSS 00010001059 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001060 +705bonus pay for amazing work on #OSS 00010001060 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001061 +705bonus pay for amazing work on #OSS 00010001061 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001062 +705bonus pay for amazing work on #OSS 00010001062 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001063 +705bonus pay for amazing work on #OSS 00010001063 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001064 +705bonus pay for amazing work on #OSS 00010001064 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001065 +705bonus pay for amazing work on #OSS 00010001065 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001066 +705bonus pay for amazing work on #OSS 00010001066 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001067 +705bonus pay for amazing work on #OSS 00010001067 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001068 +705bonus pay for amazing work on #OSS 00010001068 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001069 +705bonus pay for amazing work on #OSS 00010001069 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001070 +705bonus pay for amazing work on #OSS 00010001070 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001071 +705bonus pay for amazing work on #OSS 00010001071 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001072 +705bonus pay for amazing work on #OSS 00010001072 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001073 +705bonus pay for amazing work on #OSS 00010001073 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001074 +705bonus pay for amazing work on #OSS 00010001074 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001075 +705bonus pay for amazing work on #OSS 00010001075 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001076 +705bonus pay for amazing work on #OSS 00010001076 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001077 +705bonus pay for amazing work on #OSS 00010001077 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001078 +705bonus pay for amazing work on #OSS 00010001078 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001079 +705bonus pay for amazing work on #OSS 00010001079 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001080 +705bonus pay for amazing work on #OSS 00010001080 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001081 +705bonus pay for amazing work on #OSS 00010001081 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001082 +705bonus pay for amazing work on #OSS 00010001082 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001083 +705bonus pay for amazing work on #OSS 00010001083 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001084 +705bonus pay for amazing work on #OSS 00010001084 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001085 +705bonus pay for amazing work on #OSS 00010001085 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001086 +705bonus pay for amazing work on #OSS 00010001086 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001087 +705bonus pay for amazing work on #OSS 00010001087 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001088 +705bonus pay for amazing work on #OSS 00010001088 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001089 +705bonus pay for amazing work on #OSS 00010001089 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001090 +705bonus pay for amazing work on #OSS 00010001090 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001091 +705bonus pay for amazing work on #OSS 00010001091 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001092 +705bonus pay for amazing work on #OSS 00010001092 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001093 +705bonus pay for amazing work on #OSS 00010001093 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001094 +705bonus pay for amazing work on #OSS 00010001094 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001095 +705bonus pay for amazing work on #OSS 00010001095 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001096 +705bonus pay for amazing work on #OSS 00010001096 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001097 +705bonus pay for amazing work on #OSS 00010001097 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001098 +705bonus pay for amazing work on #OSS 00010001098 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001099 +705bonus pay for amazing work on #OSS 00010001099 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001100 +705bonus pay for amazing work on #OSS 00010001100 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001101 +705bonus pay for amazing work on #OSS 00010001101 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001102 +705bonus pay for amazing work on #OSS 00010001102 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001103 +705bonus pay for amazing work on #OSS 00010001103 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001104 +705bonus pay for amazing work on #OSS 00010001104 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001105 +705bonus pay for amazing work on #OSS 00010001105 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001106 +705bonus pay for amazing work on #OSS 00010001106 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001107 +705bonus pay for amazing work on #OSS 00010001107 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001108 +705bonus pay for amazing work on #OSS 00010001108 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001109 +705bonus pay for amazing work on #OSS 00010001109 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001110 +705bonus pay for amazing work on #OSS 00010001110 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001111 +705bonus pay for amazing work on #OSS 00010001111 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001112 +705bonus pay for amazing work on #OSS 00010001112 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001113 +705bonus pay for amazing work on #OSS 00010001113 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001114 +705bonus pay for amazing work on #OSS 00010001114 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001115 +705bonus pay for amazing work on #OSS 00010001115 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001116 +705bonus pay for amazing work on #OSS 00010001116 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001117 +705bonus pay for amazing work on #OSS 00010001117 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001118 +705bonus pay for amazing work on #OSS 00010001118 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001119 +705bonus pay for amazing work on #OSS 00010001119 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001120 +705bonus pay for amazing work on #OSS 00010001120 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001121 +705bonus pay for amazing work on #OSS 00010001121 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001122 +705bonus pay for amazing work on #OSS 00010001122 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001123 +705bonus pay for amazing work on #OSS 00010001123 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001124 +705bonus pay for amazing work on #OSS 00010001124 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001125 +705bonus pay for amazing work on #OSS 00010001125 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001126 +705bonus pay for amazing work on #OSS 00010001126 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001127 +705bonus pay for amazing work on #OSS 00010001127 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001128 +705bonus pay for amazing work on #OSS 00010001128 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001129 +705bonus pay for amazing work on #OSS 00010001129 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001130 +705bonus pay for amazing work on #OSS 00010001130 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001131 +705bonus pay for amazing work on #OSS 00010001131 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001132 +705bonus pay for amazing work on #OSS 00010001132 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001133 +705bonus pay for amazing work on #OSS 00010001133 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001134 +705bonus pay for amazing work on #OSS 00010001134 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001135 +705bonus pay for amazing work on #OSS 00010001135 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001136 +705bonus pay for amazing work on #OSS 00010001136 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001137 +705bonus pay for amazing work on #OSS 00010001137 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001138 +705bonus pay for amazing work on #OSS 00010001138 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001139 +705bonus pay for amazing work on #OSS 00010001139 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001140 +705bonus pay for amazing work on #OSS 00010001140 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001141 +705bonus pay for amazing work on #OSS 00010001141 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001142 +705bonus pay for amazing work on #OSS 00010001142 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001143 +705bonus pay for amazing work on #OSS 00010001143 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001144 +705bonus pay for amazing work on #OSS 00010001144 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001145 +705bonus pay for amazing work on #OSS 00010001145 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001146 +705bonus pay for amazing work on #OSS 00010001146 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001147 +705bonus pay for amazing work on #OSS 00010001147 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001148 +705bonus pay for amazing work on #OSS 00010001148 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001149 +705bonus pay for amazing work on #OSS 00010001149 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001150 +705bonus pay for amazing work on #OSS 00010001150 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001151 +705bonus pay for amazing work on #OSS 00010001151 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001152 +705bonus pay for amazing work on #OSS 00010001152 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001153 +705bonus pay for amazing work on #OSS 00010001153 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001154 +705bonus pay for amazing work on #OSS 00010001154 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001155 +705bonus pay for amazing work on #OSS 00010001155 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001156 +705bonus pay for amazing work on #OSS 00010001156 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001157 +705bonus pay for amazing work on #OSS 00010001157 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001158 +705bonus pay for amazing work on #OSS 00010001158 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001159 +705bonus pay for amazing work on #OSS 00010001159 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001160 +705bonus pay for amazing work on #OSS 00010001160 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001161 +705bonus pay for amazing work on #OSS 00010001161 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001162 +705bonus pay for amazing work on #OSS 00010001162 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001163 +705bonus pay for amazing work on #OSS 00010001163 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001164 +705bonus pay for amazing work on #OSS 00010001164 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001165 +705bonus pay for amazing work on #OSS 00010001165 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001166 +705bonus pay for amazing work on #OSS 00010001166 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001167 +705bonus pay for amazing work on #OSS 00010001167 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001168 +705bonus pay for amazing work on #OSS 00010001168 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001169 +705bonus pay for amazing work on #OSS 00010001169 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001170 +705bonus pay for amazing work on #OSS 00010001170 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001171 +705bonus pay for amazing work on #OSS 00010001171 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001172 +705bonus pay for amazing work on #OSS 00010001172 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001173 +705bonus pay for amazing work on #OSS 00010001173 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001174 +705bonus pay for amazing work on #OSS 00010001174 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001175 +705bonus pay for amazing work on #OSS 00010001175 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001176 +705bonus pay for amazing work on #OSS 00010001176 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001177 +705bonus pay for amazing work on #OSS 00010001177 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001178 +705bonus pay for amazing work on #OSS 00010001178 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001179 +705bonus pay for amazing work on #OSS 00010001179 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001180 +705bonus pay for amazing work on #OSS 00010001180 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001181 +705bonus pay for amazing work on #OSS 00010001181 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001182 +705bonus pay for amazing work on #OSS 00010001182 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001183 +705bonus pay for amazing work on #OSS 00010001183 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001184 +705bonus pay for amazing work on #OSS 00010001184 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001185 +705bonus pay for amazing work on #OSS 00010001185 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001186 +705bonus pay for amazing work on #OSS 00010001186 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001187 +705bonus pay for amazing work on #OSS 00010001187 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001188 +705bonus pay for amazing work on #OSS 00010001188 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001189 +705bonus pay for amazing work on #OSS 00010001189 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001190 +705bonus pay for amazing work on #OSS 00010001190 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001191 +705bonus pay for amazing work on #OSS 00010001191 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001192 +705bonus pay for amazing work on #OSS 00010001192 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001193 +705bonus pay for amazing work on #OSS 00010001193 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001194 +705bonus pay for amazing work on #OSS 00010001194 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001195 +705bonus pay for amazing work on #OSS 00010001195 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001196 +705bonus pay for amazing work on #OSS 00010001196 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001197 +705bonus pay for amazing work on #OSS 00010001197 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001198 +705bonus pay for amazing work on #OSS 00010001198 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001199 +705bonus pay for amazing work on #OSS 00010001199 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001200 +705bonus pay for amazing work on #OSS 00010001200 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001201 +705bonus pay for amazing work on #OSS 00010001201 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001202 +705bonus pay for amazing work on #OSS 00010001202 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001203 +705bonus pay for amazing work on #OSS 00010001203 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001204 +705bonus pay for amazing work on #OSS 00010001204 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001205 +705bonus pay for amazing work on #OSS 00010001205 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001206 +705bonus pay for amazing work on #OSS 00010001206 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001207 +705bonus pay for amazing work on #OSS 00010001207 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001208 +705bonus pay for amazing work on #OSS 00010001208 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001209 +705bonus pay for amazing work on #OSS 00010001209 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001210 +705bonus pay for amazing work on #OSS 00010001210 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001211 +705bonus pay for amazing work on #OSS 00010001211 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001212 +705bonus pay for amazing work on #OSS 00010001212 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001213 +705bonus pay for amazing work on #OSS 00010001213 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001214 +705bonus pay for amazing work on #OSS 00010001214 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001215 +705bonus pay for amazing work on #OSS 00010001215 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001216 +705bonus pay for amazing work on #OSS 00010001216 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001217 +705bonus pay for amazing work on #OSS 00010001217 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001218 +705bonus pay for amazing work on #OSS 00010001218 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001219 +705bonus pay for amazing work on #OSS 00010001219 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001220 +705bonus pay for amazing work on #OSS 00010001220 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001221 +705bonus pay for amazing work on #OSS 00010001221 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001222 +705bonus pay for amazing work on #OSS 00010001222 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001223 +705bonus pay for amazing work on #OSS 00010001223 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001224 +705bonus pay for amazing work on #OSS 00010001224 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001225 +705bonus pay for amazing work on #OSS 00010001225 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001226 +705bonus pay for amazing work on #OSS 00010001226 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001227 +705bonus pay for amazing work on #OSS 00010001227 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001228 +705bonus pay for amazing work on #OSS 00010001228 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001229 +705bonus pay for amazing work on #OSS 00010001229 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001230 +705bonus pay for amazing work on #OSS 00010001230 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001231 +705bonus pay for amazing work on #OSS 00010001231 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001232 +705bonus pay for amazing work on #OSS 00010001232 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001233 +705bonus pay for amazing work on #OSS 00010001233 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001234 +705bonus pay for amazing work on #OSS 00010001234 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001235 +705bonus pay for amazing work on #OSS 00010001235 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001236 +705bonus pay for amazing work on #OSS 00010001236 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001237 +705bonus pay for amazing work on #OSS 00010001237 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001238 +705bonus pay for amazing work on #OSS 00010001238 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001239 +705bonus pay for amazing work on #OSS 00010001239 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001240 +705bonus pay for amazing work on #OSS 00010001240 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001241 +705bonus pay for amazing work on #OSS 00010001241 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001242 +705bonus pay for amazing work on #OSS 00010001242 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001243 +705bonus pay for amazing work on #OSS 00010001243 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001244 +705bonus pay for amazing work on #OSS 00010001244 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001245 +705bonus pay for amazing work on #OSS 00010001245 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001246 +705bonus pay for amazing work on #OSS 00010001246 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001247 +705bonus pay for amazing work on #OSS 00010001247 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001248 +705bonus pay for amazing work on #OSS 00010001248 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001249 +705bonus pay for amazing work on #OSS 00010001249 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001250 +705bonus pay for amazing work on #OSS 00010001250 +82000025008922512500000000000000000125000000121042882 121042880000003 +5200Wells Fargo 121042882 PPDTrans. Des 190301 1121042880000004 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000001 +705bonus pay for amazing work on #OSS 00010000001 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000002 +705bonus pay for amazing work on #OSS 00010000002 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000003 +705bonus pay for amazing work on #OSS 00010000003 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000004 +705bonus pay for amazing work on #OSS 00010000004 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000005 +705bonus pay for amazing work on #OSS 00010000005 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000006 +705bonus pay for amazing work on #OSS 00010000006 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000007 +705bonus pay for amazing work on #OSS 00010000007 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000008 +705bonus pay for amazing work on #OSS 00010000008 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000009 +705bonus pay for amazing work on #OSS 00010000009 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000010 +705bonus pay for amazing work on #OSS 00010000010 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000011 +705bonus pay for amazing work on #OSS 00010000011 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000012 +705bonus pay for amazing work on #OSS 00010000012 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000013 +705bonus pay for amazing work on #OSS 00010000013 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000014 +705bonus pay for amazing work on #OSS 00010000014 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000015 +705bonus pay for amazing work on #OSS 00010000015 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000016 +705bonus pay for amazing work on #OSS 00010000016 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000017 +705bonus pay for amazing work on #OSS 00010000017 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000018 +705bonus pay for amazing work on #OSS 00010000018 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000019 +705bonus pay for amazing work on #OSS 00010000019 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000020 +705bonus pay for amazing work on #OSS 00010000020 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000021 +705bonus pay for amazing work on #OSS 00010000021 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000022 +705bonus pay for amazing work on #OSS 00010000022 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000023 +705bonus pay for amazing work on #OSS 00010000023 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000024 +705bonus pay for amazing work on #OSS 00010000024 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000025 +705bonus pay for amazing work on #OSS 00010000025 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000026 +705bonus pay for amazing work on #OSS 00010000026 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000027 +705bonus pay for amazing work on #OSS 00010000027 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000028 +705bonus pay for amazing work on #OSS 00010000028 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000029 +705bonus pay for amazing work on #OSS 00010000029 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000030 +705bonus pay for amazing work on #OSS 00010000030 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000031 +705bonus pay for amazing work on #OSS 00010000031 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000032 +705bonus pay for amazing work on #OSS 00010000032 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000033 +705bonus pay for amazing work on #OSS 00010000033 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000034 +705bonus pay for amazing work on #OSS 00010000034 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000035 +705bonus pay for amazing work on #OSS 00010000035 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000036 +705bonus pay for amazing work on #OSS 00010000036 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000037 +705bonus pay for amazing work on #OSS 00010000037 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000038 +705bonus pay for amazing work on #OSS 00010000038 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000039 +705bonus pay for amazing work on #OSS 00010000039 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000040 +705bonus pay for amazing work on #OSS 00010000040 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000041 +705bonus pay for amazing work on #OSS 00010000041 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000042 +705bonus pay for amazing work on #OSS 00010000042 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000043 +705bonus pay for amazing work on #OSS 00010000043 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000044 +705bonus pay for amazing work on #OSS 00010000044 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000045 +705bonus pay for amazing work on #OSS 00010000045 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000046 +705bonus pay for amazing work on #OSS 00010000046 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000047 +705bonus pay for amazing work on #OSS 00010000047 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000048 +705bonus pay for amazing work on #OSS 00010000048 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000049 +705bonus pay for amazing work on #OSS 00010000049 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000050 +705bonus pay for amazing work on #OSS 00010000050 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000051 +705bonus pay for amazing work on #OSS 00010000051 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000052 +705bonus pay for amazing work on #OSS 00010000052 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000053 +705bonus pay for amazing work on #OSS 00010000053 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000054 +705bonus pay for amazing work on #OSS 00010000054 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000055 +705bonus pay for amazing work on #OSS 00010000055 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000056 +705bonus pay for amazing work on #OSS 00010000056 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000057 +705bonus pay for amazing work on #OSS 00010000057 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000058 +705bonus pay for amazing work on #OSS 00010000058 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000059 +705bonus pay for amazing work on #OSS 00010000059 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000060 +705bonus pay for amazing work on #OSS 00010000060 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000061 +705bonus pay for amazing work on #OSS 00010000061 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000062 +705bonus pay for amazing work on #OSS 00010000062 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000063 +705bonus pay for amazing work on #OSS 00010000063 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000064 +705bonus pay for amazing work on #OSS 00010000064 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000065 +705bonus pay for amazing work on #OSS 00010000065 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000066 +705bonus pay for amazing work on #OSS 00010000066 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000067 +705bonus pay for amazing work on #OSS 00010000067 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000068 +705bonus pay for amazing work on #OSS 00010000068 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000069 +705bonus pay for amazing work on #OSS 00010000069 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000070 +705bonus pay for amazing work on #OSS 00010000070 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000071 +705bonus pay for amazing work on #OSS 00010000071 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000072 +705bonus pay for amazing work on #OSS 00010000072 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000073 +705bonus pay for amazing work on #OSS 00010000073 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000074 +705bonus pay for amazing work on #OSS 00010000074 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000075 +705bonus pay for amazing work on #OSS 00010000075 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000076 +705bonus pay for amazing work on #OSS 00010000076 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000077 +705bonus pay for amazing work on #OSS 00010000077 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000078 +705bonus pay for amazing work on #OSS 00010000078 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000079 +705bonus pay for amazing work on #OSS 00010000079 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000080 +705bonus pay for amazing work on #OSS 00010000080 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000081 +705bonus pay for amazing work on #OSS 00010000081 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000082 +705bonus pay for amazing work on #OSS 00010000082 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000083 +705bonus pay for amazing work on #OSS 00010000083 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000084 +705bonus pay for amazing work on #OSS 00010000084 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000085 +705bonus pay for amazing work on #OSS 00010000085 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000086 +705bonus pay for amazing work on #OSS 00010000086 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000087 +705bonus pay for amazing work on #OSS 00010000087 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000088 +705bonus pay for amazing work on #OSS 00010000088 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000089 +705bonus pay for amazing work on #OSS 00010000089 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000090 +705bonus pay for amazing work on #OSS 00010000090 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000091 +705bonus pay for amazing work on #OSS 00010000091 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000092 +705bonus pay for amazing work on #OSS 00010000092 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000093 +705bonus pay for amazing work on #OSS 00010000093 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000094 +705bonus pay for amazing work on #OSS 00010000094 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000095 +705bonus pay for amazing work on #OSS 00010000095 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000096 +705bonus pay for amazing work on #OSS 00010000096 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000097 +705bonus pay for amazing work on #OSS 00010000097 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000098 +705bonus pay for amazing work on #OSS 00010000098 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000099 +705bonus pay for amazing work on #OSS 00010000099 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000100 +705bonus pay for amazing work on #OSS 00010000100 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000101 +705bonus pay for amazing work on #OSS 00010000101 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000102 +705bonus pay for amazing work on #OSS 00010000102 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000103 +705bonus pay for amazing work on #OSS 00010000103 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000104 +705bonus pay for amazing work on #OSS 00010000104 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000105 +705bonus pay for amazing work on #OSS 00010000105 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000106 +705bonus pay for amazing work on #OSS 00010000106 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000107 +705bonus pay for amazing work on #OSS 00010000107 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000108 +705bonus pay for amazing work on #OSS 00010000108 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000109 +705bonus pay for amazing work on #OSS 00010000109 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000110 +705bonus pay for amazing work on #OSS 00010000110 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000111 +705bonus pay for amazing work on #OSS 00010000111 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000112 +705bonus pay for amazing work on #OSS 00010000112 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000113 +705bonus pay for amazing work on #OSS 00010000113 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000114 +705bonus pay for amazing work on #OSS 00010000114 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000115 +705bonus pay for amazing work on #OSS 00010000115 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000116 +705bonus pay for amazing work on #OSS 00010000116 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000117 +705bonus pay for amazing work on #OSS 00010000117 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000118 +705bonus pay for amazing work on #OSS 00010000118 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000119 +705bonus pay for amazing work on #OSS 00010000119 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000120 +705bonus pay for amazing work on #OSS 00010000120 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000121 +705bonus pay for amazing work on #OSS 00010000121 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000122 +705bonus pay for amazing work on #OSS 00010000122 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000123 +705bonus pay for amazing work on #OSS 00010000123 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000124 +705bonus pay for amazing work on #OSS 00010000124 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000125 +705bonus pay for amazing work on #OSS 00010000125 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000126 +705bonus pay for amazing work on #OSS 00010000126 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000127 +705bonus pay for amazing work on #OSS 00010000127 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000128 +705bonus pay for amazing work on #OSS 00010000128 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000129 +705bonus pay for amazing work on #OSS 00010000129 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000130 +705bonus pay for amazing work on #OSS 00010000130 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000131 +705bonus pay for amazing work on #OSS 00010000131 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000132 +705bonus pay for amazing work on #OSS 00010000132 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000133 +705bonus pay for amazing work on #OSS 00010000133 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000134 +705bonus pay for amazing work on #OSS 00010000134 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000135 +705bonus pay for amazing work on #OSS 00010000135 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000136 +705bonus pay for amazing work on #OSS 00010000136 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000137 +705bonus pay for amazing work on #OSS 00010000137 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000138 +705bonus pay for amazing work on #OSS 00010000138 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000139 +705bonus pay for amazing work on #OSS 00010000139 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000140 +705bonus pay for amazing work on #OSS 00010000140 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000141 +705bonus pay for amazing work on #OSS 00010000141 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000142 +705bonus pay for amazing work on #OSS 00010000142 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000143 +705bonus pay for amazing work on #OSS 00010000143 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000144 +705bonus pay for amazing work on #OSS 00010000144 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000145 +705bonus pay for amazing work on #OSS 00010000145 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000146 +705bonus pay for amazing work on #OSS 00010000146 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000147 +705bonus pay for amazing work on #OSS 00010000147 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000148 +705bonus pay for amazing work on #OSS 00010000148 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000149 +705bonus pay for amazing work on #OSS 00010000149 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000150 +705bonus pay for amazing work on #OSS 00010000150 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000151 +705bonus pay for amazing work on #OSS 00010000151 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000152 +705bonus pay for amazing work on #OSS 00010000152 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000153 +705bonus pay for amazing work on #OSS 00010000153 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000154 +705bonus pay for amazing work on #OSS 00010000154 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000155 +705bonus pay for amazing work on #OSS 00010000155 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000156 +705bonus pay for amazing work on #OSS 00010000156 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000157 +705bonus pay for amazing work on #OSS 00010000157 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000158 +705bonus pay for amazing work on #OSS 00010000158 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000159 +705bonus pay for amazing work on #OSS 00010000159 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000160 +705bonus pay for amazing work on #OSS 00010000160 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000161 +705bonus pay for amazing work on #OSS 00010000161 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000162 +705bonus pay for amazing work on #OSS 00010000162 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000163 +705bonus pay for amazing work on #OSS 00010000163 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000164 +705bonus pay for amazing work on #OSS 00010000164 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000165 +705bonus pay for amazing work on #OSS 00010000165 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000166 +705bonus pay for amazing work on #OSS 00010000166 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000167 +705bonus pay for amazing work on #OSS 00010000167 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000168 +705bonus pay for amazing work on #OSS 00010000168 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000169 +705bonus pay for amazing work on #OSS 00010000169 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000170 +705bonus pay for amazing work on #OSS 00010000170 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000171 +705bonus pay for amazing work on #OSS 00010000171 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000172 +705bonus pay for amazing work on #OSS 00010000172 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000173 +705bonus pay for amazing work on #OSS 00010000173 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000174 +705bonus pay for amazing work on #OSS 00010000174 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000175 +705bonus pay for amazing work on #OSS 00010000175 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000176 +705bonus pay for amazing work on #OSS 00010000176 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000177 +705bonus pay for amazing work on #OSS 00010000177 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000178 +705bonus pay for amazing work on #OSS 00010000178 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000179 +705bonus pay for amazing work on #OSS 00010000179 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000180 +705bonus pay for amazing work on #OSS 00010000180 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000181 +705bonus pay for amazing work on #OSS 00010000181 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000182 +705bonus pay for amazing work on #OSS 00010000182 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000183 +705bonus pay for amazing work on #OSS 00010000183 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000184 +705bonus pay for amazing work on #OSS 00010000184 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000185 +705bonus pay for amazing work on #OSS 00010000185 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000186 +705bonus pay for amazing work on #OSS 00010000186 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000187 +705bonus pay for amazing work on #OSS 00010000187 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000188 +705bonus pay for amazing work on #OSS 00010000188 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000189 +705bonus pay for amazing work on #OSS 00010000189 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000190 +705bonus pay for amazing work on #OSS 00010000190 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000191 +705bonus pay for amazing work on #OSS 00010000191 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000192 +705bonus pay for amazing work on #OSS 00010000192 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000193 +705bonus pay for amazing work on #OSS 00010000193 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000194 +705bonus pay for amazing work on #OSS 00010000194 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000195 +705bonus pay for amazing work on #OSS 00010000195 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000196 +705bonus pay for amazing work on #OSS 00010000196 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000197 +705bonus pay for amazing work on #OSS 00010000197 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000198 +705bonus pay for amazing work on #OSS 00010000198 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000199 +705bonus pay for amazing work on #OSS 00010000199 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000200 +705bonus pay for amazing work on #OSS 00010000200 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000201 +705bonus pay for amazing work on #OSS 00010000201 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000202 +705bonus pay for amazing work on #OSS 00010000202 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000203 +705bonus pay for amazing work on #OSS 00010000203 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000204 +705bonus pay for amazing work on #OSS 00010000204 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000205 +705bonus pay for amazing work on #OSS 00010000205 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000206 +705bonus pay for amazing work on #OSS 00010000206 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000207 +705bonus pay for amazing work on #OSS 00010000207 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000208 +705bonus pay for amazing work on #OSS 00010000208 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000209 +705bonus pay for amazing work on #OSS 00010000209 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000210 +705bonus pay for amazing work on #OSS 00010000210 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000211 +705bonus pay for amazing work on #OSS 00010000211 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000212 +705bonus pay for amazing work on #OSS 00010000212 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000213 +705bonus pay for amazing work on #OSS 00010000213 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000214 +705bonus pay for amazing work on #OSS 00010000214 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000215 +705bonus pay for amazing work on #OSS 00010000215 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000216 +705bonus pay for amazing work on #OSS 00010000216 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000217 +705bonus pay for amazing work on #OSS 00010000217 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000218 +705bonus pay for amazing work on #OSS 00010000218 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000219 +705bonus pay for amazing work on #OSS 00010000219 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000220 +705bonus pay for amazing work on #OSS 00010000220 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000221 +705bonus pay for amazing work on #OSS 00010000221 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000222 +705bonus pay for amazing work on #OSS 00010000222 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000223 +705bonus pay for amazing work on #OSS 00010000223 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000224 +705bonus pay for amazing work on #OSS 00010000224 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000225 +705bonus pay for amazing work on #OSS 00010000225 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000226 +705bonus pay for amazing work on #OSS 00010000226 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000227 +705bonus pay for amazing work on #OSS 00010000227 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000228 +705bonus pay for amazing work on #OSS 00010000228 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000229 +705bonus pay for amazing work on #OSS 00010000229 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000230 +705bonus pay for amazing work on #OSS 00010000230 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000231 +705bonus pay for amazing work on #OSS 00010000231 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000232 +705bonus pay for amazing work on #OSS 00010000232 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000233 +705bonus pay for amazing work on #OSS 00010000233 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000234 +705bonus pay for amazing work on #OSS 00010000234 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000235 +705bonus pay for amazing work on #OSS 00010000235 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000236 +705bonus pay for amazing work on #OSS 00010000236 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000237 +705bonus pay for amazing work on #OSS 00010000237 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000238 +705bonus pay for amazing work on #OSS 00010000238 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000239 +705bonus pay for amazing work on #OSS 00010000239 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000240 +705bonus pay for amazing work on #OSS 00010000240 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000241 +705bonus pay for amazing work on #OSS 00010000241 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000242 +705bonus pay for amazing work on #OSS 00010000242 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000243 +705bonus pay for amazing work on #OSS 00010000243 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000244 +705bonus pay for amazing work on #OSS 00010000244 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000245 +705bonus pay for amazing work on #OSS 00010000245 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000246 +705bonus pay for amazing work on #OSS 00010000246 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000247 +705bonus pay for amazing work on #OSS 00010000247 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000248 +705bonus pay for amazing work on #OSS 00010000248 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000249 +705bonus pay for amazing work on #OSS 00010000249 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000250 +705bonus pay for amazing work on #OSS 00010000250 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000251 +705bonus pay for amazing work on #OSS 00010000251 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000252 +705bonus pay for amazing work on #OSS 00010000252 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000253 +705bonus pay for amazing work on #OSS 00010000253 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000254 +705bonus pay for amazing work on #OSS 00010000254 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000255 +705bonus pay for amazing work on #OSS 00010000255 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000256 +705bonus pay for amazing work on #OSS 00010000256 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000257 +705bonus pay for amazing work on #OSS 00010000257 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000258 +705bonus pay for amazing work on #OSS 00010000258 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000259 +705bonus pay for amazing work on #OSS 00010000259 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000260 +705bonus pay for amazing work on #OSS 00010000260 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000261 +705bonus pay for amazing work on #OSS 00010000261 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000262 +705bonus pay for amazing work on #OSS 00010000262 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000263 +705bonus pay for amazing work on #OSS 00010000263 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000264 +705bonus pay for amazing work on #OSS 00010000264 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000265 +705bonus pay for amazing work on #OSS 00010000265 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000266 +705bonus pay for amazing work on #OSS 00010000266 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000267 +705bonus pay for amazing work on #OSS 00010000267 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000268 +705bonus pay for amazing work on #OSS 00010000268 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000269 +705bonus pay for amazing work on #OSS 00010000269 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000270 +705bonus pay for amazing work on #OSS 00010000270 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000271 +705bonus pay for amazing work on #OSS 00010000271 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000272 +705bonus pay for amazing work on #OSS 00010000272 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000273 +705bonus pay for amazing work on #OSS 00010000273 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000274 +705bonus pay for amazing work on #OSS 00010000274 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000275 +705bonus pay for amazing work on #OSS 00010000275 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000276 +705bonus pay for amazing work on #OSS 00010000276 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000277 +705bonus pay for amazing work on #OSS 00010000277 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000278 +705bonus pay for amazing work on #OSS 00010000278 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000279 +705bonus pay for amazing work on #OSS 00010000279 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000280 +705bonus pay for amazing work on #OSS 00010000280 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000281 +705bonus pay for amazing work on #OSS 00010000281 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000282 +705bonus pay for amazing work on #OSS 00010000282 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000283 +705bonus pay for amazing work on #OSS 00010000283 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000284 +705bonus pay for amazing work on #OSS 00010000284 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000285 +705bonus pay for amazing work on #OSS 00010000285 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000286 +705bonus pay for amazing work on #OSS 00010000286 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000287 +705bonus pay for amazing work on #OSS 00010000287 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000288 +705bonus pay for amazing work on #OSS 00010000288 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000289 +705bonus pay for amazing work on #OSS 00010000289 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000290 +705bonus pay for amazing work on #OSS 00010000290 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000291 +705bonus pay for amazing work on #OSS 00010000291 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000292 +705bonus pay for amazing work on #OSS 00010000292 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000293 +705bonus pay for amazing work on #OSS 00010000293 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000294 +705bonus pay for amazing work on #OSS 00010000294 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000295 +705bonus pay for amazing work on #OSS 00010000295 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000296 +705bonus pay for amazing work on #OSS 00010000296 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000297 +705bonus pay for amazing work on #OSS 00010000297 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000298 +705bonus pay for amazing work on #OSS 00010000298 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000299 +705bonus pay for amazing work on #OSS 00010000299 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000300 +705bonus pay for amazing work on #OSS 00010000300 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000301 +705bonus pay for amazing work on #OSS 00010000301 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000302 +705bonus pay for amazing work on #OSS 00010000302 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000303 +705bonus pay for amazing work on #OSS 00010000303 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000304 +705bonus pay for amazing work on #OSS 00010000304 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000305 +705bonus pay for amazing work on #OSS 00010000305 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000306 +705bonus pay for amazing work on #OSS 00010000306 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000307 +705bonus pay for amazing work on #OSS 00010000307 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000308 +705bonus pay for amazing work on #OSS 00010000308 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000309 +705bonus pay for amazing work on #OSS 00010000309 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000310 +705bonus pay for amazing work on #OSS 00010000310 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000311 +705bonus pay for amazing work on #OSS 00010000311 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000312 +705bonus pay for amazing work on #OSS 00010000312 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000313 +705bonus pay for amazing work on #OSS 00010000313 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000314 +705bonus pay for amazing work on #OSS 00010000314 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000315 +705bonus pay for amazing work on #OSS 00010000315 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000316 +705bonus pay for amazing work on #OSS 00010000316 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000317 +705bonus pay for amazing work on #OSS 00010000317 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000318 +705bonus pay for amazing work on #OSS 00010000318 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000319 +705bonus pay for amazing work on #OSS 00010000319 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000320 +705bonus pay for amazing work on #OSS 00010000320 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000321 +705bonus pay for amazing work on #OSS 00010000321 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000322 +705bonus pay for amazing work on #OSS 00010000322 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000323 +705bonus pay for amazing work on #OSS 00010000323 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000324 +705bonus pay for amazing work on #OSS 00010000324 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000325 +705bonus pay for amazing work on #OSS 00010000325 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000326 +705bonus pay for amazing work on #OSS 00010000326 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000327 +705bonus pay for amazing work on #OSS 00010000327 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000328 +705bonus pay for amazing work on #OSS 00010000328 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000329 +705bonus pay for amazing work on #OSS 00010000329 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000330 +705bonus pay for amazing work on #OSS 00010000330 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000331 +705bonus pay for amazing work on #OSS 00010000331 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000332 +705bonus pay for amazing work on #OSS 00010000332 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000333 +705bonus pay for amazing work on #OSS 00010000333 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000334 +705bonus pay for amazing work on #OSS 00010000334 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000335 +705bonus pay for amazing work on #OSS 00010000335 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000336 +705bonus pay for amazing work on #OSS 00010000336 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000337 +705bonus pay for amazing work on #OSS 00010000337 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000338 +705bonus pay for amazing work on #OSS 00010000338 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000339 +705bonus pay for amazing work on #OSS 00010000339 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000340 +705bonus pay for amazing work on #OSS 00010000340 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000341 +705bonus pay for amazing work on #OSS 00010000341 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000342 +705bonus pay for amazing work on #OSS 00010000342 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000343 +705bonus pay for amazing work on #OSS 00010000343 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000344 +705bonus pay for amazing work on #OSS 00010000344 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000345 +705bonus pay for amazing work on #OSS 00010000345 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000346 +705bonus pay for amazing work on #OSS 00010000346 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000347 +705bonus pay for amazing work on #OSS 00010000347 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000348 +705bonus pay for amazing work on #OSS 00010000348 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000349 +705bonus pay for amazing work on #OSS 00010000349 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000350 +705bonus pay for amazing work on #OSS 00010000350 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000351 +705bonus pay for amazing work on #OSS 00010000351 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000352 +705bonus pay for amazing work on #OSS 00010000352 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000353 +705bonus pay for amazing work on #OSS 00010000353 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000354 +705bonus pay for amazing work on #OSS 00010000354 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000355 +705bonus pay for amazing work on #OSS 00010000355 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000356 +705bonus pay for amazing work on #OSS 00010000356 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000357 +705bonus pay for amazing work on #OSS 00010000357 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000358 +705bonus pay for amazing work on #OSS 00010000358 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000359 +705bonus pay for amazing work on #OSS 00010000359 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000360 +705bonus pay for amazing work on #OSS 00010000360 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000361 +705bonus pay for amazing work on #OSS 00010000361 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000362 +705bonus pay for amazing work on #OSS 00010000362 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000363 +705bonus pay for amazing work on #OSS 00010000363 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000364 +705bonus pay for amazing work on #OSS 00010000364 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000365 +705bonus pay for amazing work on #OSS 00010000365 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000366 +705bonus pay for amazing work on #OSS 00010000366 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000367 +705bonus pay for amazing work on #OSS 00010000367 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000368 +705bonus pay for amazing work on #OSS 00010000368 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000369 +705bonus pay for amazing work on #OSS 00010000369 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000370 +705bonus pay for amazing work on #OSS 00010000370 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000371 +705bonus pay for amazing work on #OSS 00010000371 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000372 +705bonus pay for amazing work on #OSS 00010000372 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000373 +705bonus pay for amazing work on #OSS 00010000373 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000374 +705bonus pay for amazing work on #OSS 00010000374 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000375 +705bonus pay for amazing work on #OSS 00010000375 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000376 +705bonus pay for amazing work on #OSS 00010000376 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000377 +705bonus pay for amazing work on #OSS 00010000377 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000378 +705bonus pay for amazing work on #OSS 00010000378 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000379 +705bonus pay for amazing work on #OSS 00010000379 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000380 +705bonus pay for amazing work on #OSS 00010000380 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000381 +705bonus pay for amazing work on #OSS 00010000381 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000382 +705bonus pay for amazing work on #OSS 00010000382 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000383 +705bonus pay for amazing work on #OSS 00010000383 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000384 +705bonus pay for amazing work on #OSS 00010000384 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000385 +705bonus pay for amazing work on #OSS 00010000385 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000386 +705bonus pay for amazing work on #OSS 00010000386 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000387 +705bonus pay for amazing work on #OSS 00010000387 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000388 +705bonus pay for amazing work on #OSS 00010000388 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000389 +705bonus pay for amazing work on #OSS 00010000389 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000390 +705bonus pay for amazing work on #OSS 00010000390 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000391 +705bonus pay for amazing work on #OSS 00010000391 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000392 +705bonus pay for amazing work on #OSS 00010000392 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000393 +705bonus pay for amazing work on #OSS 00010000393 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000394 +705bonus pay for amazing work on #OSS 00010000394 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000395 +705bonus pay for amazing work on #OSS 00010000395 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000396 +705bonus pay for amazing work on #OSS 00010000396 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000397 +705bonus pay for amazing work on #OSS 00010000397 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000398 +705bonus pay for amazing work on #OSS 00010000398 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000399 +705bonus pay for amazing work on #OSS 00010000399 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000400 +705bonus pay for amazing work on #OSS 00010000400 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000401 +705bonus pay for amazing work on #OSS 00010000401 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000402 +705bonus pay for amazing work on #OSS 00010000402 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000403 +705bonus pay for amazing work on #OSS 00010000403 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000404 +705bonus pay for amazing work on #OSS 00010000404 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000405 +705bonus pay for amazing work on #OSS 00010000405 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000406 +705bonus pay for amazing work on #OSS 00010000406 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000407 +705bonus pay for amazing work on #OSS 00010000407 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000408 +705bonus pay for amazing work on #OSS 00010000408 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000409 +705bonus pay for amazing work on #OSS 00010000409 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000410 +705bonus pay for amazing work on #OSS 00010000410 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000411 +705bonus pay for amazing work on #OSS 00010000411 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000412 +705bonus pay for amazing work on #OSS 00010000412 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000413 +705bonus pay for amazing work on #OSS 00010000413 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000414 +705bonus pay for amazing work on #OSS 00010000414 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000415 +705bonus pay for amazing work on #OSS 00010000415 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000416 +705bonus pay for amazing work on #OSS 00010000416 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000417 +705bonus pay for amazing work on #OSS 00010000417 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000418 +705bonus pay for amazing work on #OSS 00010000418 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000419 +705bonus pay for amazing work on #OSS 00010000419 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000420 +705bonus pay for amazing work on #OSS 00010000420 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000421 +705bonus pay for amazing work on #OSS 00010000421 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000422 +705bonus pay for amazing work on #OSS 00010000422 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000423 +705bonus pay for amazing work on #OSS 00010000423 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000424 +705bonus pay for amazing work on #OSS 00010000424 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000425 +705bonus pay for amazing work on #OSS 00010000425 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000426 +705bonus pay for amazing work on #OSS 00010000426 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000427 +705bonus pay for amazing work on #OSS 00010000427 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000428 +705bonus pay for amazing work on #OSS 00010000428 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000429 +705bonus pay for amazing work on #OSS 00010000429 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000430 +705bonus pay for amazing work on #OSS 00010000430 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000431 +705bonus pay for amazing work on #OSS 00010000431 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000432 +705bonus pay for amazing work on #OSS 00010000432 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000433 +705bonus pay for amazing work on #OSS 00010000433 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000434 +705bonus pay for amazing work on #OSS 00010000434 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000435 +705bonus pay for amazing work on #OSS 00010000435 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000436 +705bonus pay for amazing work on #OSS 00010000436 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000437 +705bonus pay for amazing work on #OSS 00010000437 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000438 +705bonus pay for amazing work on #OSS 00010000438 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000439 +705bonus pay for amazing work on #OSS 00010000439 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000440 +705bonus pay for amazing work on #OSS 00010000440 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000441 +705bonus pay for amazing work on #OSS 00010000441 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000442 +705bonus pay for amazing work on #OSS 00010000442 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000443 +705bonus pay for amazing work on #OSS 00010000443 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000444 +705bonus pay for amazing work on #OSS 00010000444 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000445 +705bonus pay for amazing work on #OSS 00010000445 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000446 +705bonus pay for amazing work on #OSS 00010000446 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000447 +705bonus pay for amazing work on #OSS 00010000447 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000448 +705bonus pay for amazing work on #OSS 00010000448 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000449 +705bonus pay for amazing work on #OSS 00010000449 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000450 +705bonus pay for amazing work on #OSS 00010000450 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000451 +705bonus pay for amazing work on #OSS 00010000451 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000452 +705bonus pay for amazing work on #OSS 00010000452 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000453 +705bonus pay for amazing work on #OSS 00010000453 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000454 +705bonus pay for amazing work on #OSS 00010000454 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000455 +705bonus pay for amazing work on #OSS 00010000455 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000456 +705bonus pay for amazing work on #OSS 00010000456 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000457 +705bonus pay for amazing work on #OSS 00010000457 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000458 +705bonus pay for amazing work on #OSS 00010000458 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000459 +705bonus pay for amazing work on #OSS 00010000459 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000460 +705bonus pay for amazing work on #OSS 00010000460 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000461 +705bonus pay for amazing work on #OSS 00010000461 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000462 +705bonus pay for amazing work on #OSS 00010000462 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000463 +705bonus pay for amazing work on #OSS 00010000463 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000464 +705bonus pay for amazing work on #OSS 00010000464 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000465 +705bonus pay for amazing work on #OSS 00010000465 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000466 +705bonus pay for amazing work on #OSS 00010000466 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000467 +705bonus pay for amazing work on #OSS 00010000467 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000468 +705bonus pay for amazing work on #OSS 00010000468 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000469 +705bonus pay for amazing work on #OSS 00010000469 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000470 +705bonus pay for amazing work on #OSS 00010000470 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000471 +705bonus pay for amazing work on #OSS 00010000471 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000472 +705bonus pay for amazing work on #OSS 00010000472 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000473 +705bonus pay for amazing work on #OSS 00010000473 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000474 +705bonus pay for amazing work on #OSS 00010000474 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000475 +705bonus pay for amazing work on #OSS 00010000475 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000476 +705bonus pay for amazing work on #OSS 00010000476 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000477 +705bonus pay for amazing work on #OSS 00010000477 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000478 +705bonus pay for amazing work on #OSS 00010000478 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000479 +705bonus pay for amazing work on #OSS 00010000479 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000480 +705bonus pay for amazing work on #OSS 00010000480 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000481 +705bonus pay for amazing work on #OSS 00010000481 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000482 +705bonus pay for amazing work on #OSS 00010000482 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000483 +705bonus pay for amazing work on #OSS 00010000483 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000484 +705bonus pay for amazing work on #OSS 00010000484 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000485 +705bonus pay for amazing work on #OSS 00010000485 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000486 +705bonus pay for amazing work on #OSS 00010000486 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000487 +705bonus pay for amazing work on #OSS 00010000487 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000488 +705bonus pay for amazing work on #OSS 00010000488 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000489 +705bonus pay for amazing work on #OSS 00010000489 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000490 +705bonus pay for amazing work on #OSS 00010000490 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000491 +705bonus pay for amazing work on #OSS 00010000491 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000492 +705bonus pay for amazing work on #OSS 00010000492 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000493 +705bonus pay for amazing work on #OSS 00010000493 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000494 +705bonus pay for amazing work on #OSS 00010000494 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000495 +705bonus pay for amazing work on #OSS 00010000495 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000496 +705bonus pay for amazing work on #OSS 00010000496 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000497 +705bonus pay for amazing work on #OSS 00010000497 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000498 +705bonus pay for amazing work on #OSS 00010000498 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000499 +705bonus pay for amazing work on #OSS 00010000499 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000500 +705bonus pay for amazing work on #OSS 00010000500 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000501 +705bonus pay for amazing work on #OSS 00010000501 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000502 +705bonus pay for amazing work on #OSS 00010000502 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000503 +705bonus pay for amazing work on #OSS 00010000503 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000504 +705bonus pay for amazing work on #OSS 00010000504 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000505 +705bonus pay for amazing work on #OSS 00010000505 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000506 +705bonus pay for amazing work on #OSS 00010000506 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000507 +705bonus pay for amazing work on #OSS 00010000507 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000508 +705bonus pay for amazing work on #OSS 00010000508 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000509 +705bonus pay for amazing work on #OSS 00010000509 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000510 +705bonus pay for amazing work on #OSS 00010000510 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000511 +705bonus pay for amazing work on #OSS 00010000511 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000512 +705bonus pay for amazing work on #OSS 00010000512 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000513 +705bonus pay for amazing work on #OSS 00010000513 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000514 +705bonus pay for amazing work on #OSS 00010000514 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000515 +705bonus pay for amazing work on #OSS 00010000515 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000516 +705bonus pay for amazing work on #OSS 00010000516 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000517 +705bonus pay for amazing work on #OSS 00010000517 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000518 +705bonus pay for amazing work on #OSS 00010000518 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000519 +705bonus pay for amazing work on #OSS 00010000519 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000520 +705bonus pay for amazing work on #OSS 00010000520 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000521 +705bonus pay for amazing work on #OSS 00010000521 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000522 +705bonus pay for amazing work on #OSS 00010000522 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000523 +705bonus pay for amazing work on #OSS 00010000523 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000524 +705bonus pay for amazing work on #OSS 00010000524 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000525 +705bonus pay for amazing work on #OSS 00010000525 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000526 +705bonus pay for amazing work on #OSS 00010000526 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000527 +705bonus pay for amazing work on #OSS 00010000527 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000528 +705bonus pay for amazing work on #OSS 00010000528 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000529 +705bonus pay for amazing work on #OSS 00010000529 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000530 +705bonus pay for amazing work on #OSS 00010000530 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000531 +705bonus pay for amazing work on #OSS 00010000531 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000532 +705bonus pay for amazing work on #OSS 00010000532 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000533 +705bonus pay for amazing work on #OSS 00010000533 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000534 +705bonus pay for amazing work on #OSS 00010000534 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000535 +705bonus pay for amazing work on #OSS 00010000535 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000536 +705bonus pay for amazing work on #OSS 00010000536 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000537 +705bonus pay for amazing work on #OSS 00010000537 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000538 +705bonus pay for amazing work on #OSS 00010000538 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000539 +705bonus pay for amazing work on #OSS 00010000539 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000540 +705bonus pay for amazing work on #OSS 00010000540 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000541 +705bonus pay for amazing work on #OSS 00010000541 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000542 +705bonus pay for amazing work on #OSS 00010000542 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000543 +705bonus pay for amazing work on #OSS 00010000543 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000544 +705bonus pay for amazing work on #OSS 00010000544 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000545 +705bonus pay for amazing work on #OSS 00010000545 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000546 +705bonus pay for amazing work on #OSS 00010000546 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000547 +705bonus pay for amazing work on #OSS 00010000547 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000548 +705bonus pay for amazing work on #OSS 00010000548 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000549 +705bonus pay for amazing work on #OSS 00010000549 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000550 +705bonus pay for amazing work on #OSS 00010000550 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000551 +705bonus pay for amazing work on #OSS 00010000551 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000552 +705bonus pay for amazing work on #OSS 00010000552 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000553 +705bonus pay for amazing work on #OSS 00010000553 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000554 +705bonus pay for amazing work on #OSS 00010000554 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000555 +705bonus pay for amazing work on #OSS 00010000555 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000556 +705bonus pay for amazing work on #OSS 00010000556 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000557 +705bonus pay for amazing work on #OSS 00010000557 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000558 +705bonus pay for amazing work on #OSS 00010000558 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000559 +705bonus pay for amazing work on #OSS 00010000559 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000560 +705bonus pay for amazing work on #OSS 00010000560 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000561 +705bonus pay for amazing work on #OSS 00010000561 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000562 +705bonus pay for amazing work on #OSS 00010000562 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000563 +705bonus pay for amazing work on #OSS 00010000563 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000564 +705bonus pay for amazing work on #OSS 00010000564 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000565 +705bonus pay for amazing work on #OSS 00010000565 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000566 +705bonus pay for amazing work on #OSS 00010000566 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000567 +705bonus pay for amazing work on #OSS 00010000567 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000568 +705bonus pay for amazing work on #OSS 00010000568 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000569 +705bonus pay for amazing work on #OSS 00010000569 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000570 +705bonus pay for amazing work on #OSS 00010000570 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000571 +705bonus pay for amazing work on #OSS 00010000571 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000572 +705bonus pay for amazing work on #OSS 00010000572 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000573 +705bonus pay for amazing work on #OSS 00010000573 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000574 +705bonus pay for amazing work on #OSS 00010000574 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000575 +705bonus pay for amazing work on #OSS 00010000575 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000576 +705bonus pay for amazing work on #OSS 00010000576 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000577 +705bonus pay for amazing work on #OSS 00010000577 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000578 +705bonus pay for amazing work on #OSS 00010000578 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000579 +705bonus pay for amazing work on #OSS 00010000579 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000580 +705bonus pay for amazing work on #OSS 00010000580 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000581 +705bonus pay for amazing work on #OSS 00010000581 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000582 +705bonus pay for amazing work on #OSS 00010000582 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000583 +705bonus pay for amazing work on #OSS 00010000583 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000584 +705bonus pay for amazing work on #OSS 00010000584 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000585 +705bonus pay for amazing work on #OSS 00010000585 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000586 +705bonus pay for amazing work on #OSS 00010000586 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000587 +705bonus pay for amazing work on #OSS 00010000587 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000588 +705bonus pay for amazing work on #OSS 00010000588 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000589 +705bonus pay for amazing work on #OSS 00010000589 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000590 +705bonus pay for amazing work on #OSS 00010000590 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000591 +705bonus pay for amazing work on #OSS 00010000591 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000592 +705bonus pay for amazing work on #OSS 00010000592 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000593 +705bonus pay for amazing work on #OSS 00010000593 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000594 +705bonus pay for amazing work on #OSS 00010000594 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000595 +705bonus pay for amazing work on #OSS 00010000595 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000596 +705bonus pay for amazing work on #OSS 00010000596 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000597 +705bonus pay for amazing work on #OSS 00010000597 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000598 +705bonus pay for amazing work on #OSS 00010000598 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000599 +705bonus pay for amazing work on #OSS 00010000599 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000600 +705bonus pay for amazing work on #OSS 00010000600 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000601 +705bonus pay for amazing work on #OSS 00010000601 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000602 +705bonus pay for amazing work on #OSS 00010000602 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000603 +705bonus pay for amazing work on #OSS 00010000603 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000604 +705bonus pay for amazing work on #OSS 00010000604 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000605 +705bonus pay for amazing work on #OSS 00010000605 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000606 +705bonus pay for amazing work on #OSS 00010000606 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000607 +705bonus pay for amazing work on #OSS 00010000607 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000608 +705bonus pay for amazing work on #OSS 00010000608 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000609 +705bonus pay for amazing work on #OSS 00010000609 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000610 +705bonus pay for amazing work on #OSS 00010000610 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000611 +705bonus pay for amazing work on #OSS 00010000611 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000612 +705bonus pay for amazing work on #OSS 00010000612 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000613 +705bonus pay for amazing work on #OSS 00010000613 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000614 +705bonus pay for amazing work on #OSS 00010000614 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000615 +705bonus pay for amazing work on #OSS 00010000615 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000616 +705bonus pay for amazing work on #OSS 00010000616 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000617 +705bonus pay for amazing work on #OSS 00010000617 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000618 +705bonus pay for amazing work on #OSS 00010000618 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000619 +705bonus pay for amazing work on #OSS 00010000619 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000620 +705bonus pay for amazing work on #OSS 00010000620 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000621 +705bonus pay for amazing work on #OSS 00010000621 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000622 +705bonus pay for amazing work on #OSS 00010000622 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000623 +705bonus pay for amazing work on #OSS 00010000623 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000624 +705bonus pay for amazing work on #OSS 00010000624 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000625 +705bonus pay for amazing work on #OSS 00010000625 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000626 +705bonus pay for amazing work on #OSS 00010000626 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000627 +705bonus pay for amazing work on #OSS 00010000627 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000628 +705bonus pay for amazing work on #OSS 00010000628 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000629 +705bonus pay for amazing work on #OSS 00010000629 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000630 +705bonus pay for amazing work on #OSS 00010000630 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000631 +705bonus pay for amazing work on #OSS 00010000631 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000632 +705bonus pay for amazing work on #OSS 00010000632 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000633 +705bonus pay for amazing work on #OSS 00010000633 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000634 +705bonus pay for amazing work on #OSS 00010000634 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000635 +705bonus pay for amazing work on #OSS 00010000635 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000636 +705bonus pay for amazing work on #OSS 00010000636 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000637 +705bonus pay for amazing work on #OSS 00010000637 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000638 +705bonus pay for amazing work on #OSS 00010000638 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000639 +705bonus pay for amazing work on #OSS 00010000639 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000640 +705bonus pay for amazing work on #OSS 00010000640 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000641 +705bonus pay for amazing work on #OSS 00010000641 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000642 +705bonus pay for amazing work on #OSS 00010000642 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000643 +705bonus pay for amazing work on #OSS 00010000643 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000644 +705bonus pay for amazing work on #OSS 00010000644 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000645 +705bonus pay for amazing work on #OSS 00010000645 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000646 +705bonus pay for amazing work on #OSS 00010000646 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000647 +705bonus pay for amazing work on #OSS 00010000647 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000648 +705bonus pay for amazing work on #OSS 00010000648 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000649 +705bonus pay for amazing work on #OSS 00010000649 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000650 +705bonus pay for amazing work on #OSS 00010000650 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000651 +705bonus pay for amazing work on #OSS 00010000651 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000652 +705bonus pay for amazing work on #OSS 00010000652 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000653 +705bonus pay for amazing work on #OSS 00010000653 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000654 +705bonus pay for amazing work on #OSS 00010000654 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000655 +705bonus pay for amazing work on #OSS 00010000655 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000656 +705bonus pay for amazing work on #OSS 00010000656 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000657 +705bonus pay for amazing work on #OSS 00010000657 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000658 +705bonus pay for amazing work on #OSS 00010000658 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000659 +705bonus pay for amazing work on #OSS 00010000659 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000660 +705bonus pay for amazing work on #OSS 00010000660 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000661 +705bonus pay for amazing work on #OSS 00010000661 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000662 +705bonus pay for amazing work on #OSS 00010000662 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000663 +705bonus pay for amazing work on #OSS 00010000663 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000664 +705bonus pay for amazing work on #OSS 00010000664 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000665 +705bonus pay for amazing work on #OSS 00010000665 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000666 +705bonus pay for amazing work on #OSS 00010000666 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000667 +705bonus pay for amazing work on #OSS 00010000667 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000668 +705bonus pay for amazing work on #OSS 00010000668 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000669 +705bonus pay for amazing work on #OSS 00010000669 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000670 +705bonus pay for amazing work on #OSS 00010000670 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000671 +705bonus pay for amazing work on #OSS 00010000671 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000672 +705bonus pay for amazing work on #OSS 00010000672 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000673 +705bonus pay for amazing work on #OSS 00010000673 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000674 +705bonus pay for amazing work on #OSS 00010000674 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000675 +705bonus pay for amazing work on #OSS 00010000675 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000676 +705bonus pay for amazing work on #OSS 00010000676 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000677 +705bonus pay for amazing work on #OSS 00010000677 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000678 +705bonus pay for amazing work on #OSS 00010000678 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000679 +705bonus pay for amazing work on #OSS 00010000679 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000680 +705bonus pay for amazing work on #OSS 00010000680 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000681 +705bonus pay for amazing work on #OSS 00010000681 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000682 +705bonus pay for amazing work on #OSS 00010000682 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000683 +705bonus pay for amazing work on #OSS 00010000683 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000684 +705bonus pay for amazing work on #OSS 00010000684 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000685 +705bonus pay for amazing work on #OSS 00010000685 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000686 +705bonus pay for amazing work on #OSS 00010000686 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000687 +705bonus pay for amazing work on #OSS 00010000687 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000688 +705bonus pay for amazing work on #OSS 00010000688 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000689 +705bonus pay for amazing work on #OSS 00010000689 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000690 +705bonus pay for amazing work on #OSS 00010000690 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000691 +705bonus pay for amazing work on #OSS 00010000691 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000692 +705bonus pay for amazing work on #OSS 00010000692 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000693 +705bonus pay for amazing work on #OSS 00010000693 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000694 +705bonus pay for amazing work on #OSS 00010000694 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000695 +705bonus pay for amazing work on #OSS 00010000695 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000696 +705bonus pay for amazing work on #OSS 00010000696 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000697 +705bonus pay for amazing work on #OSS 00010000697 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000698 +705bonus pay for amazing work on #OSS 00010000698 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000699 +705bonus pay for amazing work on #OSS 00010000699 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000700 +705bonus pay for amazing work on #OSS 00010000700 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000701 +705bonus pay for amazing work on #OSS 00010000701 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000702 +705bonus pay for amazing work on #OSS 00010000702 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000703 +705bonus pay for amazing work on #OSS 00010000703 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000704 +705bonus pay for amazing work on #OSS 00010000704 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000705 +705bonus pay for amazing work on #OSS 00010000705 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000706 +705bonus pay for amazing work on #OSS 00010000706 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000707 +705bonus pay for amazing work on #OSS 00010000707 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000708 +705bonus pay for amazing work on #OSS 00010000708 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000709 +705bonus pay for amazing work on #OSS 00010000709 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000710 +705bonus pay for amazing work on #OSS 00010000710 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000711 +705bonus pay for amazing work on #OSS 00010000711 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000712 +705bonus pay for amazing work on #OSS 00010000712 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000713 +705bonus pay for amazing work on #OSS 00010000713 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000714 +705bonus pay for amazing work on #OSS 00010000714 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000715 +705bonus pay for amazing work on #OSS 00010000715 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000716 +705bonus pay for amazing work on #OSS 00010000716 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000717 +705bonus pay for amazing work on #OSS 00010000717 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000718 +705bonus pay for amazing work on #OSS 00010000718 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000719 +705bonus pay for amazing work on #OSS 00010000719 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000720 +705bonus pay for amazing work on #OSS 00010000720 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000721 +705bonus pay for amazing work on #OSS 00010000721 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000722 +705bonus pay for amazing work on #OSS 00010000722 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000723 +705bonus pay for amazing work on #OSS 00010000723 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000724 +705bonus pay for amazing work on #OSS 00010000724 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000725 +705bonus pay for amazing work on #OSS 00010000725 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000726 +705bonus pay for amazing work on #OSS 00010000726 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000727 +705bonus pay for amazing work on #OSS 00010000727 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000728 +705bonus pay for amazing work on #OSS 00010000728 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000729 +705bonus pay for amazing work on #OSS 00010000729 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000730 +705bonus pay for amazing work on #OSS 00010000730 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000731 +705bonus pay for amazing work on #OSS 00010000731 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000732 +705bonus pay for amazing work on #OSS 00010000732 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000733 +705bonus pay for amazing work on #OSS 00010000733 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000734 +705bonus pay for amazing work on #OSS 00010000734 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000735 +705bonus pay for amazing work on #OSS 00010000735 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000736 +705bonus pay for amazing work on #OSS 00010000736 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000737 +705bonus pay for amazing work on #OSS 00010000737 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000738 +705bonus pay for amazing work on #OSS 00010000738 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000739 +705bonus pay for amazing work on #OSS 00010000739 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000740 +705bonus pay for amazing work on #OSS 00010000740 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000741 +705bonus pay for amazing work on #OSS 00010000741 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000742 +705bonus pay for amazing work on #OSS 00010000742 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000743 +705bonus pay for amazing work on #OSS 00010000743 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000744 +705bonus pay for amazing work on #OSS 00010000744 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000745 +705bonus pay for amazing work on #OSS 00010000745 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000746 +705bonus pay for amazing work on #OSS 00010000746 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000747 +705bonus pay for amazing work on #OSS 00010000747 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000748 +705bonus pay for amazing work on #OSS 00010000748 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000749 +705bonus pay for amazing work on #OSS 00010000749 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000750 +705bonus pay for amazing work on #OSS 00010000750 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000751 +705bonus pay for amazing work on #OSS 00010000751 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000752 +705bonus pay for amazing work on #OSS 00010000752 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000753 +705bonus pay for amazing work on #OSS 00010000753 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000754 +705bonus pay for amazing work on #OSS 00010000754 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000755 +705bonus pay for amazing work on #OSS 00010000755 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000756 +705bonus pay for amazing work on #OSS 00010000756 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000757 +705bonus pay for amazing work on #OSS 00010000757 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000758 +705bonus pay for amazing work on #OSS 00010000758 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000759 +705bonus pay for amazing work on #OSS 00010000759 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000760 +705bonus pay for amazing work on #OSS 00010000760 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000761 +705bonus pay for amazing work on #OSS 00010000761 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000762 +705bonus pay for amazing work on #OSS 00010000762 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000763 +705bonus pay for amazing work on #OSS 00010000763 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000764 +705bonus pay for amazing work on #OSS 00010000764 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000765 +705bonus pay for amazing work on #OSS 00010000765 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000766 +705bonus pay for amazing work on #OSS 00010000766 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000767 +705bonus pay for amazing work on #OSS 00010000767 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000768 +705bonus pay for amazing work on #OSS 00010000768 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000769 +705bonus pay for amazing work on #OSS 00010000769 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000770 +705bonus pay for amazing work on #OSS 00010000770 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000771 +705bonus pay for amazing work on #OSS 00010000771 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000772 +705bonus pay for amazing work on #OSS 00010000772 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000773 +705bonus pay for amazing work on #OSS 00010000773 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000774 +705bonus pay for amazing work on #OSS 00010000774 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000775 +705bonus pay for amazing work on #OSS 00010000775 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000776 +705bonus pay for amazing work on #OSS 00010000776 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000777 +705bonus pay for amazing work on #OSS 00010000777 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000778 +705bonus pay for amazing work on #OSS 00010000778 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000779 +705bonus pay for amazing work on #OSS 00010000779 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000780 +705bonus pay for amazing work on #OSS 00010000780 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000781 +705bonus pay for amazing work on #OSS 00010000781 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000782 +705bonus pay for amazing work on #OSS 00010000782 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000783 +705bonus pay for amazing work on #OSS 00010000783 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000784 +705bonus pay for amazing work on #OSS 00010000784 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000785 +705bonus pay for amazing work on #OSS 00010000785 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000786 +705bonus pay for amazing work on #OSS 00010000786 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000787 +705bonus pay for amazing work on #OSS 00010000787 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000788 +705bonus pay for amazing work on #OSS 00010000788 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000789 +705bonus pay for amazing work on #OSS 00010000789 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000790 +705bonus pay for amazing work on #OSS 00010000790 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000791 +705bonus pay for amazing work on #OSS 00010000791 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000792 +705bonus pay for amazing work on #OSS 00010000792 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000793 +705bonus pay for amazing work on #OSS 00010000793 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000794 +705bonus pay for amazing work on #OSS 00010000794 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000795 +705bonus pay for amazing work on #OSS 00010000795 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000796 +705bonus pay for amazing work on #OSS 00010000796 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000797 +705bonus pay for amazing work on #OSS 00010000797 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000798 +705bonus pay for amazing work on #OSS 00010000798 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000799 +705bonus pay for amazing work on #OSS 00010000799 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000800 +705bonus pay for amazing work on #OSS 00010000800 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000801 +705bonus pay for amazing work on #OSS 00010000801 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000802 +705bonus pay for amazing work on #OSS 00010000802 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000803 +705bonus pay for amazing work on #OSS 00010000803 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000804 +705bonus pay for amazing work on #OSS 00010000804 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000805 +705bonus pay for amazing work on #OSS 00010000805 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000806 +705bonus pay for amazing work on #OSS 00010000806 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000807 +705bonus pay for amazing work on #OSS 00010000807 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000808 +705bonus pay for amazing work on #OSS 00010000808 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000809 +705bonus pay for amazing work on #OSS 00010000809 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000810 +705bonus pay for amazing work on #OSS 00010000810 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000811 +705bonus pay for amazing work on #OSS 00010000811 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000812 +705bonus pay for amazing work on #OSS 00010000812 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000813 +705bonus pay for amazing work on #OSS 00010000813 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000814 +705bonus pay for amazing work on #OSS 00010000814 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000815 +705bonus pay for amazing work on #OSS 00010000815 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000816 +705bonus pay for amazing work on #OSS 00010000816 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000817 +705bonus pay for amazing work on #OSS 00010000817 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000818 +705bonus pay for amazing work on #OSS 00010000818 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000819 +705bonus pay for amazing work on #OSS 00010000819 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000820 +705bonus pay for amazing work on #OSS 00010000820 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000821 +705bonus pay for amazing work on #OSS 00010000821 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000822 +705bonus pay for amazing work on #OSS 00010000822 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000823 +705bonus pay for amazing work on #OSS 00010000823 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000824 +705bonus pay for amazing work on #OSS 00010000824 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000825 +705bonus pay for amazing work on #OSS 00010000825 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000826 +705bonus pay for amazing work on #OSS 00010000826 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000827 +705bonus pay for amazing work on #OSS 00010000827 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000828 +705bonus pay for amazing work on #OSS 00010000828 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000829 +705bonus pay for amazing work on #OSS 00010000829 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000830 +705bonus pay for amazing work on #OSS 00010000830 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000831 +705bonus pay for amazing work on #OSS 00010000831 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000832 +705bonus pay for amazing work on #OSS 00010000832 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000833 +705bonus pay for amazing work on #OSS 00010000833 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000834 +705bonus pay for amazing work on #OSS 00010000834 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000835 +705bonus pay for amazing work on #OSS 00010000835 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000836 +705bonus pay for amazing work on #OSS 00010000836 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000837 +705bonus pay for amazing work on #OSS 00010000837 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000838 +705bonus pay for amazing work on #OSS 00010000838 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000839 +705bonus pay for amazing work on #OSS 00010000839 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000840 +705bonus pay for amazing work on #OSS 00010000840 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000841 +705bonus pay for amazing work on #OSS 00010000841 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000842 +705bonus pay for amazing work on #OSS 00010000842 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000843 +705bonus pay for amazing work on #OSS 00010000843 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000844 +705bonus pay for amazing work on #OSS 00010000844 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000845 +705bonus pay for amazing work on #OSS 00010000845 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000846 +705bonus pay for amazing work on #OSS 00010000846 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000847 +705bonus pay for amazing work on #OSS 00010000847 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000848 +705bonus pay for amazing work on #OSS 00010000848 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000849 +705bonus pay for amazing work on #OSS 00010000849 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000850 +705bonus pay for amazing work on #OSS 00010000850 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000851 +705bonus pay for amazing work on #OSS 00010000851 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000852 +705bonus pay for amazing work on #OSS 00010000852 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000853 +705bonus pay for amazing work on #OSS 00010000853 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000854 +705bonus pay for amazing work on #OSS 00010000854 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000855 +705bonus pay for amazing work on #OSS 00010000855 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000856 +705bonus pay for amazing work on #OSS 00010000856 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000857 +705bonus pay for amazing work on #OSS 00010000857 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000858 +705bonus pay for amazing work on #OSS 00010000858 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000859 +705bonus pay for amazing work on #OSS 00010000859 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000860 +705bonus pay for amazing work on #OSS 00010000860 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000861 +705bonus pay for amazing work on #OSS 00010000861 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000862 +705bonus pay for amazing work on #OSS 00010000862 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000863 +705bonus pay for amazing work on #OSS 00010000863 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000864 +705bonus pay for amazing work on #OSS 00010000864 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000865 +705bonus pay for amazing work on #OSS 00010000865 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000866 +705bonus pay for amazing work on #OSS 00010000866 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000867 +705bonus pay for amazing work on #OSS 00010000867 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000868 +705bonus pay for amazing work on #OSS 00010000868 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000869 +705bonus pay for amazing work on #OSS 00010000869 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000870 +705bonus pay for amazing work on #OSS 00010000870 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000871 +705bonus pay for amazing work on #OSS 00010000871 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000872 +705bonus pay for amazing work on #OSS 00010000872 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000873 +705bonus pay for amazing work on #OSS 00010000873 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000874 +705bonus pay for amazing work on #OSS 00010000874 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000875 +705bonus pay for amazing work on #OSS 00010000875 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000876 +705bonus pay for amazing work on #OSS 00010000876 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000877 +705bonus pay for amazing work on #OSS 00010000877 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000878 +705bonus pay for amazing work on #OSS 00010000878 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000879 +705bonus pay for amazing work on #OSS 00010000879 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000880 +705bonus pay for amazing work on #OSS 00010000880 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000881 +705bonus pay for amazing work on #OSS 00010000881 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000882 +705bonus pay for amazing work on #OSS 00010000882 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000883 +705bonus pay for amazing work on #OSS 00010000883 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000884 +705bonus pay for amazing work on #OSS 00010000884 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000885 +705bonus pay for amazing work on #OSS 00010000885 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000886 +705bonus pay for amazing work on #OSS 00010000886 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000887 +705bonus pay for amazing work on #OSS 00010000887 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000888 +705bonus pay for amazing work on #OSS 00010000888 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000889 +705bonus pay for amazing work on #OSS 00010000889 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000890 +705bonus pay for amazing work on #OSS 00010000890 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000891 +705bonus pay for amazing work on #OSS 00010000891 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000892 +705bonus pay for amazing work on #OSS 00010000892 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000893 +705bonus pay for amazing work on #OSS 00010000893 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000894 +705bonus pay for amazing work on #OSS 00010000894 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000895 +705bonus pay for amazing work on #OSS 00010000895 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000896 +705bonus pay for amazing work on #OSS 00010000896 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000897 +705bonus pay for amazing work on #OSS 00010000897 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000898 +705bonus pay for amazing work on #OSS 00010000898 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000899 +705bonus pay for amazing work on #OSS 00010000899 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000900 +705bonus pay for amazing work on #OSS 00010000900 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000901 +705bonus pay for amazing work on #OSS 00010000901 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000902 +705bonus pay for amazing work on #OSS 00010000902 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000903 +705bonus pay for amazing work on #OSS 00010000903 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000904 +705bonus pay for amazing work on #OSS 00010000904 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000905 +705bonus pay for amazing work on #OSS 00010000905 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000906 +705bonus pay for amazing work on #OSS 00010000906 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000907 +705bonus pay for amazing work on #OSS 00010000907 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000908 +705bonus pay for amazing work on #OSS 00010000908 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000909 +705bonus pay for amazing work on #OSS 00010000909 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000910 +705bonus pay for amazing work on #OSS 00010000910 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000911 +705bonus pay for amazing work on #OSS 00010000911 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000912 +705bonus pay for amazing work on #OSS 00010000912 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000913 +705bonus pay for amazing work on #OSS 00010000913 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000914 +705bonus pay for amazing work on #OSS 00010000914 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000915 +705bonus pay for amazing work on #OSS 00010000915 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000916 +705bonus pay for amazing work on #OSS 00010000916 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000917 +705bonus pay for amazing work on #OSS 00010000917 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000918 +705bonus pay for amazing work on #OSS 00010000918 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000919 +705bonus pay for amazing work on #OSS 00010000919 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000920 +705bonus pay for amazing work on #OSS 00010000920 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000921 +705bonus pay for amazing work on #OSS 00010000921 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000922 +705bonus pay for amazing work on #OSS 00010000922 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000923 +705bonus pay for amazing work on #OSS 00010000923 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000924 +705bonus pay for amazing work on #OSS 00010000924 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000925 +705bonus pay for amazing work on #OSS 00010000925 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000926 +705bonus pay for amazing work on #OSS 00010000926 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000927 +705bonus pay for amazing work on #OSS 00010000927 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000928 +705bonus pay for amazing work on #OSS 00010000928 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000929 +705bonus pay for amazing work on #OSS 00010000929 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000930 +705bonus pay for amazing work on #OSS 00010000930 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000931 +705bonus pay for amazing work on #OSS 00010000931 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000932 +705bonus pay for amazing work on #OSS 00010000932 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000933 +705bonus pay for amazing work on #OSS 00010000933 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000934 +705bonus pay for amazing work on #OSS 00010000934 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000935 +705bonus pay for amazing work on #OSS 00010000935 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000936 +705bonus pay for amazing work on #OSS 00010000936 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000937 +705bonus pay for amazing work on #OSS 00010000937 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000938 +705bonus pay for amazing work on #OSS 00010000938 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000939 +705bonus pay for amazing work on #OSS 00010000939 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000940 +705bonus pay for amazing work on #OSS 00010000940 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000941 +705bonus pay for amazing work on #OSS 00010000941 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000942 +705bonus pay for amazing work on #OSS 00010000942 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000943 +705bonus pay for amazing work on #OSS 00010000943 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000944 +705bonus pay for amazing work on #OSS 00010000944 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000945 +705bonus pay for amazing work on #OSS 00010000945 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000946 +705bonus pay for amazing work on #OSS 00010000946 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000947 +705bonus pay for amazing work on #OSS 00010000947 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000948 +705bonus pay for amazing work on #OSS 00010000948 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000949 +705bonus pay for amazing work on #OSS 00010000949 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000950 +705bonus pay for amazing work on #OSS 00010000950 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000951 +705bonus pay for amazing work on #OSS 00010000951 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000952 +705bonus pay for amazing work on #OSS 00010000952 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000953 +705bonus pay for amazing work on #OSS 00010000953 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000954 +705bonus pay for amazing work on #OSS 00010000954 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000955 +705bonus pay for amazing work on #OSS 00010000955 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000956 +705bonus pay for amazing work on #OSS 00010000956 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000957 +705bonus pay for amazing work on #OSS 00010000957 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000958 +705bonus pay for amazing work on #OSS 00010000958 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000959 +705bonus pay for amazing work on #OSS 00010000959 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000960 +705bonus pay for amazing work on #OSS 00010000960 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000961 +705bonus pay for amazing work on #OSS 00010000961 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000962 +705bonus pay for amazing work on #OSS 00010000962 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000963 +705bonus pay for amazing work on #OSS 00010000963 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000964 +705bonus pay for amazing work on #OSS 00010000964 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000965 +705bonus pay for amazing work on #OSS 00010000965 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000966 +705bonus pay for amazing work on #OSS 00010000966 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000967 +705bonus pay for amazing work on #OSS 00010000967 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000968 +705bonus pay for amazing work on #OSS 00010000968 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000969 +705bonus pay for amazing work on #OSS 00010000969 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000970 +705bonus pay for amazing work on #OSS 00010000970 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000971 +705bonus pay for amazing work on #OSS 00010000971 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000972 +705bonus pay for amazing work on #OSS 00010000972 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000973 +705bonus pay for amazing work on #OSS 00010000973 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000974 +705bonus pay for amazing work on #OSS 00010000974 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000975 +705bonus pay for amazing work on #OSS 00010000975 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000976 +705bonus pay for amazing work on #OSS 00010000976 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000977 +705bonus pay for amazing work on #OSS 00010000977 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000978 +705bonus pay for amazing work on #OSS 00010000978 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000979 +705bonus pay for amazing work on #OSS 00010000979 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000980 +705bonus pay for amazing work on #OSS 00010000980 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000981 +705bonus pay for amazing work on #OSS 00010000981 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000982 +705bonus pay for amazing work on #OSS 00010000982 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000983 +705bonus pay for amazing work on #OSS 00010000983 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000984 +705bonus pay for amazing work on #OSS 00010000984 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000985 +705bonus pay for amazing work on #OSS 00010000985 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000986 +705bonus pay for amazing work on #OSS 00010000986 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000987 +705bonus pay for amazing work on #OSS 00010000987 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000988 +705bonus pay for amazing work on #OSS 00010000988 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000989 +705bonus pay for amazing work on #OSS 00010000989 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000990 +705bonus pay for amazing work on #OSS 00010000990 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000991 +705bonus pay for amazing work on #OSS 00010000991 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000992 +705bonus pay for amazing work on #OSS 00010000992 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000993 +705bonus pay for amazing work on #OSS 00010000993 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000994 +705bonus pay for amazing work on #OSS 00010000994 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000995 +705bonus pay for amazing work on #OSS 00010000995 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000996 +705bonus pay for amazing work on #OSS 00010000996 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000997 +705bonus pay for amazing work on #OSS 00010000997 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000998 +705bonus pay for amazing work on #OSS 00010000998 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000999 +705bonus pay for amazing work on #OSS 00010000999 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001000 +705bonus pay for amazing work on #OSS 00010001000 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001001 +705bonus pay for amazing work on #OSS 00010001001 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001002 +705bonus pay for amazing work on #OSS 00010001002 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001003 +705bonus pay for amazing work on #OSS 00010001003 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001004 +705bonus pay for amazing work on #OSS 00010001004 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001005 +705bonus pay for amazing work on #OSS 00010001005 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001006 +705bonus pay for amazing work on #OSS 00010001006 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001007 +705bonus pay for amazing work on #OSS 00010001007 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001008 +705bonus pay for amazing work on #OSS 00010001008 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001009 +705bonus pay for amazing work on #OSS 00010001009 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001010 +705bonus pay for amazing work on #OSS 00010001010 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001011 +705bonus pay for amazing work on #OSS 00010001011 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001012 +705bonus pay for amazing work on #OSS 00010001012 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001013 +705bonus pay for amazing work on #OSS 00010001013 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001014 +705bonus pay for amazing work on #OSS 00010001014 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001015 +705bonus pay for amazing work on #OSS 00010001015 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001016 +705bonus pay for amazing work on #OSS 00010001016 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001017 +705bonus pay for amazing work on #OSS 00010001017 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001018 +705bonus pay for amazing work on #OSS 00010001018 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001019 +705bonus pay for amazing work on #OSS 00010001019 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001020 +705bonus pay for amazing work on #OSS 00010001020 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001021 +705bonus pay for amazing work on #OSS 00010001021 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001022 +705bonus pay for amazing work on #OSS 00010001022 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001023 +705bonus pay for amazing work on #OSS 00010001023 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001024 +705bonus pay for amazing work on #OSS 00010001024 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001025 +705bonus pay for amazing work on #OSS 00010001025 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001026 +705bonus pay for amazing work on #OSS 00010001026 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001027 +705bonus pay for amazing work on #OSS 00010001027 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001028 +705bonus pay for amazing work on #OSS 00010001028 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001029 +705bonus pay for amazing work on #OSS 00010001029 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001030 +705bonus pay for amazing work on #OSS 00010001030 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001031 +705bonus pay for amazing work on #OSS 00010001031 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001032 +705bonus pay for amazing work on #OSS 00010001032 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001033 +705bonus pay for amazing work on #OSS 00010001033 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001034 +705bonus pay for amazing work on #OSS 00010001034 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001035 +705bonus pay for amazing work on #OSS 00010001035 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001036 +705bonus pay for amazing work on #OSS 00010001036 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001037 +705bonus pay for amazing work on #OSS 00010001037 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001038 +705bonus pay for amazing work on #OSS 00010001038 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001039 +705bonus pay for amazing work on #OSS 00010001039 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001040 +705bonus pay for amazing work on #OSS 00010001040 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001041 +705bonus pay for amazing work on #OSS 00010001041 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001042 +705bonus pay for amazing work on #OSS 00010001042 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001043 +705bonus pay for amazing work on #OSS 00010001043 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001044 +705bonus pay for amazing work on #OSS 00010001044 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001045 +705bonus pay for amazing work on #OSS 00010001045 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001046 +705bonus pay for amazing work on #OSS 00010001046 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001047 +705bonus pay for amazing work on #OSS 00010001047 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001048 +705bonus pay for amazing work on #OSS 00010001048 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001049 +705bonus pay for amazing work on #OSS 00010001049 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001050 +705bonus pay for amazing work on #OSS 00010001050 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001051 +705bonus pay for amazing work on #OSS 00010001051 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001052 +705bonus pay for amazing work on #OSS 00010001052 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001053 +705bonus pay for amazing work on #OSS 00010001053 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001054 +705bonus pay for amazing work on #OSS 00010001054 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001055 +705bonus pay for amazing work on #OSS 00010001055 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001056 +705bonus pay for amazing work on #OSS 00010001056 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001057 +705bonus pay for amazing work on #OSS 00010001057 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001058 +705bonus pay for amazing work on #OSS 00010001058 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001059 +705bonus pay for amazing work on #OSS 00010001059 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001060 +705bonus pay for amazing work on #OSS 00010001060 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001061 +705bonus pay for amazing work on #OSS 00010001061 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001062 +705bonus pay for amazing work on #OSS 00010001062 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001063 +705bonus pay for amazing work on #OSS 00010001063 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001064 +705bonus pay for amazing work on #OSS 00010001064 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001065 +705bonus pay for amazing work on #OSS 00010001065 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001066 +705bonus pay for amazing work on #OSS 00010001066 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001067 +705bonus pay for amazing work on #OSS 00010001067 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001068 +705bonus pay for amazing work on #OSS 00010001068 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001069 +705bonus pay for amazing work on #OSS 00010001069 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001070 +705bonus pay for amazing work on #OSS 00010001070 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001071 +705bonus pay for amazing work on #OSS 00010001071 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001072 +705bonus pay for amazing work on #OSS 00010001072 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001073 +705bonus pay for amazing work on #OSS 00010001073 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001074 +705bonus pay for amazing work on #OSS 00010001074 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001075 +705bonus pay for amazing work on #OSS 00010001075 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001076 +705bonus pay for amazing work on #OSS 00010001076 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001077 +705bonus pay for amazing work on #OSS 00010001077 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001078 +705bonus pay for amazing work on #OSS 00010001078 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001079 +705bonus pay for amazing work on #OSS 00010001079 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001080 +705bonus pay for amazing work on #OSS 00010001080 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001081 +705bonus pay for amazing work on #OSS 00010001081 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001082 +705bonus pay for amazing work on #OSS 00010001082 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001083 +705bonus pay for amazing work on #OSS 00010001083 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001084 +705bonus pay for amazing work on #OSS 00010001084 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001085 +705bonus pay for amazing work on #OSS 00010001085 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001086 +705bonus pay for amazing work on #OSS 00010001086 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001087 +705bonus pay for amazing work on #OSS 00010001087 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001088 +705bonus pay for amazing work on #OSS 00010001088 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001089 +705bonus pay for amazing work on #OSS 00010001089 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001090 +705bonus pay for amazing work on #OSS 00010001090 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001091 +705bonus pay for amazing work on #OSS 00010001091 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001092 +705bonus pay for amazing work on #OSS 00010001092 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001093 +705bonus pay for amazing work on #OSS 00010001093 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001094 +705bonus pay for amazing work on #OSS 00010001094 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001095 +705bonus pay for amazing work on #OSS 00010001095 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001096 +705bonus pay for amazing work on #OSS 00010001096 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001097 +705bonus pay for amazing work on #OSS 00010001097 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001098 +705bonus pay for amazing work on #OSS 00010001098 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001099 +705bonus pay for amazing work on #OSS 00010001099 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001100 +705bonus pay for amazing work on #OSS 00010001100 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001101 +705bonus pay for amazing work on #OSS 00010001101 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001102 +705bonus pay for amazing work on #OSS 00010001102 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001103 +705bonus pay for amazing work on #OSS 00010001103 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001104 +705bonus pay for amazing work on #OSS 00010001104 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001105 +705bonus pay for amazing work on #OSS 00010001105 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001106 +705bonus pay for amazing work on #OSS 00010001106 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001107 +705bonus pay for amazing work on #OSS 00010001107 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001108 +705bonus pay for amazing work on #OSS 00010001108 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001109 +705bonus pay for amazing work on #OSS 00010001109 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001110 +705bonus pay for amazing work on #OSS 00010001110 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001111 +705bonus pay for amazing work on #OSS 00010001111 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001112 +705bonus pay for amazing work on #OSS 00010001112 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001113 +705bonus pay for amazing work on #OSS 00010001113 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001114 +705bonus pay for amazing work on #OSS 00010001114 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001115 +705bonus pay for amazing work on #OSS 00010001115 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001116 +705bonus pay for amazing work on #OSS 00010001116 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001117 +705bonus pay for amazing work on #OSS 00010001117 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001118 +705bonus pay for amazing work on #OSS 00010001118 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001119 +705bonus pay for amazing work on #OSS 00010001119 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001120 +705bonus pay for amazing work on #OSS 00010001120 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001121 +705bonus pay for amazing work on #OSS 00010001121 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001122 +705bonus pay for amazing work on #OSS 00010001122 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001123 +705bonus pay for amazing work on #OSS 00010001123 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001124 +705bonus pay for amazing work on #OSS 00010001124 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001125 +705bonus pay for amazing work on #OSS 00010001125 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001126 +705bonus pay for amazing work on #OSS 00010001126 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001127 +705bonus pay for amazing work on #OSS 00010001127 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001128 +705bonus pay for amazing work on #OSS 00010001128 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001129 +705bonus pay for amazing work on #OSS 00010001129 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001130 +705bonus pay for amazing work on #OSS 00010001130 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001131 +705bonus pay for amazing work on #OSS 00010001131 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001132 +705bonus pay for amazing work on #OSS 00010001132 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001133 +705bonus pay for amazing work on #OSS 00010001133 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001134 +705bonus pay for amazing work on #OSS 00010001134 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001135 +705bonus pay for amazing work on #OSS 00010001135 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001136 +705bonus pay for amazing work on #OSS 00010001136 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001137 +705bonus pay for amazing work on #OSS 00010001137 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001138 +705bonus pay for amazing work on #OSS 00010001138 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001139 +705bonus pay for amazing work on #OSS 00010001139 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001140 +705bonus pay for amazing work on #OSS 00010001140 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001141 +705bonus pay for amazing work on #OSS 00010001141 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001142 +705bonus pay for amazing work on #OSS 00010001142 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001143 +705bonus pay for amazing work on #OSS 00010001143 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001144 +705bonus pay for amazing work on #OSS 00010001144 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001145 +705bonus pay for amazing work on #OSS 00010001145 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001146 +705bonus pay for amazing work on #OSS 00010001146 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001147 +705bonus pay for amazing work on #OSS 00010001147 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001148 +705bonus pay for amazing work on #OSS 00010001148 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001149 +705bonus pay for amazing work on #OSS 00010001149 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001150 +705bonus pay for amazing work on #OSS 00010001150 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001151 +705bonus pay for amazing work on #OSS 00010001151 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001152 +705bonus pay for amazing work on #OSS 00010001152 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001153 +705bonus pay for amazing work on #OSS 00010001153 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001154 +705bonus pay for amazing work on #OSS 00010001154 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001155 +705bonus pay for amazing work on #OSS 00010001155 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001156 +705bonus pay for amazing work on #OSS 00010001156 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001157 +705bonus pay for amazing work on #OSS 00010001157 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001158 +705bonus pay for amazing work on #OSS 00010001158 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001159 +705bonus pay for amazing work on #OSS 00010001159 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001160 +705bonus pay for amazing work on #OSS 00010001160 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001161 +705bonus pay for amazing work on #OSS 00010001161 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001162 +705bonus pay for amazing work on #OSS 00010001162 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001163 +705bonus pay for amazing work on #OSS 00010001163 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001164 +705bonus pay for amazing work on #OSS 00010001164 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001165 +705bonus pay for amazing work on #OSS 00010001165 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001166 +705bonus pay for amazing work on #OSS 00010001166 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001167 +705bonus pay for amazing work on #OSS 00010001167 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001168 +705bonus pay for amazing work on #OSS 00010001168 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001169 +705bonus pay for amazing work on #OSS 00010001169 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001170 +705bonus pay for amazing work on #OSS 00010001170 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001171 +705bonus pay for amazing work on #OSS 00010001171 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001172 +705bonus pay for amazing work on #OSS 00010001172 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001173 +705bonus pay for amazing work on #OSS 00010001173 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001174 +705bonus pay for amazing work on #OSS 00010001174 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001175 +705bonus pay for amazing work on #OSS 00010001175 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001176 +705bonus pay for amazing work on #OSS 00010001176 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001177 +705bonus pay for amazing work on #OSS 00010001177 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001178 +705bonus pay for amazing work on #OSS 00010001178 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001179 +705bonus pay for amazing work on #OSS 00010001179 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001180 +705bonus pay for amazing work on #OSS 00010001180 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001181 +705bonus pay for amazing work on #OSS 00010001181 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001182 +705bonus pay for amazing work on #OSS 00010001182 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001183 +705bonus pay for amazing work on #OSS 00010001183 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001184 +705bonus pay for amazing work on #OSS 00010001184 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001185 +705bonus pay for amazing work on #OSS 00010001185 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001186 +705bonus pay for amazing work on #OSS 00010001186 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001187 +705bonus pay for amazing work on #OSS 00010001187 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001188 +705bonus pay for amazing work on #OSS 00010001188 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001189 +705bonus pay for amazing work on #OSS 00010001189 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001190 +705bonus pay for amazing work on #OSS 00010001190 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001191 +705bonus pay for amazing work on #OSS 00010001191 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001192 +705bonus pay for amazing work on #OSS 00010001192 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001193 +705bonus pay for amazing work on #OSS 00010001193 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001194 +705bonus pay for amazing work on #OSS 00010001194 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001195 +705bonus pay for amazing work on #OSS 00010001195 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001196 +705bonus pay for amazing work on #OSS 00010001196 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001197 +705bonus pay for amazing work on #OSS 00010001197 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001198 +705bonus pay for amazing work on #OSS 00010001198 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001199 +705bonus pay for amazing work on #OSS 00010001199 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001200 +705bonus pay for amazing work on #OSS 00010001200 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001201 +705bonus pay for amazing work on #OSS 00010001201 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001202 +705bonus pay for amazing work on #OSS 00010001202 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001203 +705bonus pay for amazing work on #OSS 00010001203 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001204 +705bonus pay for amazing work on #OSS 00010001204 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001205 +705bonus pay for amazing work on #OSS 00010001205 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001206 +705bonus pay for amazing work on #OSS 00010001206 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001207 +705bonus pay for amazing work on #OSS 00010001207 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001208 +705bonus pay for amazing work on #OSS 00010001208 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001209 +705bonus pay for amazing work on #OSS 00010001209 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001210 +705bonus pay for amazing work on #OSS 00010001210 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001211 +705bonus pay for amazing work on #OSS 00010001211 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001212 +705bonus pay for amazing work on #OSS 00010001212 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001213 +705bonus pay for amazing work on #OSS 00010001213 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001214 +705bonus pay for amazing work on #OSS 00010001214 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001215 +705bonus pay for amazing work on #OSS 00010001215 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001216 +705bonus pay for amazing work on #OSS 00010001216 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001217 +705bonus pay for amazing work on #OSS 00010001217 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001218 +705bonus pay for amazing work on #OSS 00010001218 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001219 +705bonus pay for amazing work on #OSS 00010001219 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001220 +705bonus pay for amazing work on #OSS 00010001220 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001221 +705bonus pay for amazing work on #OSS 00010001221 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001222 +705bonus pay for amazing work on #OSS 00010001222 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001223 +705bonus pay for amazing work on #OSS 00010001223 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001224 +705bonus pay for amazing work on #OSS 00010001224 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001225 +705bonus pay for amazing work on #OSS 00010001225 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001226 +705bonus pay for amazing work on #OSS 00010001226 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001227 +705bonus pay for amazing work on #OSS 00010001227 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001228 +705bonus pay for amazing work on #OSS 00010001228 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001229 +705bonus pay for amazing work on #OSS 00010001229 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001230 +705bonus pay for amazing work on #OSS 00010001230 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001231 +705bonus pay for amazing work on #OSS 00010001231 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001232 +705bonus pay for amazing work on #OSS 00010001232 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001233 +705bonus pay for amazing work on #OSS 00010001233 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001234 +705bonus pay for amazing work on #OSS 00010001234 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001235 +705bonus pay for amazing work on #OSS 00010001235 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001236 +705bonus pay for amazing work on #OSS 00010001236 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001237 +705bonus pay for amazing work on #OSS 00010001237 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001238 +705bonus pay for amazing work on #OSS 00010001238 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001239 +705bonus pay for amazing work on #OSS 00010001239 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001240 +705bonus pay for amazing work on #OSS 00010001240 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001241 +705bonus pay for amazing work on #OSS 00010001241 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001242 +705bonus pay for amazing work on #OSS 00010001242 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001243 +705bonus pay for amazing work on #OSS 00010001243 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001244 +705bonus pay for amazing work on #OSS 00010001244 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001245 +705bonus pay for amazing work on #OSS 00010001245 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001246 +705bonus pay for amazing work on #OSS 00010001246 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001247 +705bonus pay for amazing work on #OSS 00010001247 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001248 +705bonus pay for amazing work on #OSS 00010001248 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001249 +705bonus pay for amazing work on #OSS 00010001249 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880001250 +705bonus pay for amazing work on #OSS 00010001250 +82000025008922512500000000000000000125000000121042882 121042880000004 +9000004001001000100005690050000000000000000000500000000 diff --git a/cmd/readACH/main.go b/cmd/readACH/main.go new file mode 100644 index 000000000..4de204c9f --- /dev/null +++ b/cmd/readACH/main.go @@ -0,0 +1,84 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "encoding/json" + "flag" + "fmt" + "log" + "os" + "runtime/pprof" + + "github.com/moov-io/ach" +) + +var ( + fPath = flag.String("fPath", "201805101354.ach", "File Path") + cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") + + flagJson = flag.Bool("json", false, "Output ACH File in JSON to stdout") +) + +func main() { + flag.Parse() + + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + log.Fatal(err) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + + path := *fPath + + // open a file for reading. Any io.Reader Can be used + f, err := os.Open(path) + + if err != nil { + log.Panicf("Can not open file: %s: \n", err) + } + + achFile, err := ach.NewReader(f).Read() + if err != nil { + fmt.Printf("Issue reading file: %+v \n", err) + } + + // If you trust the file but it's formating is off building will probably resolve the malformed file. + if err := achFile.Create(); err != nil { + fmt.Printf("Could not create file with read properties: %v", err) + } + + // ensure we have a validated file structure + if err := achFile.Validate(); err != nil { + fmt.Printf("Could not validate entire read file: %v", err) + } + + // Output file contents + if *flagJson { + if err := json.NewEncoder(os.Stdout).Encode(achFile); err != nil { + fmt.Printf("ERROR: problem writing ACH File to stdout: %v\n", err) + os.Exit(1) + } + } else { + fmt.Printf("total amount debit: %v \n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("total amount credit: %v \n", achFile.Control.TotalCreditEntryDollarAmountInFile) + } +} diff --git a/cmd/readACH/main_test.go b/cmd/readACH/main_test.go new file mode 100644 index 000000000..375ff54cb --- /dev/null +++ b/cmd/readACH/main_test.go @@ -0,0 +1,37 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func TestFileRead(t *testing.T) { + testFileRead(t) +} + +/*//BenchmarkTestFileCreate benchmarks creating an ACH File +func BenchmarkTestFileRead(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileRead(b) + } +}*/ + +// FileCreate creates an ACH File +func testFileRead(t testing.TB) { + main() +} diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 000000000..b0e8af70e --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,157 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "context" + "crypto/tls" + "flag" + "fmt" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/moov-io/ach" + "github.com/moov-io/ach/server" + "github.com/moov-io/base/admin" + "github.com/moov-io/base/http/bind" + "github.com/moov-io/base/log" + + kitlog "github.com/go-kit/log" +) + +var ( + httpAddr = flag.String("http.addr", bind.HTTP("ach"), "HTTP listen address") + adminAddr = flag.String("admin.addr", bind.Admin("ach"), "Admin HTTP listen address") + + flagLogFormat = flag.String("log.format", "", "Format for log lines (Options: json, plain") + + svc server.Service + handler http.Handler +) + +func main() { + flag.Parse() + + // Setup logging, default to stdout + var kitlogger kitlog.Logger + if v := os.Getenv("LOG_FORMAT"); v != "" { + *flagLogFormat = v + } + if *flagLogFormat == "json" { + kitlogger = kitlog.NewLogfmtLogger(kitlog.NewSyncWriter(os.Stdout)) + } else { + kitlogger = kitlog.NewJSONLogger(kitlog.NewSyncWriter(os.Stdout)) + } + + logger := log.NewLogger(kitlogger) + logger.Logf("Starting ach server version %s", ach.Version) + + // Setup underlying ach service + var achFileTTL time.Duration + if v := os.Getenv("ACH_FILE_TTL"); v != "" { + dur, err := time.ParseDuration(v) + if err == nil { + achFileTTL = dur + logger.Logf("Using %v as ach.File TTL", achFileTTL) + } + } + r := server.NewRepositoryInMemory(achFileTTL, logger) + svc = server.NewService(r) + + // Create HTTP server + handler = server.MakeHTTPHandler(svc, r, kitlog.With(kitlogger, "component", "HTTP")) + + // Listen for application termination. + errs := make(chan error) + go func() { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + errs <- fmt.Errorf("%s", <-c) + }() + + readTimeout, _ := time.ParseDuration("30s") + writTimeout, _ := time.ParseDuration("30s") + idleTimeout, _ := time.ParseDuration("60s") + + // Check to see if our -http.addr flag has been overridden + if v := os.Getenv("HTTP_BIND_ADDRESS"); v != "" { + *httpAddr = v + } + + serve := &http.Server{ + Addr: *httpAddr, + Handler: handler, + TLSConfig: &tls.Config{ + InsecureSkipVerify: false, + PreferServerCipherSuites: true, + MinVersion: tls.VersionTLS12, + }, + ReadTimeout: readTimeout, + ReadHeaderTimeout: readTimeout, + WriteTimeout: writTimeout, + IdleTimeout: idleTimeout, + } + shutdownServer := func() { + if err := serve.Shutdown(context.TODO()); err != nil { + logger.LogError(err) + } + } + + // Check to see if our -admin.addr flag has been overridden + if v := os.Getenv("HTTP_ADMIN_BIND_ADDRESS"); v != "" { + *adminAddr = v + } + + // Admin server (metrics and debugging) + adminServer := admin.NewServer(*adminAddr) + adminServer.AddVersionHandler(ach.Version) // Setup 'GET /version' + go func() { + logger.Logf("admin listening on %s", adminServer.BindAddr()) + if err := adminServer.Listen(); err != nil { + err = fmt.Errorf("problem starting admin http: %v", err) + logger.LogError(err) + errs <- err + } + }() + defer adminServer.Shutdown() + + // Start main HTTP server + go func() { + if certFile, keyFile := os.Getenv("HTTPS_CERT_FILE"), os.Getenv("HTTPS_KEY_FILE"); certFile != "" && keyFile != "" { + logger.Logf("startup binding to %s for secure HTTP server", *httpAddr) + if err := serve.ListenAndServeTLS(certFile, keyFile); err != nil { + errs <- err + logger.LogError(err) + } + } else { + logger.Logf("startup binding to %s for HTTP server", *httpAddr) + if err := serve.ListenAndServe(); err != nil { + errs <- err + logger.LogError(err) + } + } + }() + + if err := <-errs; err != nil { + shutdownServer() + logger.LogError(err) + } +} diff --git a/cmd/webui/ach/ach_js.go b/cmd/webui/ach/ach_js.go new file mode 100644 index 000000000..cf6c594be --- /dev/null +++ b/cmd/webui/ach/ach_js.go @@ -0,0 +1,152 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" + "syscall/js" + + "github.com/moov-io/ach" + "github.com/moov-io/ach/cmd/achcli/describe" +) + +func parseACH(input string) (string, error) { + r := strings.NewReader(input) + file, err := ach.NewReader(r).Read() + if err != nil { + return "", err + } + + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(file); err != nil { + return "", err + } + return buf.String(), nil +} + +func parseReadable(file *ach.File) (string, error) { + var buf bytes.Buffer + opts := describe.Opts{MaskNames: false, MaskAccountNumbers: false} + describe.File(&buf, file, &opts) + return buf.String(), nil +} + +func prettyJson(input string) (string, error) { + var raw interface{} + if err := json.Unmarshal([]byte(input), &raw); err != nil { + return "", err + } + pretty, err := json.MarshalIndent(raw, "", " ") + if err != nil { + return "", err + } + return string(pretty), nil +} + +func prettyPrintJSON() js.Func { + return js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + if len(args) != 1 { + return "Invalid number of arguments passed" + } + + if json.Valid([]byte(args[0].String())) { + return args[0].String() + } + + parsed, err := parseACH(args[0].String()) + if err != nil { + msg := fmt.Sprintf("unable to parse ach file: %v", err) + fmt.Println(msg) + return msg + } + pretty, err := prettyJson(parsed) + if err != nil { + fmt.Printf("unable to convert ach file to json %s\n", err) + return "There was an error converting the json" + } + + return pretty + }) +} + +func printACH() js.Func { + return js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + if len(args) != 1 { + return "Invalid number of arguments passed" + } + + if !json.Valid([]byte(args[0].String())) { + return args[0].String() + } + + file, err := ach.FileFromJSON([]byte(args[0].String())) + if err != nil { + msg := fmt.Sprintf("unable to parse json ACH file: %v", err) + fmt.Println(msg) + return msg + } + var buf bytes.Buffer + if err := ach.NewWriter(&buf).Write(file); err != nil { + msg := fmt.Sprintf("problem writing ACH file: %v", err) + fmt.Println(msg) + return msg + } + return buf.String() + }) +} + +func printReadable() js.Func { + return js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + if len(args) != 1 { + return "Invalid number of arguments passed" + } + + file, err := parseFile(args[0].String()) + if err != nil { + msg := fmt.Sprintf("unable to parse ach file: %v", err) + fmt.Println(msg) + return msg + } + + parsed, err := parseReadable(file) + if err != nil { + fmt.Printf("unable to convert ach file to human-readable format %s\n", err) + return "There was an error formatting the output" + } + + return parsed + }) +} + +// Parses input, either JSON or Nacha format to an ach.File +func parseFile(input string) (*ach.File, error) { + if json.Valid([]byte(input)) { + file, err := ach.FileFromJSON([]byte(input)) + if err != nil { + return nil, err + } + return file, nil + } + + file, err := ach.NewReader(strings.NewReader(input)).Read() + if err != nil { + return nil, err + } + return &file, nil +} + +func writeVersion() { + span := js.Global().Get("document").Call("querySelector", "#version") + span.Set("innerHTML", fmt.Sprintf("Version: %s", ach.Version)) +} + +func main() { + js.Global().Set("parseACH", prettyPrintJSON()) + js.Global().Set("parseJSON", printACH()) + js.Global().Set("parseReadable", printReadable()) + + writeVersion() + + <-make(chan bool) +} diff --git a/cmd/webui/ach/ach_other.go b/cmd/webui/ach/ach_other.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/cmd/webui/ach/ach_other.go @@ -0,0 +1 @@ +package main diff --git a/cmd/webui/assets/index.html b/cmd/webui/assets/index.html new file mode 100644 index 000000000..643a97247 --- /dev/null +++ b/cmd/webui/assets/index.html @@ -0,0 +1,178 @@ + + + + + + ACH | Moov + + + + + +
+
+ + +

ACH File Parser

+

+ This tool converts ACH files into their JSON definition or a human-readable format using Moov's ACH library. Paste an ACH file on the left, in Nacha or JSON format, and the formatted version will be generated to the right. +

+

+ For an example, try some of our test files. +

+
+ + + +
+
+ + + + +
+ +
+
+ +
+
+ + + diff --git a/cmd/webui/assets/wasm_exec.js b/cmd/webui/assets/wasm_exec.js new file mode 100644 index 000000000..9ce6a20c3 --- /dev/null +++ b/cmd/webui/assets/wasm_exec.js @@ -0,0 +1,554 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +"use strict"; + +(() => { + const enosys = () => { + const err = new Error("not implemented"); + err.code = "ENOSYS"; + return err; + }; + + if (!globalThis.fs) { + let outputBuf = ""; + globalThis.fs = { + constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused + writeSync(fd, buf) { + outputBuf += decoder.decode(buf); + const nl = outputBuf.lastIndexOf("\n"); + if (nl != -1) { + console.log(outputBuf.substr(0, nl)); + outputBuf = outputBuf.substr(nl + 1); + } + return buf.length; + }, + write(fd, buf, offset, length, position, callback) { + if (offset !== 0 || length !== buf.length || position !== null) { + callback(enosys()); + return; + } + const n = this.writeSync(fd, buf); + callback(null, n); + }, + chmod(path, mode, callback) { callback(enosys()); }, + chown(path, uid, gid, callback) { callback(enosys()); }, + close(fd, callback) { callback(enosys()); }, + fchmod(fd, mode, callback) { callback(enosys()); }, + fchown(fd, uid, gid, callback) { callback(enosys()); }, + fstat(fd, callback) { callback(enosys()); }, + fsync(fd, callback) { callback(null); }, + ftruncate(fd, length, callback) { callback(enosys()); }, + lchown(path, uid, gid, callback) { callback(enosys()); }, + link(path, link, callback) { callback(enosys()); }, + lstat(path, callback) { callback(enosys()); }, + mkdir(path, perm, callback) { callback(enosys()); }, + open(path, flags, mode, callback) { callback(enosys()); }, + read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, + readdir(path, callback) { callback(enosys()); }, + readlink(path, callback) { callback(enosys()); }, + rename(from, to, callback) { callback(enosys()); }, + rmdir(path, callback) { callback(enosys()); }, + stat(path, callback) { callback(enosys()); }, + symlink(path, link, callback) { callback(enosys()); }, + truncate(path, length, callback) { callback(enosys()); }, + unlink(path, callback) { callback(enosys()); }, + utimes(path, atime, mtime, callback) { callback(enosys()); }, + }; + } + + if (!globalThis.process) { + globalThis.process = { + getuid() { return -1; }, + getgid() { return -1; }, + geteuid() { return -1; }, + getegid() { return -1; }, + getgroups() { throw enosys(); }, + pid: -1, + ppid: -1, + umask() { throw enosys(); }, + cwd() { throw enosys(); }, + chdir() { throw enosys(); }, + } + } + + if (!globalThis.crypto) { + throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)"); + } + + if (!globalThis.performance) { + throw new Error("globalThis.performance is not available, polyfill required (performance.now only)"); + } + + if (!globalThis.TextEncoder) { + throw new Error("globalThis.TextEncoder is not available, polyfill required"); + } + + if (!globalThis.TextDecoder) { + throw new Error("globalThis.TextDecoder is not available, polyfill required"); + } + + const encoder = new TextEncoder("utf-8"); + const decoder = new TextDecoder("utf-8"); + + globalThis.Go = class { + constructor() { + this.argv = ["js"]; + this.env = {}; + this.exit = (code) => { + if (code !== 0) { + console.warn("exit code:", code); + } + }; + this._exitPromise = new Promise((resolve) => { + this._resolveExitPromise = resolve; + }); + this._pendingEvent = null; + this._scheduledTimeouts = new Map(); + this._nextCallbackTimeoutID = 1; + + const setInt64 = (addr, v) => { + this.mem.setUint32(addr + 0, v, true); + this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); + } + + const getInt64 = (addr) => { + const low = this.mem.getUint32(addr + 0, true); + const high = this.mem.getInt32(addr + 4, true); + return low + high * 4294967296; + } + + const loadValue = (addr) => { + const f = this.mem.getFloat64(addr, true); + if (f === 0) { + return undefined; + } + if (!isNaN(f)) { + return f; + } + + const id = this.mem.getUint32(addr, true); + return this._values[id]; + } + + const storeValue = (addr, v) => { + const nanHead = 0x7FF80000; + + if (typeof v === "number" && v !== 0) { + if (isNaN(v)) { + this.mem.setUint32(addr + 4, nanHead, true); + this.mem.setUint32(addr, 0, true); + return; + } + this.mem.setFloat64(addr, v, true); + return; + } + + if (v === undefined) { + this.mem.setFloat64(addr, 0, true); + return; + } + + let id = this._ids.get(v); + if (id === undefined) { + id = this._idPool.pop(); + if (id === undefined) { + id = this._values.length; + } + this._values[id] = v; + this._goRefCounts[id] = 0; + this._ids.set(v, id); + } + this._goRefCounts[id]++; + let typeFlag = 0; + switch (typeof v) { + case "object": + if (v !== null) { + typeFlag = 1; + } + break; + case "string": + typeFlag = 2; + break; + case "symbol": + typeFlag = 3; + break; + case "function": + typeFlag = 4; + break; + } + this.mem.setUint32(addr + 4, nanHead | typeFlag, true); + this.mem.setUint32(addr, id, true); + } + + const loadSlice = (addr) => { + const array = getInt64(addr + 0); + const len = getInt64(addr + 8); + return new Uint8Array(this._inst.exports.mem.buffer, array, len); + } + + const loadSliceOfValues = (addr) => { + const array = getInt64(addr + 0); + const len = getInt64(addr + 8); + const a = new Array(len); + for (let i = 0; i < len; i++) { + a[i] = loadValue(array + i * 8); + } + return a; + } + + const loadString = (addr) => { + const saddr = getInt64(addr + 0); + const len = getInt64(addr + 8); + return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); + } + + const timeOrigin = Date.now() - performance.now(); + this.importObject = { + go: { + // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) + // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported + // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). + // This changes the SP, thus we have to update the SP used by the imported function. + + // func wasmExit(code int32) + "runtime.wasmExit": (sp) => { + sp >>>= 0; + const code = this.mem.getInt32(sp + 8, true); + this.exited = true; + delete this._inst; + delete this._values; + delete this._goRefCounts; + delete this._ids; + delete this._idPool; + this.exit(code); + }, + + // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) + "runtime.wasmWrite": (sp) => { + sp >>>= 0; + const fd = getInt64(sp + 8); + const p = getInt64(sp + 16); + const n = this.mem.getInt32(sp + 24, true); + fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); + }, + + // func resetMemoryDataView() + "runtime.resetMemoryDataView": (sp) => { + sp >>>= 0; + this.mem = new DataView(this._inst.exports.mem.buffer); + }, + + // func nanotime1() int64 + "runtime.nanotime1": (sp) => { + sp >>>= 0; + setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); + }, + + // func walltime() (sec int64, nsec int32) + "runtime.walltime": (sp) => { + sp >>>= 0; + const msec = (new Date).getTime(); + setInt64(sp + 8, msec / 1000); + this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); + }, + + // func scheduleTimeoutEvent(delay int64) int32 + "runtime.scheduleTimeoutEvent": (sp) => { + sp >>>= 0; + const id = this._nextCallbackTimeoutID; + this._nextCallbackTimeoutID++; + this._scheduledTimeouts.set(id, setTimeout( + () => { + this._resume(); + while (this._scheduledTimeouts.has(id)) { + // for some reason Go failed to register the timeout event, log and try again + // (temporary workaround for https://github.com/golang/go/issues/28975) + console.warn("scheduleTimeoutEvent: missed timeout event"); + this._resume(); + } + }, + getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early + )); + this.mem.setInt32(sp + 16, id, true); + }, + + // func clearTimeoutEvent(id int32) + "runtime.clearTimeoutEvent": (sp) => { + sp >>>= 0; + const id = this.mem.getInt32(sp + 8, true); + clearTimeout(this._scheduledTimeouts.get(id)); + this._scheduledTimeouts.delete(id); + }, + + // func getRandomData(r []byte) + "runtime.getRandomData": (sp) => { + sp >>>= 0; + crypto.getRandomValues(loadSlice(sp + 8)); + }, + + // func finalizeRef(v ref) + "syscall/js.finalizeRef": (sp) => { + sp >>>= 0; + const id = this.mem.getUint32(sp + 8, true); + this._goRefCounts[id]--; + if (this._goRefCounts[id] === 0) { + const v = this._values[id]; + this._values[id] = null; + this._ids.delete(v); + this._idPool.push(id); + } + }, + + // func stringVal(value string) ref + "syscall/js.stringVal": (sp) => { + sp >>>= 0; + storeValue(sp + 24, loadString(sp + 8)); + }, + + // func valueGet(v ref, p string) ref + "syscall/js.valueGet": (sp) => { + sp >>>= 0; + const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 32, result); + }, + + // func valueSet(v ref, p string, x ref) + "syscall/js.valueSet": (sp) => { + sp >>>= 0; + Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); + }, + + // func valueDelete(v ref, p string) + "syscall/js.valueDelete": (sp) => { + sp >>>= 0; + Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); + }, + + // func valueIndex(v ref, i int) ref + "syscall/js.valueIndex": (sp) => { + sp >>>= 0; + storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); + }, + + // valueSetIndex(v ref, i int, x ref) + "syscall/js.valueSetIndex": (sp) => { + sp >>>= 0; + Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); + }, + + // func valueCall(v ref, m string, args []ref) (ref, bool) + "syscall/js.valueCall": (sp) => { + sp >>>= 0; + try { + const v = loadValue(sp + 8); + const m = Reflect.get(v, loadString(sp + 16)); + const args = loadSliceOfValues(sp + 32); + const result = Reflect.apply(m, v, args); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 56, result); + this.mem.setUint8(sp + 64, 1); + } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 56, err); + this.mem.setUint8(sp + 64, 0); + } + }, + + // func valueInvoke(v ref, args []ref) (ref, bool) + "syscall/js.valueInvoke": (sp) => { + sp >>>= 0; + try { + const v = loadValue(sp + 8); + const args = loadSliceOfValues(sp + 16); + const result = Reflect.apply(v, undefined, args); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, result); + this.mem.setUint8(sp + 48, 1); + } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, err); + this.mem.setUint8(sp + 48, 0); + } + }, + + // func valueNew(v ref, args []ref) (ref, bool) + "syscall/js.valueNew": (sp) => { + sp >>>= 0; + try { + const v = loadValue(sp + 8); + const args = loadSliceOfValues(sp + 16); + const result = Reflect.construct(v, args); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, result); + this.mem.setUint8(sp + 48, 1); + } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, err); + this.mem.setUint8(sp + 48, 0); + } + }, + + // func valueLength(v ref) int + "syscall/js.valueLength": (sp) => { + sp >>>= 0; + setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); + }, + + // valuePrepareString(v ref) (ref, int) + "syscall/js.valuePrepareString": (sp) => { + sp >>>= 0; + const str = encoder.encode(String(loadValue(sp + 8))); + storeValue(sp + 16, str); + setInt64(sp + 24, str.length); + }, + + // valueLoadString(v ref, b []byte) + "syscall/js.valueLoadString": (sp) => { + sp >>>= 0; + const str = loadValue(sp + 8); + loadSlice(sp + 16).set(str); + }, + + // func valueInstanceOf(v ref, t ref) bool + "syscall/js.valueInstanceOf": (sp) => { + sp >>>= 0; + this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0); + }, + + // func copyBytesToGo(dst []byte, src ref) (int, bool) + "syscall/js.copyBytesToGo": (sp) => { + sp >>>= 0; + const dst = loadSlice(sp + 8); + const src = loadValue(sp + 32); + if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { + this.mem.setUint8(sp + 48, 0); + return; + } + const toCopy = src.subarray(0, dst.length); + dst.set(toCopy); + setInt64(sp + 40, toCopy.length); + this.mem.setUint8(sp + 48, 1); + }, + + // func copyBytesToJS(dst ref, src []byte) (int, bool) + "syscall/js.copyBytesToJS": (sp) => { + sp >>>= 0; + const dst = loadValue(sp + 8); + const src = loadSlice(sp + 16); + if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { + this.mem.setUint8(sp + 48, 0); + return; + } + const toCopy = src.subarray(0, dst.length); + dst.set(toCopy); + setInt64(sp + 40, toCopy.length); + this.mem.setUint8(sp + 48, 1); + }, + + "debug": (value) => { + console.log(value); + }, + } + }; + } + + async run(instance) { + if (!(instance instanceof WebAssembly.Instance)) { + throw new Error("Go.run: WebAssembly.Instance expected"); + } + this._inst = instance; + this.mem = new DataView(this._inst.exports.mem.buffer); + this._values = [ // JS values that Go currently has references to, indexed by reference id + NaN, + 0, + null, + true, + false, + globalThis, + this, + ]; + this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id + this._ids = new Map([ // mapping from JS values to reference ids + [0, 1], + [null, 2], + [true, 3], + [false, 4], + [globalThis, 5], + [this, 6], + ]); + this._idPool = []; // unused ids that have been garbage collected + this.exited = false; // whether the Go program has exited + + // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. + let offset = 4096; + + const strPtr = (str) => { + const ptr = offset; + const bytes = encoder.encode(str + "\0"); + new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); + offset += bytes.length; + if (offset % 8 !== 0) { + offset += 8 - (offset % 8); + } + return ptr; + }; + + const argc = this.argv.length; + + const argvPtrs = []; + this.argv.forEach((arg) => { + argvPtrs.push(strPtr(arg)); + }); + argvPtrs.push(0); + + const keys = Object.keys(this.env).sort(); + keys.forEach((key) => { + argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); + }); + argvPtrs.push(0); + + const argv = offset; + argvPtrs.forEach((ptr) => { + this.mem.setUint32(offset, ptr, true); + this.mem.setUint32(offset + 4, 0, true); + offset += 8; + }); + + // The linker guarantees global data starts from at least wasmMinDataAddr. + // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. + const wasmMinDataAddr = 4096 + 8192; + if (offset >= wasmMinDataAddr) { + throw new Error("total length of command line and environment variables exceeds limit"); + } + + this._inst.exports.run(argc, argv); + if (this.exited) { + this._resolveExitPromise(); + } + await this._exitPromise; + } + + _resume() { + if (this.exited) { + throw new Error("Go program has already exited"); + } + this._inst.exports.resume(); + if (this.exited) { + this._resolveExitPromise(); + } + } + + _makeFuncWrapper(id) { + const go = this; + return function () { + const event = { id: id, this: this, args: arguments }; + go._pendingEvent = event; + go._resume(); + return event.result; + }; + } + } +})(); diff --git a/cmd/webui/main.go b/cmd/webui/main.go new file mode 100644 index 000000000..67a4f9067 --- /dev/null +++ b/cmd/webui/main.go @@ -0,0 +1,124 @@ +package main + +import ( + "context" + "crypto/tls" + "flag" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "path/filepath" + "syscall" + "time" + + "github.com/moov-io/base/admin" + moovhttp "github.com/moov-io/base/http" + "github.com/moov-io/base/http/bind" + "github.com/moov-io/base/strx" + + "github.com/moov-io/ach" + + "github.com/gorilla/mux" +) + +var ( + httpAddr = flag.String("http.addr", bind.HTTP("ACH"), "HTTP listen address") + adminAddr = flag.String("admin.addr", bind.Admin("ACH"), "Admin HTTP listen address") + + flagBasePath = flag.String("base-path", "/", "Base path to serve HTTP routes and webui from") +) + +func main() { + flag.Parse() + + log.Printf("Starting moov-io/ach webui version %s", ach.Version) + + // Channel for errors + errs := make(chan error) + + go func() { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + errs <- fmt.Errorf("%s", <-c) + }() + + // Start Admin server (with Prometheus metrics) + adminServer := admin.NewServer(*adminAddr) + adminServer.AddVersionHandler(ach.Version) // Setup 'GET /version' + go func() { + log.Printf("listening on %s", adminServer.BindAddr()) + if err := adminServer.Listen(); err != nil { + err = fmt.Errorf("problem starting admin http: %v", err) + log.Print(err) + errs <- err + } + }() + defer adminServer.Shutdown() + + // Setup business HTTP routes + router := mux.NewRouter().PathPrefix(*flagBasePath).Subrouter() + addPingRoute(router) + + // Register our assets route + assetsPath := strx.Or(os.Getenv("ASSETS_PATH"), filepath.Join("cmd", "webui", "assets")) + log.Printf("serving assets from %s", assetsPath) + addAssetsPath(router, assetsPath) + + serve := &http.Server{ + Addr: *httpAddr, + Handler: router, + TLSConfig: &tls.Config{ + InsecureSkipVerify: false, + PreferServerCipherSuites: true, + MinVersion: tls.VersionTLS12, + }, + ReadTimeout: 10 * time.Second, + ReadHeaderTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 10 * time.Second, + } + shutdownServer := func() { + if err := serve.Shutdown(context.TODO()); err != nil { + log.Print(err) + } + } + + // Start business logic HTTP server + go func() { + if certFile, keyFile := os.Getenv("HTTPS_CERT_FILE"), os.Getenv("HTTPS_KEY_FILE"); certFile != "" && keyFile != "" { + log.Printf("binding to %s for secure HTTP server", *httpAddr) + if err := serve.ListenAndServeTLS(certFile, keyFile); err != nil { + log.Print(err) + } + } else { + log.Printf("binding to %s for HTTP server", *httpAddr) + if err := serve.ListenAndServe(); err != nil { + log.Print(err) + } + } + }() + + // Block/Wait for an error + if err := <-errs; err != nil { + shutdownServer() + log.Print(err) + } +} + +func addPingRoute(r *mux.Router) { + r.Methods("GET").Path("/ping").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + moovhttp.SetAccessControlAllowHeaders(w, r.Header.Get("Origin")) + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + w.Write([]byte("PONG")) + }) +} + +func addAssetsPath(r *mux.Router, assetPath string) { + if _, err := os.Stat(assetPath); err != nil { + panic(fmt.Sprintf("ERROR: unable to stat %s: %v", assetPath, err)) + } + r.Methods("GET").PathPrefix("/").Handler(http.StripPrefix(*flagBasePath, http.FileServer(http.Dir(assetPath)))) +} diff --git a/cmd/writeACH/201805101354.ach b/cmd/writeACH/201805101354.ach new file mode 100644 index 000000000..7bab11094 --- /dev/null +++ b/cmd/writeACH/201805101354.ach @@ -0,0 +1,10010 @@ +101 231380104 1210428821805100000A094101Citadel Wells Fargo +5200Wells Fargo 121042882 PPDTrans. Des 180511 0121042880000001 +62223138010481967038518 0000100000#KnhT6dHNOpUN3#Lily Martin 1121042880000001 +705bonus pay for amazing work on #OSS 00010000001 +62223138010481967038518 0000100000#KnhT6dHNOpUN3#Lily Martin 1121042880000002 +705bonus pay for amazing work on #OSS 00010000002 +62223138010481967038518 0000100000#KnhT6dHNOpUN3#Lily Martin 1121042880000003 +705bonus pay for amazing work on #OSS 00010000003 +62223138010481967038518 0000100000#KnhT6dHNOpUN3#Lily Martin 1121042880000004 +705bonus pay for amazing work on #OSS 00010000004 +62223138010481967038518 0000100000#g2c4up44DsnU6#Lily Taylor 1121042880000005 +705bonus pay for amazing work on #OSS 00010000005 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000006 +705bonus pay for amazing work on #OSS 00010000006 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000007 +705bonus pay for amazing work on #OSS 00010000007 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000008 +705bonus pay for amazing work on #OSS 00010000008 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000009 +705bonus pay for amazing work on #OSS 00010000009 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000010 +705bonus pay for amazing work on #OSS 00010000010 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000011 +705bonus pay for amazing work on #OSS 00010000011 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000012 +705bonus pay for amazing work on #OSS 00010000012 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000013 +705bonus pay for amazing work on #OSS 00010000013 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000014 +705bonus pay for amazing work on #OSS 00010000014 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000015 +705bonus pay for amazing work on #OSS 00010000015 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000016 +705bonus pay for amazing work on #OSS 00010000016 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000017 +705bonus pay for amazing work on #OSS 00010000017 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000018 +705bonus pay for amazing work on #OSS 00010000018 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000019 +705bonus pay for amazing work on #OSS 00010000019 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000020 +705bonus pay for amazing work on #OSS 00010000020 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000021 +705bonus pay for amazing work on #OSS 00010000021 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000022 +705bonus pay for amazing work on #OSS 00010000022 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000023 +705bonus pay for amazing work on #OSS 00010000023 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000024 +705bonus pay for amazing work on #OSS 00010000024 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000025 +705bonus pay for amazing work on #OSS 00010000025 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000026 +705bonus pay for amazing work on #OSS 00010000026 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000027 +705bonus pay for amazing work on #OSS 00010000027 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000028 +705bonus pay for amazing work on #OSS 00010000028 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000029 +705bonus pay for amazing work on #OSS 00010000029 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000030 +705bonus pay for amazing work on #OSS 00010000030 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000031 +705bonus pay for amazing work on #OSS 00010000031 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000032 +705bonus pay for amazing work on #OSS 00010000032 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000033 +705bonus pay for amazing work on #OSS 00010000033 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000034 +705bonus pay for amazing work on #OSS 00010000034 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000035 +705bonus pay for amazing work on #OSS 00010000035 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000036 +705bonus pay for amazing work on #OSS 00010000036 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000037 +705bonus pay for amazing work on #OSS 00010000037 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000038 +705bonus pay for amazing work on #OSS 00010000038 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000039 +705bonus pay for amazing work on #OSS 00010000039 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000040 +705bonus pay for amazing work on #OSS 00010000040 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000041 +705bonus pay for amazing work on #OSS 00010000041 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000042 +705bonus pay for amazing work on #OSS 00010000042 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000043 +705bonus pay for amazing work on #OSS 00010000043 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000044 +705bonus pay for amazing work on #OSS 00010000044 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000045 +705bonus pay for amazing work on #OSS 00010000045 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000046 +705bonus pay for amazing work on #OSS 00010000046 +62223138010481967038518 0000100000#g2c4up44DsnU6#Elizabeth Taylor 1121042880000047 +705bonus pay for amazing work on #OSS 00010000047 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000048 +705bonus pay for amazing work on #OSS 00010000048 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000049 +705bonus pay for amazing work on #OSS 00010000049 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000050 +705bonus pay for amazing work on #OSS 00010000050 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000051 +705bonus pay for amazing work on #OSS 00010000051 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000052 +705bonus pay for amazing work on #OSS 00010000052 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000053 +705bonus pay for amazing work on #OSS 00010000053 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000054 +705bonus pay for amazing work on #OSS 00010000054 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000055 +705bonus pay for amazing work on #OSS 00010000055 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000056 +705bonus pay for amazing work on #OSS 00010000056 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000057 +705bonus pay for amazing work on #OSS 00010000057 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000058 +705bonus pay for amazing work on #OSS 00010000058 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000059 +705bonus pay for amazing work on #OSS 00010000059 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000060 +705bonus pay for amazing work on #OSS 00010000060 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000061 +705bonus pay for amazing work on #OSS 00010000061 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000062 +705bonus pay for amazing work on #OSS 00010000062 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000063 +705bonus pay for amazing work on #OSS 00010000063 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000064 +705bonus pay for amazing work on #OSS 00010000064 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000065 +705bonus pay for amazing work on #OSS 00010000065 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000066 +705bonus pay for amazing work on #OSS 00010000066 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000067 +705bonus pay for amazing work on #OSS 00010000067 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000068 +705bonus pay for amazing work on #OSS 00010000068 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000069 +705bonus pay for amazing work on #OSS 00010000069 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000070 +705bonus pay for amazing work on #OSS 00010000070 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000071 +705bonus pay for amazing work on #OSS 00010000071 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000072 +705bonus pay for amazing work on #OSS 00010000072 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000073 +705bonus pay for amazing work on #OSS 00010000073 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000074 +705bonus pay for amazing work on #OSS 00010000074 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000075 +705bonus pay for amazing work on #OSS 00010000075 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000076 +705bonus pay for amazing work on #OSS 00010000076 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000077 +705bonus pay for amazing work on #OSS 00010000077 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000078 +705bonus pay for amazing work on #OSS 00010000078 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000079 +705bonus pay for amazing work on #OSS 00010000079 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000080 +705bonus pay for amazing work on #OSS 00010000080 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000081 +705bonus pay for amazing work on #OSS 00010000081 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000082 +705bonus pay for amazing work on #OSS 00010000082 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000083 +705bonus pay for amazing work on #OSS 00010000083 +62223138010481967038518 0000100000#jMsroTi0Z44Rx#Olivia Jones 1121042880000084 +705bonus pay for amazing work on #OSS 00010000084 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000085 +705bonus pay for amazing work on #OSS 00010000085 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000086 +705bonus pay for amazing work on #OSS 00010000086 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000087 +705bonus pay for amazing work on #OSS 00010000087 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000088 +705bonus pay for amazing work on #OSS 00010000088 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000089 +705bonus pay for amazing work on #OSS 00010000089 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000090 +705bonus pay for amazing work on #OSS 00010000090 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000091 +705bonus pay for amazing work on #OSS 00010000091 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000092 +705bonus pay for amazing work on #OSS 00010000092 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000093 +705bonus pay for amazing work on #OSS 00010000093 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000094 +705bonus pay for amazing work on #OSS 00010000094 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000095 +705bonus pay for amazing work on #OSS 00010000095 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000096 +705bonus pay for amazing work on #OSS 00010000096 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000097 +705bonus pay for amazing work on #OSS 00010000097 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000098 +705bonus pay for amazing work on #OSS 00010000098 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000099 +705bonus pay for amazing work on #OSS 00010000099 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000100 +705bonus pay for amazing work on #OSS 00010000100 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000101 +705bonus pay for amazing work on #OSS 00010000101 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000102 +705bonus pay for amazing work on #OSS 00010000102 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000103 +705bonus pay for amazing work on #OSS 00010000103 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000104 +705bonus pay for amazing work on #OSS 00010000104 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000105 +705bonus pay for amazing work on #OSS 00010000105 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000106 +705bonus pay for amazing work on #OSS 00010000106 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000107 +705bonus pay for amazing work on #OSS 00010000107 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000108 +705bonus pay for amazing work on #OSS 00010000108 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000109 +705bonus pay for amazing work on #OSS 00010000109 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000110 +705bonus pay for amazing work on #OSS 00010000110 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000111 +705bonus pay for amazing work on #OSS 00010000111 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000112 +705bonus pay for amazing work on #OSS 00010000112 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000113 +705bonus pay for amazing work on #OSS 00010000113 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000114 +705bonus pay for amazing work on #OSS 00010000114 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000115 +705bonus pay for amazing work on #OSS 00010000115 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000116 +705bonus pay for amazing work on #OSS 00010000116 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000117 +705bonus pay for amazing work on #OSS 00010000117 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000118 +705bonus pay for amazing work on #OSS 00010000118 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000119 +705bonus pay for amazing work on #OSS 00010000119 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000120 +705bonus pay for amazing work on #OSS 00010000120 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000121 +705bonus pay for amazing work on #OSS 00010000121 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000122 +705bonus pay for amazing work on #OSS 00010000122 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000123 +705bonus pay for amazing work on #OSS 00010000123 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000124 +705bonus pay for amazing work on #OSS 00010000124 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000125 +705bonus pay for amazing work on #OSS 00010000125 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000126 +705bonus pay for amazing work on #OSS 00010000126 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000127 +705bonus pay for amazing work on #OSS 00010000127 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000128 +705bonus pay for amazing work on #OSS 00010000128 +62223138010481967038518 0000100000#VPZDeKLSorlkP#Mia Wilson 1121042880000129 +705bonus pay for amazing work on #OSS 00010000129 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000130 +705bonus pay for amazing work on #OSS 00010000130 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000131 +705bonus pay for amazing work on #OSS 00010000131 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000132 +705bonus pay for amazing work on #OSS 00010000132 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000133 +705bonus pay for amazing work on #OSS 00010000133 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000134 +705bonus pay for amazing work on #OSS 00010000134 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000135 +705bonus pay for amazing work on #OSS 00010000135 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000136 +705bonus pay for amazing work on #OSS 00010000136 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000137 +705bonus pay for amazing work on #OSS 00010000137 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000138 +705bonus pay for amazing work on #OSS 00010000138 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000139 +705bonus pay for amazing work on #OSS 00010000139 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000140 +705bonus pay for amazing work on #OSS 00010000140 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000141 +705bonus pay for amazing work on #OSS 00010000141 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000142 +705bonus pay for amazing work on #OSS 00010000142 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000143 +705bonus pay for amazing work on #OSS 00010000143 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000144 +705bonus pay for amazing work on #OSS 00010000144 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000145 +705bonus pay for amazing work on #OSS 00010000145 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000146 +705bonus pay for amazing work on #OSS 00010000146 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000147 +705bonus pay for amazing work on #OSS 00010000147 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000148 +705bonus pay for amazing work on #OSS 00010000148 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000149 +705bonus pay for amazing work on #OSS 00010000149 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000150 +705bonus pay for amazing work on #OSS 00010000150 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000151 +705bonus pay for amazing work on #OSS 00010000151 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000152 +705bonus pay for amazing work on #OSS 00010000152 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000153 +705bonus pay for amazing work on #OSS 00010000153 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000154 +705bonus pay for amazing work on #OSS 00010000154 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000155 +705bonus pay for amazing work on #OSS 00010000155 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000156 +705bonus pay for amazing work on #OSS 00010000156 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000157 +705bonus pay for amazing work on #OSS 00010000157 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000158 +705bonus pay for amazing work on #OSS 00010000158 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000159 +705bonus pay for amazing work on #OSS 00010000159 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000160 +705bonus pay for amazing work on #OSS 00010000160 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000161 +705bonus pay for amazing work on #OSS 00010000161 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000162 +705bonus pay for amazing work on #OSS 00010000162 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000163 +705bonus pay for amazing work on #OSS 00010000163 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000164 +705bonus pay for amazing work on #OSS 00010000164 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000165 +705bonus pay for amazing work on #OSS 00010000165 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000166 +705bonus pay for amazing work on #OSS 00010000166 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000167 +705bonus pay for amazing work on #OSS 00010000167 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000168 +705bonus pay for amazing work on #OSS 00010000168 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000169 +705bonus pay for amazing work on #OSS 00010000169 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000170 +705bonus pay for amazing work on #OSS 00010000170 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000171 +705bonus pay for amazing work on #OSS 00010000171 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000172 +705bonus pay for amazing work on #OSS 00010000172 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000173 +705bonus pay for amazing work on #OSS 00010000173 +62223138010481967038518 0000100000#Joy8Qmdc3V5JT#Elijah Jackson 1121042880000174 +705bonus pay for amazing work on #OSS 00010000174 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Elijah Johnson 1121042880000175 +705bonus pay for amazing work on #OSS 00010000175 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000176 +705bonus pay for amazing work on #OSS 00010000176 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000177 +705bonus pay for amazing work on #OSS 00010000177 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000178 +705bonus pay for amazing work on #OSS 00010000178 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000179 +705bonus pay for amazing work on #OSS 00010000179 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000180 +705bonus pay for amazing work on #OSS 00010000180 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000181 +705bonus pay for amazing work on #OSS 00010000181 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000182 +705bonus pay for amazing work on #OSS 00010000182 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000183 +705bonus pay for amazing work on #OSS 00010000183 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000184 +705bonus pay for amazing work on #OSS 00010000184 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000185 +705bonus pay for amazing work on #OSS 00010000185 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000186 +705bonus pay for amazing work on #OSS 00010000186 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000187 +705bonus pay for amazing work on #OSS 00010000187 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000188 +705bonus pay for amazing work on #OSS 00010000188 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000189 +705bonus pay for amazing work on #OSS 00010000189 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000190 +705bonus pay for amazing work on #OSS 00010000190 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000191 +705bonus pay for amazing work on #OSS 00010000191 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000192 +705bonus pay for amazing work on #OSS 00010000192 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000193 +705bonus pay for amazing work on #OSS 00010000193 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000194 +705bonus pay for amazing work on #OSS 00010000194 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000195 +705bonus pay for amazing work on #OSS 00010000195 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000196 +705bonus pay for amazing work on #OSS 00010000196 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000197 +705bonus pay for amazing work on #OSS 00010000197 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000198 +705bonus pay for amazing work on #OSS 00010000198 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000199 +705bonus pay for amazing work on #OSS 00010000199 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000200 +705bonus pay for amazing work on #OSS 00010000200 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000201 +705bonus pay for amazing work on #OSS 00010000201 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000202 +705bonus pay for amazing work on #OSS 00010000202 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000203 +705bonus pay for amazing work on #OSS 00010000203 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000204 +705bonus pay for amazing work on #OSS 00010000204 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000205 +705bonus pay for amazing work on #OSS 00010000205 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000206 +705bonus pay for amazing work on #OSS 00010000206 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000207 +705bonus pay for amazing work on #OSS 00010000207 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000208 +705bonus pay for amazing work on #OSS 00010000208 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000209 +705bonus pay for amazing work on #OSS 00010000209 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000210 +705bonus pay for amazing work on #OSS 00010000210 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000211 +705bonus pay for amazing work on #OSS 00010000211 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000212 +705bonus pay for amazing work on #OSS 00010000212 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000213 +705bonus pay for amazing work on #OSS 00010000213 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000214 +705bonus pay for amazing work on #OSS 00010000214 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000215 +705bonus pay for amazing work on #OSS 00010000215 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000216 +705bonus pay for amazing work on #OSS 00010000216 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000217 +705bonus pay for amazing work on #OSS 00010000217 +62223138010481967038518 0000100000#OH8ap2hRc7v6S#Emma Johnson 1121042880000218 +705bonus pay for amazing work on #OSS 00010000218 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Emma White 1121042880000219 +705bonus pay for amazing work on #OSS 00010000219 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000220 +705bonus pay for amazing work on #OSS 00010000220 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000221 +705bonus pay for amazing work on #OSS 00010000221 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000222 +705bonus pay for amazing work on #OSS 00010000222 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000223 +705bonus pay for amazing work on #OSS 00010000223 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000224 +705bonus pay for amazing work on #OSS 00010000224 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000225 +705bonus pay for amazing work on #OSS 00010000225 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000226 +705bonus pay for amazing work on #OSS 00010000226 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000227 +705bonus pay for amazing work on #OSS 00010000227 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000228 +705bonus pay for amazing work on #OSS 00010000228 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000229 +705bonus pay for amazing work on #OSS 00010000229 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000230 +705bonus pay for amazing work on #OSS 00010000230 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000231 +705bonus pay for amazing work on #OSS 00010000231 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000232 +705bonus pay for amazing work on #OSS 00010000232 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000233 +705bonus pay for amazing work on #OSS 00010000233 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000234 +705bonus pay for amazing work on #OSS 00010000234 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000235 +705bonus pay for amazing work on #OSS 00010000235 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000236 +705bonus pay for amazing work on #OSS 00010000236 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000237 +705bonus pay for amazing work on #OSS 00010000237 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000238 +705bonus pay for amazing work on #OSS 00010000238 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000239 +705bonus pay for amazing work on #OSS 00010000239 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000240 +705bonus pay for amazing work on #OSS 00010000240 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000241 +705bonus pay for amazing work on #OSS 00010000241 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000242 +705bonus pay for amazing work on #OSS 00010000242 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000243 +705bonus pay for amazing work on #OSS 00010000243 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000244 +705bonus pay for amazing work on #OSS 00010000244 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000245 +705bonus pay for amazing work on #OSS 00010000245 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000246 +705bonus pay for amazing work on #OSS 00010000246 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000247 +705bonus pay for amazing work on #OSS 00010000247 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000248 +705bonus pay for amazing work on #OSS 00010000248 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000249 +705bonus pay for amazing work on #OSS 00010000249 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000250 +705bonus pay for amazing work on #OSS 00010000250 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000251 +705bonus pay for amazing work on #OSS 00010000251 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000252 +705bonus pay for amazing work on #OSS 00010000252 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000253 +705bonus pay for amazing work on #OSS 00010000253 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000254 +705bonus pay for amazing work on #OSS 00010000254 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000255 +705bonus pay for amazing work on #OSS 00010000255 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000256 +705bonus pay for amazing work on #OSS 00010000256 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000257 +705bonus pay for amazing work on #OSS 00010000257 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000258 +705bonus pay for amazing work on #OSS 00010000258 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000259 +705bonus pay for amazing work on #OSS 00010000259 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000260 +705bonus pay for amazing work on #OSS 00010000260 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000261 +705bonus pay for amazing work on #OSS 00010000261 +62223138010481967038518 0000100000#1qMcmmsMWFCHI#Addison White 1121042880000262 +705bonus pay for amazing work on #OSS 00010000262 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Natalie Thompson 1121042880000263 +705bonus pay for amazing work on #OSS 00010000263 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000264 +705bonus pay for amazing work on #OSS 00010000264 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000265 +705bonus pay for amazing work on #OSS 00010000265 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000266 +705bonus pay for amazing work on #OSS 00010000266 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000267 +705bonus pay for amazing work on #OSS 00010000267 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000268 +705bonus pay for amazing work on #OSS 00010000268 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000269 +705bonus pay for amazing work on #OSS 00010000269 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000270 +705bonus pay for amazing work on #OSS 00010000270 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000271 +705bonus pay for amazing work on #OSS 00010000271 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000272 +705bonus pay for amazing work on #OSS 00010000272 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000273 +705bonus pay for amazing work on #OSS 00010000273 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000274 +705bonus pay for amazing work on #OSS 00010000274 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000275 +705bonus pay for amazing work on #OSS 00010000275 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000276 +705bonus pay for amazing work on #OSS 00010000276 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000277 +705bonus pay for amazing work on #OSS 00010000277 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000278 +705bonus pay for amazing work on #OSS 00010000278 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000279 +705bonus pay for amazing work on #OSS 00010000279 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000280 +705bonus pay for amazing work on #OSS 00010000280 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000281 +705bonus pay for amazing work on #OSS 00010000281 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000282 +705bonus pay for amazing work on #OSS 00010000282 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000283 +705bonus pay for amazing work on #OSS 00010000283 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000284 +705bonus pay for amazing work on #OSS 00010000284 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000285 +705bonus pay for amazing work on #OSS 00010000285 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000286 +705bonus pay for amazing work on #OSS 00010000286 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000287 +705bonus pay for amazing work on #OSS 00010000287 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000288 +705bonus pay for amazing work on #OSS 00010000288 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000289 +705bonus pay for amazing work on #OSS 00010000289 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000290 +705bonus pay for amazing work on #OSS 00010000290 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000291 +705bonus pay for amazing work on #OSS 00010000291 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000292 +705bonus pay for amazing work on #OSS 00010000292 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000293 +705bonus pay for amazing work on #OSS 00010000293 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000294 +705bonus pay for amazing work on #OSS 00010000294 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000295 +705bonus pay for amazing work on #OSS 00010000295 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000296 +705bonus pay for amazing work on #OSS 00010000296 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000297 +705bonus pay for amazing work on #OSS 00010000297 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000298 +705bonus pay for amazing work on #OSS 00010000298 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000299 +705bonus pay for amazing work on #OSS 00010000299 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000300 +705bonus pay for amazing work on #OSS 00010000300 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000301 +705bonus pay for amazing work on #OSS 00010000301 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000302 +705bonus pay for amazing work on #OSS 00010000302 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000303 +705bonus pay for amazing work on #OSS 00010000303 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000304 +705bonus pay for amazing work on #OSS 00010000304 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000305 +705bonus pay for amazing work on #OSS 00010000305 +62223138010481967038518 0000100000#9Bi5N80svoNJF#Joshua Thompson 1121042880000306 +705bonus pay for amazing work on #OSS 00010000306 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Joshua Moore 1121042880000307 +705bonus pay for amazing work on #OSS 00010000307 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000308 +705bonus pay for amazing work on #OSS 00010000308 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000309 +705bonus pay for amazing work on #OSS 00010000309 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000310 +705bonus pay for amazing work on #OSS 00010000310 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000311 +705bonus pay for amazing work on #OSS 00010000311 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000312 +705bonus pay for amazing work on #OSS 00010000312 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000313 +705bonus pay for amazing work on #OSS 00010000313 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000314 +705bonus pay for amazing work on #OSS 00010000314 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000315 +705bonus pay for amazing work on #OSS 00010000315 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000316 +705bonus pay for amazing work on #OSS 00010000316 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000317 +705bonus pay for amazing work on #OSS 00010000317 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000318 +705bonus pay for amazing work on #OSS 00010000318 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000319 +705bonus pay for amazing work on #OSS 00010000319 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000320 +705bonus pay for amazing work on #OSS 00010000320 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000321 +705bonus pay for amazing work on #OSS 00010000321 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000322 +705bonus pay for amazing work on #OSS 00010000322 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000323 +705bonus pay for amazing work on #OSS 00010000323 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000324 +705bonus pay for amazing work on #OSS 00010000324 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000325 +705bonus pay for amazing work on #OSS 00010000325 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000326 +705bonus pay for amazing work on #OSS 00010000326 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000327 +705bonus pay for amazing work on #OSS 00010000327 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000328 +705bonus pay for amazing work on #OSS 00010000328 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000329 +705bonus pay for amazing work on #OSS 00010000329 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000330 +705bonus pay for amazing work on #OSS 00010000330 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000331 +705bonus pay for amazing work on #OSS 00010000331 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000332 +705bonus pay for amazing work on #OSS 00010000332 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000333 +705bonus pay for amazing work on #OSS 00010000333 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000334 +705bonus pay for amazing work on #OSS 00010000334 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000335 +705bonus pay for amazing work on #OSS 00010000335 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000336 +705bonus pay for amazing work on #OSS 00010000336 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000337 +705bonus pay for amazing work on #OSS 00010000337 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000338 +705bonus pay for amazing work on #OSS 00010000338 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000339 +705bonus pay for amazing work on #OSS 00010000339 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000340 +705bonus pay for amazing work on #OSS 00010000340 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000341 +705bonus pay for amazing work on #OSS 00010000341 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000342 +705bonus pay for amazing work on #OSS 00010000342 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000343 +705bonus pay for amazing work on #OSS 00010000343 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000344 +705bonus pay for amazing work on #OSS 00010000344 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000345 +705bonus pay for amazing work on #OSS 00010000345 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000346 +705bonus pay for amazing work on #OSS 00010000346 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000347 +705bonus pay for amazing work on #OSS 00010000347 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000348 +705bonus pay for amazing work on #OSS 00010000348 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000349 +705bonus pay for amazing work on #OSS 00010000349 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000350 +705bonus pay for amazing work on #OSS 00010000350 +62223138010481967038518 0000100000#lPIfNp4r6BV5F#Alexander Moore 1121042880000351 +705bonus pay for amazing work on #OSS 00010000351 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000352 +705bonus pay for amazing work on #OSS 00010000352 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000353 +705bonus pay for amazing work on #OSS 00010000353 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000354 +705bonus pay for amazing work on #OSS 00010000354 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000355 +705bonus pay for amazing work on #OSS 00010000355 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000356 +705bonus pay for amazing work on #OSS 00010000356 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000357 +705bonus pay for amazing work on #OSS 00010000357 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000358 +705bonus pay for amazing work on #OSS 00010000358 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000359 +705bonus pay for amazing work on #OSS 00010000359 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000360 +705bonus pay for amazing work on #OSS 00010000360 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000361 +705bonus pay for amazing work on #OSS 00010000361 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000362 +705bonus pay for amazing work on #OSS 00010000362 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000363 +705bonus pay for amazing work on #OSS 00010000363 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000364 +705bonus pay for amazing work on #OSS 00010000364 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000365 +705bonus pay for amazing work on #OSS 00010000365 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000366 +705bonus pay for amazing work on #OSS 00010000366 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000367 +705bonus pay for amazing work on #OSS 00010000367 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000368 +705bonus pay for amazing work on #OSS 00010000368 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000369 +705bonus pay for amazing work on #OSS 00010000369 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000370 +705bonus pay for amazing work on #OSS 00010000370 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000371 +705bonus pay for amazing work on #OSS 00010000371 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000372 +705bonus pay for amazing work on #OSS 00010000372 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000373 +705bonus pay for amazing work on #OSS 00010000373 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000374 +705bonus pay for amazing work on #OSS 00010000374 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000375 +705bonus pay for amazing work on #OSS 00010000375 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000376 +705bonus pay for amazing work on #OSS 00010000376 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000377 +705bonus pay for amazing work on #OSS 00010000377 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000378 +705bonus pay for amazing work on #OSS 00010000378 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000379 +705bonus pay for amazing work on #OSS 00010000379 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000380 +705bonus pay for amazing work on #OSS 00010000380 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000381 +705bonus pay for amazing work on #OSS 00010000381 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000382 +705bonus pay for amazing work on #OSS 00010000382 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000383 +705bonus pay for amazing work on #OSS 00010000383 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000384 +705bonus pay for amazing work on #OSS 00010000384 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000385 +705bonus pay for amazing work on #OSS 00010000385 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000386 +705bonus pay for amazing work on #OSS 00010000386 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000387 +705bonus pay for amazing work on #OSS 00010000387 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000388 +705bonus pay for amazing work on #OSS 00010000388 +62223138010481967038518 0000100000#uFeYOF3QSaSEB#Olivia Jones 1121042880000389 +705bonus pay for amazing work on #OSS 00010000389 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000390 +705bonus pay for amazing work on #OSS 00010000390 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000391 +705bonus pay for amazing work on #OSS 00010000391 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000392 +705bonus pay for amazing work on #OSS 00010000392 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000393 +705bonus pay for amazing work on #OSS 00010000393 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000394 +705bonus pay for amazing work on #OSS 00010000394 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000395 +705bonus pay for amazing work on #OSS 00010000395 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000396 +705bonus pay for amazing work on #OSS 00010000396 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000397 +705bonus pay for amazing work on #OSS 00010000397 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000398 +705bonus pay for amazing work on #OSS 00010000398 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000399 +705bonus pay for amazing work on #OSS 00010000399 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000400 +705bonus pay for amazing work on #OSS 00010000400 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000401 +705bonus pay for amazing work on #OSS 00010000401 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000402 +705bonus pay for amazing work on #OSS 00010000402 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000403 +705bonus pay for amazing work on #OSS 00010000403 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000404 +705bonus pay for amazing work on #OSS 00010000404 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000405 +705bonus pay for amazing work on #OSS 00010000405 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000406 +705bonus pay for amazing work on #OSS 00010000406 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000407 +705bonus pay for amazing work on #OSS 00010000407 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000408 +705bonus pay for amazing work on #OSS 00010000408 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000409 +705bonus pay for amazing work on #OSS 00010000409 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000410 +705bonus pay for amazing work on #OSS 00010000410 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000411 +705bonus pay for amazing work on #OSS 00010000411 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000412 +705bonus pay for amazing work on #OSS 00010000412 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000413 +705bonus pay for amazing work on #OSS 00010000413 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000414 +705bonus pay for amazing work on #OSS 00010000414 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000415 +705bonus pay for amazing work on #OSS 00010000415 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000416 +705bonus pay for amazing work on #OSS 00010000416 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000417 +705bonus pay for amazing work on #OSS 00010000417 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000418 +705bonus pay for amazing work on #OSS 00010000418 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000419 +705bonus pay for amazing work on #OSS 00010000419 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000420 +705bonus pay for amazing work on #OSS 00010000420 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000421 +705bonus pay for amazing work on #OSS 00010000421 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000422 +705bonus pay for amazing work on #OSS 00010000422 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000423 +705bonus pay for amazing work on #OSS 00010000423 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000424 +705bonus pay for amazing work on #OSS 00010000424 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000425 +705bonus pay for amazing work on #OSS 00010000425 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000426 +705bonus pay for amazing work on #OSS 00010000426 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000427 +705bonus pay for amazing work on #OSS 00010000427 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000428 +705bonus pay for amazing work on #OSS 00010000428 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000429 +705bonus pay for amazing work on #OSS 00010000429 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000430 +705bonus pay for amazing work on #OSS 00010000430 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000431 +705bonus pay for amazing work on #OSS 00010000431 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000432 +705bonus pay for amazing work on #OSS 00010000432 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000433 +705bonus pay for amazing work on #OSS 00010000433 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000434 +705bonus pay for amazing work on #OSS 00010000434 +62223138010481967038518 0000100000#husWmwDGavS10#Zoey Robinson 1121042880000435 +705bonus pay for amazing work on #OSS 00010000435 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Aubrey Harris 1121042880000436 +705bonus pay for amazing work on #OSS 00010000436 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000437 +705bonus pay for amazing work on #OSS 00010000437 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000438 +705bonus pay for amazing work on #OSS 00010000438 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000439 +705bonus pay for amazing work on #OSS 00010000439 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000440 +705bonus pay for amazing work on #OSS 00010000440 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000441 +705bonus pay for amazing work on #OSS 00010000441 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000442 +705bonus pay for amazing work on #OSS 00010000442 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000443 +705bonus pay for amazing work on #OSS 00010000443 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000444 +705bonus pay for amazing work on #OSS 00010000444 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000445 +705bonus pay for amazing work on #OSS 00010000445 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000446 +705bonus pay for amazing work on #OSS 00010000446 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000447 +705bonus pay for amazing work on #OSS 00010000447 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000448 +705bonus pay for amazing work on #OSS 00010000448 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000449 +705bonus pay for amazing work on #OSS 00010000449 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000450 +705bonus pay for amazing work on #OSS 00010000450 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000451 +705bonus pay for amazing work on #OSS 00010000451 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000452 +705bonus pay for amazing work on #OSS 00010000452 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000453 +705bonus pay for amazing work on #OSS 00010000453 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000454 +705bonus pay for amazing work on #OSS 00010000454 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000455 +705bonus pay for amazing work on #OSS 00010000455 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000456 +705bonus pay for amazing work on #OSS 00010000456 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000457 +705bonus pay for amazing work on #OSS 00010000457 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000458 +705bonus pay for amazing work on #OSS 00010000458 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000459 +705bonus pay for amazing work on #OSS 00010000459 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000460 +705bonus pay for amazing work on #OSS 00010000460 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000461 +705bonus pay for amazing work on #OSS 00010000461 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000462 +705bonus pay for amazing work on #OSS 00010000462 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000463 +705bonus pay for amazing work on #OSS 00010000463 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000464 +705bonus pay for amazing work on #OSS 00010000464 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000465 +705bonus pay for amazing work on #OSS 00010000465 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000466 +705bonus pay for amazing work on #OSS 00010000466 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000467 +705bonus pay for amazing work on #OSS 00010000467 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000468 +705bonus pay for amazing work on #OSS 00010000468 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000469 +705bonus pay for amazing work on #OSS 00010000469 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000470 +705bonus pay for amazing work on #OSS 00010000470 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000471 +705bonus pay for amazing work on #OSS 00010000471 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000472 +705bonus pay for amazing work on #OSS 00010000472 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000473 +705bonus pay for amazing work on #OSS 00010000473 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000474 +705bonus pay for amazing work on #OSS 00010000474 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000475 +705bonus pay for amazing work on #OSS 00010000475 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000476 +705bonus pay for amazing work on #OSS 00010000476 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000477 +705bonus pay for amazing work on #OSS 00010000477 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000478 +705bonus pay for amazing work on #OSS 00010000478 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000479 +705bonus pay for amazing work on #OSS 00010000479 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000480 +705bonus pay for amazing work on #OSS 00010000480 +62223138010481967038518 0000100000#l1wVYNOIJiDRm#Anthony Harris 1121042880000481 +705bonus pay for amazing work on #OSS 00010000481 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Aiden Taylor 1121042880000482 +705bonus pay for amazing work on #OSS 00010000482 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000483 +705bonus pay for amazing work on #OSS 00010000483 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000484 +705bonus pay for amazing work on #OSS 00010000484 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000485 +705bonus pay for amazing work on #OSS 00010000485 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000486 +705bonus pay for amazing work on #OSS 00010000486 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000487 +705bonus pay for amazing work on #OSS 00010000487 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000488 +705bonus pay for amazing work on #OSS 00010000488 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000489 +705bonus pay for amazing work on #OSS 00010000489 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000490 +705bonus pay for amazing work on #OSS 00010000490 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000491 +705bonus pay for amazing work on #OSS 00010000491 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000492 +705bonus pay for amazing work on #OSS 00010000492 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000493 +705bonus pay for amazing work on #OSS 00010000493 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000494 +705bonus pay for amazing work on #OSS 00010000494 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000495 +705bonus pay for amazing work on #OSS 00010000495 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000496 +705bonus pay for amazing work on #OSS 00010000496 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000497 +705bonus pay for amazing work on #OSS 00010000497 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000498 +705bonus pay for amazing work on #OSS 00010000498 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000499 +705bonus pay for amazing work on #OSS 00010000499 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000500 +705bonus pay for amazing work on #OSS 00010000500 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000501 +705bonus pay for amazing work on #OSS 00010000501 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000502 +705bonus pay for amazing work on #OSS 00010000502 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000503 +705bonus pay for amazing work on #OSS 00010000503 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000504 +705bonus pay for amazing work on #OSS 00010000504 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000505 +705bonus pay for amazing work on #OSS 00010000505 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000506 +705bonus pay for amazing work on #OSS 00010000506 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000507 +705bonus pay for amazing work on #OSS 00010000507 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000508 +705bonus pay for amazing work on #OSS 00010000508 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000509 +705bonus pay for amazing work on #OSS 00010000509 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000510 +705bonus pay for amazing work on #OSS 00010000510 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000511 +705bonus pay for amazing work on #OSS 00010000511 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000512 +705bonus pay for amazing work on #OSS 00010000512 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000513 +705bonus pay for amazing work on #OSS 00010000513 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000514 +705bonus pay for amazing work on #OSS 00010000514 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000515 +705bonus pay for amazing work on #OSS 00010000515 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000516 +705bonus pay for amazing work on #OSS 00010000516 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000517 +705bonus pay for amazing work on #OSS 00010000517 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000518 +705bonus pay for amazing work on #OSS 00010000518 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000519 +705bonus pay for amazing work on #OSS 00010000519 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000520 +705bonus pay for amazing work on #OSS 00010000520 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000521 +705bonus pay for amazing work on #OSS 00010000521 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000522 +705bonus pay for amazing work on #OSS 00010000522 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000523 +705bonus pay for amazing work on #OSS 00010000523 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000524 +705bonus pay for amazing work on #OSS 00010000524 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000525 +705bonus pay for amazing work on #OSS 00010000525 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000526 +705bonus pay for amazing work on #OSS 00010000526 +62223138010481967038518 0000100000#rxozmMB7HOpEy#Elizabeth Taylor 1121042880000527 +705bonus pay for amazing work on #OSS 00010000527 +62223138010481967038518 0000100000#caAIqhsZgOclH#Aubrey Harris 1121042880000528 +705bonus pay for amazing work on #OSS 00010000528 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000529 +705bonus pay for amazing work on #OSS 00010000529 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000530 +705bonus pay for amazing work on #OSS 00010000530 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000531 +705bonus pay for amazing work on #OSS 00010000531 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000532 +705bonus pay for amazing work on #OSS 00010000532 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000533 +705bonus pay for amazing work on #OSS 00010000533 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000534 +705bonus pay for amazing work on #OSS 00010000534 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000535 +705bonus pay for amazing work on #OSS 00010000535 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000536 +705bonus pay for amazing work on #OSS 00010000536 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000537 +705bonus pay for amazing work on #OSS 00010000537 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000538 +705bonus pay for amazing work on #OSS 00010000538 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000539 +705bonus pay for amazing work on #OSS 00010000539 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000540 +705bonus pay for amazing work on #OSS 00010000540 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000541 +705bonus pay for amazing work on #OSS 00010000541 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000542 +705bonus pay for amazing work on #OSS 00010000542 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000543 +705bonus pay for amazing work on #OSS 00010000543 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000544 +705bonus pay for amazing work on #OSS 00010000544 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000545 +705bonus pay for amazing work on #OSS 00010000545 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000546 +705bonus pay for amazing work on #OSS 00010000546 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000547 +705bonus pay for amazing work on #OSS 00010000547 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000548 +705bonus pay for amazing work on #OSS 00010000548 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000549 +705bonus pay for amazing work on #OSS 00010000549 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000550 +705bonus pay for amazing work on #OSS 00010000550 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000551 +705bonus pay for amazing work on #OSS 00010000551 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000552 +705bonus pay for amazing work on #OSS 00010000552 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000553 +705bonus pay for amazing work on #OSS 00010000553 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000554 +705bonus pay for amazing work on #OSS 00010000554 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000555 +705bonus pay for amazing work on #OSS 00010000555 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000556 +705bonus pay for amazing work on #OSS 00010000556 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000557 +705bonus pay for amazing work on #OSS 00010000557 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000558 +705bonus pay for amazing work on #OSS 00010000558 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000559 +705bonus pay for amazing work on #OSS 00010000559 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000560 +705bonus pay for amazing work on #OSS 00010000560 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000561 +705bonus pay for amazing work on #OSS 00010000561 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000562 +705bonus pay for amazing work on #OSS 00010000562 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000563 +705bonus pay for amazing work on #OSS 00010000563 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000564 +705bonus pay for amazing work on #OSS 00010000564 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000565 +705bonus pay for amazing work on #OSS 00010000565 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000566 +705bonus pay for amazing work on #OSS 00010000566 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000567 +705bonus pay for amazing work on #OSS 00010000567 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000568 +705bonus pay for amazing work on #OSS 00010000568 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000569 +705bonus pay for amazing work on #OSS 00010000569 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000570 +705bonus pay for amazing work on #OSS 00010000570 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000571 +705bonus pay for amazing work on #OSS 00010000571 +62223138010481967038518 0000100000#caAIqhsZgOclH#Anthony Harris 1121042880000572 +705bonus pay for amazing work on #OSS 00010000572 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000573 +705bonus pay for amazing work on #OSS 00010000573 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000574 +705bonus pay for amazing work on #OSS 00010000574 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000575 +705bonus pay for amazing work on #OSS 00010000575 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000576 +705bonus pay for amazing work on #OSS 00010000576 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000577 +705bonus pay for amazing work on #OSS 00010000577 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000578 +705bonus pay for amazing work on #OSS 00010000578 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000579 +705bonus pay for amazing work on #OSS 00010000579 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000580 +705bonus pay for amazing work on #OSS 00010000580 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000581 +705bonus pay for amazing work on #OSS 00010000581 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000582 +705bonus pay for amazing work on #OSS 00010000582 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000583 +705bonus pay for amazing work on #OSS 00010000583 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000584 +705bonus pay for amazing work on #OSS 00010000584 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000585 +705bonus pay for amazing work on #OSS 00010000585 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000586 +705bonus pay for amazing work on #OSS 00010000586 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000587 +705bonus pay for amazing work on #OSS 00010000587 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000588 +705bonus pay for amazing work on #OSS 00010000588 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000589 +705bonus pay for amazing work on #OSS 00010000589 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000590 +705bonus pay for amazing work on #OSS 00010000590 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000591 +705bonus pay for amazing work on #OSS 00010000591 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000592 +705bonus pay for amazing work on #OSS 00010000592 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000593 +705bonus pay for amazing work on #OSS 00010000593 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000594 +705bonus pay for amazing work on #OSS 00010000594 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000595 +705bonus pay for amazing work on #OSS 00010000595 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000596 +705bonus pay for amazing work on #OSS 00010000596 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000597 +705bonus pay for amazing work on #OSS 00010000597 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000598 +705bonus pay for amazing work on #OSS 00010000598 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000599 +705bonus pay for amazing work on #OSS 00010000599 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000600 +705bonus pay for amazing work on #OSS 00010000600 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000601 +705bonus pay for amazing work on #OSS 00010000601 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000602 +705bonus pay for amazing work on #OSS 00010000602 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000603 +705bonus pay for amazing work on #OSS 00010000603 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000604 +705bonus pay for amazing work on #OSS 00010000604 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000605 +705bonus pay for amazing work on #OSS 00010000605 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000606 +705bonus pay for amazing work on #OSS 00010000606 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000607 +705bonus pay for amazing work on #OSS 00010000607 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000608 +705bonus pay for amazing work on #OSS 00010000608 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000609 +705bonus pay for amazing work on #OSS 00010000609 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000610 +705bonus pay for amazing work on #OSS 00010000610 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000611 +705bonus pay for amazing work on #OSS 00010000611 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000612 +705bonus pay for amazing work on #OSS 00010000612 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000613 +705bonus pay for amazing work on #OSS 00010000613 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000614 +705bonus pay for amazing work on #OSS 00010000614 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000615 +705bonus pay for amazing work on #OSS 00010000615 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000616 +705bonus pay for amazing work on #OSS 00010000616 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000617 +705bonus pay for amazing work on #OSS 00010000617 +62223138010481967038518 0000100000#x1jHmTjFyyYh4#Ella Thomas 1121042880000618 +705bonus pay for amazing work on #OSS 00010000618 +62223138010481967038518 0000100000#WUTU148g1HtSt#Abigail Miller 1121042880000619 +705bonus pay for amazing work on #OSS 00010000619 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000620 +705bonus pay for amazing work on #OSS 00010000620 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000621 +705bonus pay for amazing work on #OSS 00010000621 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000622 +705bonus pay for amazing work on #OSS 00010000622 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000623 +705bonus pay for amazing work on #OSS 00010000623 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000624 +705bonus pay for amazing work on #OSS 00010000624 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000625 +705bonus pay for amazing work on #OSS 00010000625 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000626 +705bonus pay for amazing work on #OSS 00010000626 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000627 +705bonus pay for amazing work on #OSS 00010000627 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000628 +705bonus pay for amazing work on #OSS 00010000628 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000629 +705bonus pay for amazing work on #OSS 00010000629 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000630 +705bonus pay for amazing work on #OSS 00010000630 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000631 +705bonus pay for amazing work on #OSS 00010000631 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000632 +705bonus pay for amazing work on #OSS 00010000632 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000633 +705bonus pay for amazing work on #OSS 00010000633 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000634 +705bonus pay for amazing work on #OSS 00010000634 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000635 +705bonus pay for amazing work on #OSS 00010000635 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000636 +705bonus pay for amazing work on #OSS 00010000636 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000637 +705bonus pay for amazing work on #OSS 00010000637 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000638 +705bonus pay for amazing work on #OSS 00010000638 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000639 +705bonus pay for amazing work on #OSS 00010000639 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000640 +705bonus pay for amazing work on #OSS 00010000640 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000641 +705bonus pay for amazing work on #OSS 00010000641 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000642 +705bonus pay for amazing work on #OSS 00010000642 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000643 +705bonus pay for amazing work on #OSS 00010000643 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000644 +705bonus pay for amazing work on #OSS 00010000644 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000645 +705bonus pay for amazing work on #OSS 00010000645 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000646 +705bonus pay for amazing work on #OSS 00010000646 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000647 +705bonus pay for amazing work on #OSS 00010000647 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000648 +705bonus pay for amazing work on #OSS 00010000648 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000649 +705bonus pay for amazing work on #OSS 00010000649 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000650 +705bonus pay for amazing work on #OSS 00010000650 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000651 +705bonus pay for amazing work on #OSS 00010000651 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000652 +705bonus pay for amazing work on #OSS 00010000652 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000653 +705bonus pay for amazing work on #OSS 00010000653 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000654 +705bonus pay for amazing work on #OSS 00010000654 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000655 +705bonus pay for amazing work on #OSS 00010000655 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000656 +705bonus pay for amazing work on #OSS 00010000656 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000657 +705bonus pay for amazing work on #OSS 00010000657 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000658 +705bonus pay for amazing work on #OSS 00010000658 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000659 +705bonus pay for amazing work on #OSS 00010000659 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000660 +705bonus pay for amazing work on #OSS 00010000660 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000661 +705bonus pay for amazing work on #OSS 00010000661 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000662 +705bonus pay for amazing work on #OSS 00010000662 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000663 +705bonus pay for amazing work on #OSS 00010000663 +62223138010481967038518 0000100000#WUTU148g1HtSt#Jayden Miller 1121042880000664 +705bonus pay for amazing work on #OSS 00010000664 +62223138010481967038518 0000100000#jkcOD2PnETieL#Noah Jones 1121042880000665 +705bonus pay for amazing work on #OSS 00010000665 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000666 +705bonus pay for amazing work on #OSS 00010000666 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000667 +705bonus pay for amazing work on #OSS 00010000667 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000668 +705bonus pay for amazing work on #OSS 00010000668 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000669 +705bonus pay for amazing work on #OSS 00010000669 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000670 +705bonus pay for amazing work on #OSS 00010000670 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000671 +705bonus pay for amazing work on #OSS 00010000671 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000672 +705bonus pay for amazing work on #OSS 00010000672 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000673 +705bonus pay for amazing work on #OSS 00010000673 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000674 +705bonus pay for amazing work on #OSS 00010000674 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000675 +705bonus pay for amazing work on #OSS 00010000675 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000676 +705bonus pay for amazing work on #OSS 00010000676 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000677 +705bonus pay for amazing work on #OSS 00010000677 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000678 +705bonus pay for amazing work on #OSS 00010000678 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000679 +705bonus pay for amazing work on #OSS 00010000679 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000680 +705bonus pay for amazing work on #OSS 00010000680 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000681 +705bonus pay for amazing work on #OSS 00010000681 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000682 +705bonus pay for amazing work on #OSS 00010000682 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000683 +705bonus pay for amazing work on #OSS 00010000683 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000684 +705bonus pay for amazing work on #OSS 00010000684 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000685 +705bonus pay for amazing work on #OSS 00010000685 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000686 +705bonus pay for amazing work on #OSS 00010000686 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000687 +705bonus pay for amazing work on #OSS 00010000687 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000688 +705bonus pay for amazing work on #OSS 00010000688 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000689 +705bonus pay for amazing work on #OSS 00010000689 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000690 +705bonus pay for amazing work on #OSS 00010000690 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000691 +705bonus pay for amazing work on #OSS 00010000691 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000692 +705bonus pay for amazing work on #OSS 00010000692 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000693 +705bonus pay for amazing work on #OSS 00010000693 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000694 +705bonus pay for amazing work on #OSS 00010000694 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000695 +705bonus pay for amazing work on #OSS 00010000695 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000696 +705bonus pay for amazing work on #OSS 00010000696 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000697 +705bonus pay for amazing work on #OSS 00010000697 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000698 +705bonus pay for amazing work on #OSS 00010000698 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000699 +705bonus pay for amazing work on #OSS 00010000699 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000700 +705bonus pay for amazing work on #OSS 00010000700 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000701 +705bonus pay for amazing work on #OSS 00010000701 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000702 +705bonus pay for amazing work on #OSS 00010000702 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000703 +705bonus pay for amazing work on #OSS 00010000703 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000704 +705bonus pay for amazing work on #OSS 00010000704 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000705 +705bonus pay for amazing work on #OSS 00010000705 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000706 +705bonus pay for amazing work on #OSS 00010000706 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000707 +705bonus pay for amazing work on #OSS 00010000707 +62223138010481967038518 0000100000#jkcOD2PnETieL#Olivia Jones 1121042880000708 +705bonus pay for amazing work on #OSS 00010000708 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Isabella Williams 1121042880000709 +705bonus pay for amazing work on #OSS 00010000709 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000710 +705bonus pay for amazing work on #OSS 00010000710 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000711 +705bonus pay for amazing work on #OSS 00010000711 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000712 +705bonus pay for amazing work on #OSS 00010000712 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000713 +705bonus pay for amazing work on #OSS 00010000713 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000714 +705bonus pay for amazing work on #OSS 00010000714 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000715 +705bonus pay for amazing work on #OSS 00010000715 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000716 +705bonus pay for amazing work on #OSS 00010000716 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000717 +705bonus pay for amazing work on #OSS 00010000717 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000718 +705bonus pay for amazing work on #OSS 00010000718 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000719 +705bonus pay for amazing work on #OSS 00010000719 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000720 +705bonus pay for amazing work on #OSS 00010000720 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000721 +705bonus pay for amazing work on #OSS 00010000721 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000722 +705bonus pay for amazing work on #OSS 00010000722 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000723 +705bonus pay for amazing work on #OSS 00010000723 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000724 +705bonus pay for amazing work on #OSS 00010000724 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000725 +705bonus pay for amazing work on #OSS 00010000725 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000726 +705bonus pay for amazing work on #OSS 00010000726 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000727 +705bonus pay for amazing work on #OSS 00010000727 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000728 +705bonus pay for amazing work on #OSS 00010000728 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000729 +705bonus pay for amazing work on #OSS 00010000729 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000730 +705bonus pay for amazing work on #OSS 00010000730 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000731 +705bonus pay for amazing work on #OSS 00010000731 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000732 +705bonus pay for amazing work on #OSS 00010000732 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000733 +705bonus pay for amazing work on #OSS 00010000733 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000734 +705bonus pay for amazing work on #OSS 00010000734 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000735 +705bonus pay for amazing work on #OSS 00010000735 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000736 +705bonus pay for amazing work on #OSS 00010000736 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000737 +705bonus pay for amazing work on #OSS 00010000737 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000738 +705bonus pay for amazing work on #OSS 00010000738 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000739 +705bonus pay for amazing work on #OSS 00010000739 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000740 +705bonus pay for amazing work on #OSS 00010000740 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000741 +705bonus pay for amazing work on #OSS 00010000741 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000742 +705bonus pay for amazing work on #OSS 00010000742 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000743 +705bonus pay for amazing work on #OSS 00010000743 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000744 +705bonus pay for amazing work on #OSS 00010000744 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000745 +705bonus pay for amazing work on #OSS 00010000745 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000746 +705bonus pay for amazing work on #OSS 00010000746 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000747 +705bonus pay for amazing work on #OSS 00010000747 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000748 +705bonus pay for amazing work on #OSS 00010000748 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000749 +705bonus pay for amazing work on #OSS 00010000749 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000750 +705bonus pay for amazing work on #OSS 00010000750 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000751 +705bonus pay for amazing work on #OSS 00010000751 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000752 +705bonus pay for amazing work on #OSS 00010000752 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000753 +705bonus pay for amazing work on #OSS 00010000753 +62223138010481967038518 0000100000#hEnRfOAU59cIc#Ethan Williams 1121042880000754 +705bonus pay for amazing work on #OSS 00010000754 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ethan Thomas 1121042880000755 +705bonus pay for amazing work on #OSS 00010000755 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000756 +705bonus pay for amazing work on #OSS 00010000756 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000757 +705bonus pay for amazing work on #OSS 00010000757 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000758 +705bonus pay for amazing work on #OSS 00010000758 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000759 +705bonus pay for amazing work on #OSS 00010000759 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000760 +705bonus pay for amazing work on #OSS 00010000760 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000761 +705bonus pay for amazing work on #OSS 00010000761 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000762 +705bonus pay for amazing work on #OSS 00010000762 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000763 +705bonus pay for amazing work on #OSS 00010000763 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000764 +705bonus pay for amazing work on #OSS 00010000764 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000765 +705bonus pay for amazing work on #OSS 00010000765 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000766 +705bonus pay for amazing work on #OSS 00010000766 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000767 +705bonus pay for amazing work on #OSS 00010000767 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000768 +705bonus pay for amazing work on #OSS 00010000768 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000769 +705bonus pay for amazing work on #OSS 00010000769 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000770 +705bonus pay for amazing work on #OSS 00010000770 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000771 +705bonus pay for amazing work on #OSS 00010000771 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000772 +705bonus pay for amazing work on #OSS 00010000772 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000773 +705bonus pay for amazing work on #OSS 00010000773 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000774 +705bonus pay for amazing work on #OSS 00010000774 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000775 +705bonus pay for amazing work on #OSS 00010000775 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000776 +705bonus pay for amazing work on #OSS 00010000776 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000777 +705bonus pay for amazing work on #OSS 00010000777 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000778 +705bonus pay for amazing work on #OSS 00010000778 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000779 +705bonus pay for amazing work on #OSS 00010000779 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000780 +705bonus pay for amazing work on #OSS 00010000780 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000781 +705bonus pay for amazing work on #OSS 00010000781 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000782 +705bonus pay for amazing work on #OSS 00010000782 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000783 +705bonus pay for amazing work on #OSS 00010000783 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000784 +705bonus pay for amazing work on #OSS 00010000784 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000785 +705bonus pay for amazing work on #OSS 00010000785 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000786 +705bonus pay for amazing work on #OSS 00010000786 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000787 +705bonus pay for amazing work on #OSS 00010000787 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000788 +705bonus pay for amazing work on #OSS 00010000788 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000789 +705bonus pay for amazing work on #OSS 00010000789 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000790 +705bonus pay for amazing work on #OSS 00010000790 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000791 +705bonus pay for amazing work on #OSS 00010000791 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000792 +705bonus pay for amazing work on #OSS 00010000792 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000793 +705bonus pay for amazing work on #OSS 00010000793 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000794 +705bonus pay for amazing work on #OSS 00010000794 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000795 +705bonus pay for amazing work on #OSS 00010000795 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000796 +705bonus pay for amazing work on #OSS 00010000796 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000797 +705bonus pay for amazing work on #OSS 00010000797 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000798 +705bonus pay for amazing work on #OSS 00010000798 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000799 +705bonus pay for amazing work on #OSS 00010000799 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000800 +705bonus pay for amazing work on #OSS 00010000800 +62223138010481967038518 0000100000#fbWjGodTNtPGD#Ella Thomas 1121042880000801 +705bonus pay for amazing work on #OSS 00010000801 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000802 +705bonus pay for amazing work on #OSS 00010000802 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000803 +705bonus pay for amazing work on #OSS 00010000803 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000804 +705bonus pay for amazing work on #OSS 00010000804 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000805 +705bonus pay for amazing work on #OSS 00010000805 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000806 +705bonus pay for amazing work on #OSS 00010000806 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000807 +705bonus pay for amazing work on #OSS 00010000807 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000808 +705bonus pay for amazing work on #OSS 00010000808 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000809 +705bonus pay for amazing work on #OSS 00010000809 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000810 +705bonus pay for amazing work on #OSS 00010000810 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000811 +705bonus pay for amazing work on #OSS 00010000811 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000812 +705bonus pay for amazing work on #OSS 00010000812 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000813 +705bonus pay for amazing work on #OSS 00010000813 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000814 +705bonus pay for amazing work on #OSS 00010000814 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000815 +705bonus pay for amazing work on #OSS 00010000815 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000816 +705bonus pay for amazing work on #OSS 00010000816 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000817 +705bonus pay for amazing work on #OSS 00010000817 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000818 +705bonus pay for amazing work on #OSS 00010000818 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000819 +705bonus pay for amazing work on #OSS 00010000819 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000820 +705bonus pay for amazing work on #OSS 00010000820 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000821 +705bonus pay for amazing work on #OSS 00010000821 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000822 +705bonus pay for amazing work on #OSS 00010000822 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000823 +705bonus pay for amazing work on #OSS 00010000823 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000824 +705bonus pay for amazing work on #OSS 00010000824 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000825 +705bonus pay for amazing work on #OSS 00010000825 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000826 +705bonus pay for amazing work on #OSS 00010000826 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000827 +705bonus pay for amazing work on #OSS 00010000827 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000828 +705bonus pay for amazing work on #OSS 00010000828 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000829 +705bonus pay for amazing work on #OSS 00010000829 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000830 +705bonus pay for amazing work on #OSS 00010000830 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000831 +705bonus pay for amazing work on #OSS 00010000831 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000832 +705bonus pay for amazing work on #OSS 00010000832 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000833 +705bonus pay for amazing work on #OSS 00010000833 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000834 +705bonus pay for amazing work on #OSS 00010000834 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000835 +705bonus pay for amazing work on #OSS 00010000835 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000836 +705bonus pay for amazing work on #OSS 00010000836 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000837 +705bonus pay for amazing work on #OSS 00010000837 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000838 +705bonus pay for amazing work on #OSS 00010000838 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000839 +705bonus pay for amazing work on #OSS 00010000839 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000840 +705bonus pay for amazing work on #OSS 00010000840 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000841 +705bonus pay for amazing work on #OSS 00010000841 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000842 +705bonus pay for amazing work on #OSS 00010000842 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000843 +705bonus pay for amazing work on #OSS 00010000843 +62223138010481967038518 0000100000#VnakyV86yqfKh#Daniel Anderson 1121042880000844 +705bonus pay for amazing work on #OSS 00010000844 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000845 +705bonus pay for amazing work on #OSS 00010000845 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000846 +705bonus pay for amazing work on #OSS 00010000846 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000847 +705bonus pay for amazing work on #OSS 00010000847 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000848 +705bonus pay for amazing work on #OSS 00010000848 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000849 +705bonus pay for amazing work on #OSS 00010000849 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000850 +705bonus pay for amazing work on #OSS 00010000850 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000851 +705bonus pay for amazing work on #OSS 00010000851 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000852 +705bonus pay for amazing work on #OSS 00010000852 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000853 +705bonus pay for amazing work on #OSS 00010000853 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000854 +705bonus pay for amazing work on #OSS 00010000854 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000855 +705bonus pay for amazing work on #OSS 00010000855 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000856 +705bonus pay for amazing work on #OSS 00010000856 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000857 +705bonus pay for amazing work on #OSS 00010000857 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000858 +705bonus pay for amazing work on #OSS 00010000858 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000859 +705bonus pay for amazing work on #OSS 00010000859 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000860 +705bonus pay for amazing work on #OSS 00010000860 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000861 +705bonus pay for amazing work on #OSS 00010000861 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000862 +705bonus pay for amazing work on #OSS 00010000862 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000863 +705bonus pay for amazing work on #OSS 00010000863 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000864 +705bonus pay for amazing work on #OSS 00010000864 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000865 +705bonus pay for amazing work on #OSS 00010000865 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000866 +705bonus pay for amazing work on #OSS 00010000866 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000867 +705bonus pay for amazing work on #OSS 00010000867 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000868 +705bonus pay for amazing work on #OSS 00010000868 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000869 +705bonus pay for amazing work on #OSS 00010000869 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000870 +705bonus pay for amazing work on #OSS 00010000870 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000871 +705bonus pay for amazing work on #OSS 00010000871 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000872 +705bonus pay for amazing work on #OSS 00010000872 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000873 +705bonus pay for amazing work on #OSS 00010000873 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000874 +705bonus pay for amazing work on #OSS 00010000874 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000875 +705bonus pay for amazing work on #OSS 00010000875 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000876 +705bonus pay for amazing work on #OSS 00010000876 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000877 +705bonus pay for amazing work on #OSS 00010000877 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000878 +705bonus pay for amazing work on #OSS 00010000878 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000879 +705bonus pay for amazing work on #OSS 00010000879 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000880 +705bonus pay for amazing work on #OSS 00010000880 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000881 +705bonus pay for amazing work on #OSS 00010000881 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000882 +705bonus pay for amazing work on #OSS 00010000882 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000883 +705bonus pay for amazing work on #OSS 00010000883 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000884 +705bonus pay for amazing work on #OSS 00010000884 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000885 +705bonus pay for amazing work on #OSS 00010000885 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000886 +705bonus pay for amazing work on #OSS 00010000886 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000887 +705bonus pay for amazing work on #OSS 00010000887 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000888 +705bonus pay for amazing work on #OSS 00010000888 +62223138010481967038518 0000100000#Klz2TULxetARj#William Brown 1121042880000889 +705bonus pay for amazing work on #OSS 00010000889 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000890 +705bonus pay for amazing work on #OSS 00010000890 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000891 +705bonus pay for amazing work on #OSS 00010000891 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000892 +705bonus pay for amazing work on #OSS 00010000892 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000893 +705bonus pay for amazing work on #OSS 00010000893 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000894 +705bonus pay for amazing work on #OSS 00010000894 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000895 +705bonus pay for amazing work on #OSS 00010000895 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000896 +705bonus pay for amazing work on #OSS 00010000896 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000897 +705bonus pay for amazing work on #OSS 00010000897 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000898 +705bonus pay for amazing work on #OSS 00010000898 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000899 +705bonus pay for amazing work on #OSS 00010000899 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000900 +705bonus pay for amazing work on #OSS 00010000900 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000901 +705bonus pay for amazing work on #OSS 00010000901 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000902 +705bonus pay for amazing work on #OSS 00010000902 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000903 +705bonus pay for amazing work on #OSS 00010000903 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000904 +705bonus pay for amazing work on #OSS 00010000904 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000905 +705bonus pay for amazing work on #OSS 00010000905 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000906 +705bonus pay for amazing work on #OSS 00010000906 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000907 +705bonus pay for amazing work on #OSS 00010000907 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000908 +705bonus pay for amazing work on #OSS 00010000908 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000909 +705bonus pay for amazing work on #OSS 00010000909 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000910 +705bonus pay for amazing work on #OSS 00010000910 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000911 +705bonus pay for amazing work on #OSS 00010000911 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000912 +705bonus pay for amazing work on #OSS 00010000912 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000913 +705bonus pay for amazing work on #OSS 00010000913 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000914 +705bonus pay for amazing work on #OSS 00010000914 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000915 +705bonus pay for amazing work on #OSS 00010000915 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000916 +705bonus pay for amazing work on #OSS 00010000916 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000917 +705bonus pay for amazing work on #OSS 00010000917 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000918 +705bonus pay for amazing work on #OSS 00010000918 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000919 +705bonus pay for amazing work on #OSS 00010000919 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000920 +705bonus pay for amazing work on #OSS 00010000920 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000921 +705bonus pay for amazing work on #OSS 00010000921 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000922 +705bonus pay for amazing work on #OSS 00010000922 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000923 +705bonus pay for amazing work on #OSS 00010000923 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000924 +705bonus pay for amazing work on #OSS 00010000924 +62223138010481967038518 0000100000#4Tp1xCeUjGQlg#Lily Martin 1121042880000925 +705bonus pay for amazing work on #OSS 00010000925 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000926 +705bonus pay for amazing work on #OSS 00010000926 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000927 +705bonus pay for amazing work on #OSS 00010000927 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000928 +705bonus pay for amazing work on #OSS 00010000928 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000929 +705bonus pay for amazing work on #OSS 00010000929 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000930 +705bonus pay for amazing work on #OSS 00010000930 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000931 +705bonus pay for amazing work on #OSS 00010000931 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000932 +705bonus pay for amazing work on #OSS 00010000932 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000933 +705bonus pay for amazing work on #OSS 00010000933 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000934 +705bonus pay for amazing work on #OSS 00010000934 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000935 +705bonus pay for amazing work on #OSS 00010000935 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000936 +705bonus pay for amazing work on #OSS 00010000936 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000937 +705bonus pay for amazing work on #OSS 00010000937 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000938 +705bonus pay for amazing work on #OSS 00010000938 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000939 +705bonus pay for amazing work on #OSS 00010000939 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000940 +705bonus pay for amazing work on #OSS 00010000940 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000941 +705bonus pay for amazing work on #OSS 00010000941 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000942 +705bonus pay for amazing work on #OSS 00010000942 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000943 +705bonus pay for amazing work on #OSS 00010000943 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000944 +705bonus pay for amazing work on #OSS 00010000944 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000945 +705bonus pay for amazing work on #OSS 00010000945 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000946 +705bonus pay for amazing work on #OSS 00010000946 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000947 +705bonus pay for amazing work on #OSS 00010000947 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000948 +705bonus pay for amazing work on #OSS 00010000948 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000949 +705bonus pay for amazing work on #OSS 00010000949 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000950 +705bonus pay for amazing work on #OSS 00010000950 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000951 +705bonus pay for amazing work on #OSS 00010000951 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000952 +705bonus pay for amazing work on #OSS 00010000952 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000953 +705bonus pay for amazing work on #OSS 00010000953 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000954 +705bonus pay for amazing work on #OSS 00010000954 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000955 +705bonus pay for amazing work on #OSS 00010000955 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000956 +705bonus pay for amazing work on #OSS 00010000956 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000957 +705bonus pay for amazing work on #OSS 00010000957 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000958 +705bonus pay for amazing work on #OSS 00010000958 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000959 +705bonus pay for amazing work on #OSS 00010000959 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000960 +705bonus pay for amazing work on #OSS 00010000960 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000961 +705bonus pay for amazing work on #OSS 00010000961 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000962 +705bonus pay for amazing work on #OSS 00010000962 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000963 +705bonus pay for amazing work on #OSS 00010000963 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000964 +705bonus pay for amazing work on #OSS 00010000964 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000965 +705bonus pay for amazing work on #OSS 00010000965 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000966 +705bonus pay for amazing work on #OSS 00010000966 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000967 +705bonus pay for amazing work on #OSS 00010000967 +62223138010481967038518 0000100000#o8KQbv7XgoeWm#Mia Wilson 1121042880000968 +705bonus pay for amazing work on #OSS 00010000968 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Madison Moore 1121042880000969 +705bonus pay for amazing work on #OSS 00010000969 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000970 +705bonus pay for amazing work on #OSS 00010000970 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000971 +705bonus pay for amazing work on #OSS 00010000971 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000972 +705bonus pay for amazing work on #OSS 00010000972 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000973 +705bonus pay for amazing work on #OSS 00010000973 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000974 +705bonus pay for amazing work on #OSS 00010000974 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000975 +705bonus pay for amazing work on #OSS 00010000975 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000976 +705bonus pay for amazing work on #OSS 00010000976 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000977 +705bonus pay for amazing work on #OSS 00010000977 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000978 +705bonus pay for amazing work on #OSS 00010000978 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000979 +705bonus pay for amazing work on #OSS 00010000979 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000980 +705bonus pay for amazing work on #OSS 00010000980 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000981 +705bonus pay for amazing work on #OSS 00010000981 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000982 +705bonus pay for amazing work on #OSS 00010000982 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000983 +705bonus pay for amazing work on #OSS 00010000983 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000984 +705bonus pay for amazing work on #OSS 00010000984 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000985 +705bonus pay for amazing work on #OSS 00010000985 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000986 +705bonus pay for amazing work on #OSS 00010000986 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000987 +705bonus pay for amazing work on #OSS 00010000987 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000988 +705bonus pay for amazing work on #OSS 00010000988 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000989 +705bonus pay for amazing work on #OSS 00010000989 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000990 +705bonus pay for amazing work on #OSS 00010000990 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000991 +705bonus pay for amazing work on #OSS 00010000991 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000992 +705bonus pay for amazing work on #OSS 00010000992 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000993 +705bonus pay for amazing work on #OSS 00010000993 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000994 +705bonus pay for amazing work on #OSS 00010000994 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000995 +705bonus pay for amazing work on #OSS 00010000995 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000996 +705bonus pay for amazing work on #OSS 00010000996 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000997 +705bonus pay for amazing work on #OSS 00010000997 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000998 +705bonus pay for amazing work on #OSS 00010000998 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880000999 +705bonus pay for amazing work on #OSS 00010000999 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880001000 +705bonus pay for amazing work on #OSS 00010001000 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880001001 +705bonus pay for amazing work on #OSS 00010001001 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880001002 +705bonus pay for amazing work on #OSS 00010001002 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880001003 +705bonus pay for amazing work on #OSS 00010001003 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880001004 +705bonus pay for amazing work on #OSS 00010001004 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880001005 +705bonus pay for amazing work on #OSS 00010001005 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880001006 +705bonus pay for amazing work on #OSS 00010001006 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880001007 +705bonus pay for amazing work on #OSS 00010001007 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880001008 +705bonus pay for amazing work on #OSS 00010001008 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880001009 +705bonus pay for amazing work on #OSS 00010001009 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880001010 +705bonus pay for amazing work on #OSS 00010001010 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880001011 +705bonus pay for amazing work on #OSS 00010001011 +62223138010481967038518 0000100000#IZ9w1ixd38wUo#Alexander Moore 1121042880001012 +705bonus pay for amazing work on #OSS 00010001012 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Alexander Thompson 1121042880001013 +705bonus pay for amazing work on #OSS 00010001013 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001014 +705bonus pay for amazing work on #OSS 00010001014 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001015 +705bonus pay for amazing work on #OSS 00010001015 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001016 +705bonus pay for amazing work on #OSS 00010001016 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001017 +705bonus pay for amazing work on #OSS 00010001017 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001018 +705bonus pay for amazing work on #OSS 00010001018 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001019 +705bonus pay for amazing work on #OSS 00010001019 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001020 +705bonus pay for amazing work on #OSS 00010001020 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001021 +705bonus pay for amazing work on #OSS 00010001021 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001022 +705bonus pay for amazing work on #OSS 00010001022 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001023 +705bonus pay for amazing work on #OSS 00010001023 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001024 +705bonus pay for amazing work on #OSS 00010001024 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001025 +705bonus pay for amazing work on #OSS 00010001025 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001026 +705bonus pay for amazing work on #OSS 00010001026 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001027 +705bonus pay for amazing work on #OSS 00010001027 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001028 +705bonus pay for amazing work on #OSS 00010001028 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001029 +705bonus pay for amazing work on #OSS 00010001029 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001030 +705bonus pay for amazing work on #OSS 00010001030 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001031 +705bonus pay for amazing work on #OSS 00010001031 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001032 +705bonus pay for amazing work on #OSS 00010001032 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001033 +705bonus pay for amazing work on #OSS 00010001033 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001034 +705bonus pay for amazing work on #OSS 00010001034 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001035 +705bonus pay for amazing work on #OSS 00010001035 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001036 +705bonus pay for amazing work on #OSS 00010001036 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001037 +705bonus pay for amazing work on #OSS 00010001037 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001038 +705bonus pay for amazing work on #OSS 00010001038 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001039 +705bonus pay for amazing work on #OSS 00010001039 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001040 +705bonus pay for amazing work on #OSS 00010001040 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001041 +705bonus pay for amazing work on #OSS 00010001041 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001042 +705bonus pay for amazing work on #OSS 00010001042 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001043 +705bonus pay for amazing work on #OSS 00010001043 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001044 +705bonus pay for amazing work on #OSS 00010001044 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001045 +705bonus pay for amazing work on #OSS 00010001045 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001046 +705bonus pay for amazing work on #OSS 00010001046 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001047 +705bonus pay for amazing work on #OSS 00010001047 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001048 +705bonus pay for amazing work on #OSS 00010001048 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001049 +705bonus pay for amazing work on #OSS 00010001049 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001050 +705bonus pay for amazing work on #OSS 00010001050 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001051 +705bonus pay for amazing work on #OSS 00010001051 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001052 +705bonus pay for amazing work on #OSS 00010001052 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001053 +705bonus pay for amazing work on #OSS 00010001053 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001054 +705bonus pay for amazing work on #OSS 00010001054 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001055 +705bonus pay for amazing work on #OSS 00010001055 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001056 +705bonus pay for amazing work on #OSS 00010001056 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001057 +705bonus pay for amazing work on #OSS 00010001057 +62223138010481967038518 0000100000#4LShQ8fqlNbRQ#Joshua Thompson 1121042880001058 +705bonus pay for amazing work on #OSS 00010001058 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Joshua Miller 1121042880001059 +705bonus pay for amazing work on #OSS 00010001059 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001060 +705bonus pay for amazing work on #OSS 00010001060 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001061 +705bonus pay for amazing work on #OSS 00010001061 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001062 +705bonus pay for amazing work on #OSS 00010001062 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001063 +705bonus pay for amazing work on #OSS 00010001063 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001064 +705bonus pay for amazing work on #OSS 00010001064 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001065 +705bonus pay for amazing work on #OSS 00010001065 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001066 +705bonus pay for amazing work on #OSS 00010001066 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001067 +705bonus pay for amazing work on #OSS 00010001067 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001068 +705bonus pay for amazing work on #OSS 00010001068 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001069 +705bonus pay for amazing work on #OSS 00010001069 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001070 +705bonus pay for amazing work on #OSS 00010001070 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001071 +705bonus pay for amazing work on #OSS 00010001071 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001072 +705bonus pay for amazing work on #OSS 00010001072 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001073 +705bonus pay for amazing work on #OSS 00010001073 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001074 +705bonus pay for amazing work on #OSS 00010001074 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001075 +705bonus pay for amazing work on #OSS 00010001075 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001076 +705bonus pay for amazing work on #OSS 00010001076 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001077 +705bonus pay for amazing work on #OSS 00010001077 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001078 +705bonus pay for amazing work on #OSS 00010001078 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001079 +705bonus pay for amazing work on #OSS 00010001079 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001080 +705bonus pay for amazing work on #OSS 00010001080 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001081 +705bonus pay for amazing work on #OSS 00010001081 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001082 +705bonus pay for amazing work on #OSS 00010001082 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001083 +705bonus pay for amazing work on #OSS 00010001083 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001084 +705bonus pay for amazing work on #OSS 00010001084 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001085 +705bonus pay for amazing work on #OSS 00010001085 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001086 +705bonus pay for amazing work on #OSS 00010001086 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001087 +705bonus pay for amazing work on #OSS 00010001087 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001088 +705bonus pay for amazing work on #OSS 00010001088 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001089 +705bonus pay for amazing work on #OSS 00010001089 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001090 +705bonus pay for amazing work on #OSS 00010001090 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001091 +705bonus pay for amazing work on #OSS 00010001091 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001092 +705bonus pay for amazing work on #OSS 00010001092 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001093 +705bonus pay for amazing work on #OSS 00010001093 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001094 +705bonus pay for amazing work on #OSS 00010001094 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001095 +705bonus pay for amazing work on #OSS 00010001095 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001096 +705bonus pay for amazing work on #OSS 00010001096 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001097 +705bonus pay for amazing work on #OSS 00010001097 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001098 +705bonus pay for amazing work on #OSS 00010001098 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001099 +705bonus pay for amazing work on #OSS 00010001099 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001100 +705bonus pay for amazing work on #OSS 00010001100 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001101 +705bonus pay for amazing work on #OSS 00010001101 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001102 +705bonus pay for amazing work on #OSS 00010001102 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001103 +705bonus pay for amazing work on #OSS 00010001103 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001104 +705bonus pay for amazing work on #OSS 00010001104 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001105 +705bonus pay for amazing work on #OSS 00010001105 +62223138010481967038518 0000100000#K3kaZKZBLSVBS#Jayden Miller 1121042880001106 +705bonus pay for amazing work on #OSS 00010001106 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001107 +705bonus pay for amazing work on #OSS 00010001107 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001108 +705bonus pay for amazing work on #OSS 00010001108 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001109 +705bonus pay for amazing work on #OSS 00010001109 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001110 +705bonus pay for amazing work on #OSS 00010001110 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001111 +705bonus pay for amazing work on #OSS 00010001111 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001112 +705bonus pay for amazing work on #OSS 00010001112 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001113 +705bonus pay for amazing work on #OSS 00010001113 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001114 +705bonus pay for amazing work on #OSS 00010001114 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001115 +705bonus pay for amazing work on #OSS 00010001115 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001116 +705bonus pay for amazing work on #OSS 00010001116 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001117 +705bonus pay for amazing work on #OSS 00010001117 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001118 +705bonus pay for amazing work on #OSS 00010001118 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001119 +705bonus pay for amazing work on #OSS 00010001119 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001120 +705bonus pay for amazing work on #OSS 00010001120 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001121 +705bonus pay for amazing work on #OSS 00010001121 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001122 +705bonus pay for amazing work on #OSS 00010001122 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001123 +705bonus pay for amazing work on #OSS 00010001123 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001124 +705bonus pay for amazing work on #OSS 00010001124 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001125 +705bonus pay for amazing work on #OSS 00010001125 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001126 +705bonus pay for amazing work on #OSS 00010001126 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001127 +705bonus pay for amazing work on #OSS 00010001127 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001128 +705bonus pay for amazing work on #OSS 00010001128 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001129 +705bonus pay for amazing work on #OSS 00010001129 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001130 +705bonus pay for amazing work on #OSS 00010001130 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001131 +705bonus pay for amazing work on #OSS 00010001131 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001132 +705bonus pay for amazing work on #OSS 00010001132 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001133 +705bonus pay for amazing work on #OSS 00010001133 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001134 +705bonus pay for amazing work on #OSS 00010001134 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001135 +705bonus pay for amazing work on #OSS 00010001135 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001136 +705bonus pay for amazing work on #OSS 00010001136 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001137 +705bonus pay for amazing work on #OSS 00010001137 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001138 +705bonus pay for amazing work on #OSS 00010001138 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001139 +705bonus pay for amazing work on #OSS 00010001139 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001140 +705bonus pay for amazing work on #OSS 00010001140 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001141 +705bonus pay for amazing work on #OSS 00010001141 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001142 +705bonus pay for amazing work on #OSS 00010001142 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001143 +705bonus pay for amazing work on #OSS 00010001143 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001144 +705bonus pay for amazing work on #OSS 00010001144 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001145 +705bonus pay for amazing work on #OSS 00010001145 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001146 +705bonus pay for amazing work on #OSS 00010001146 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001147 +705bonus pay for amazing work on #OSS 00010001147 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001148 +705bonus pay for amazing work on #OSS 00010001148 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001149 +705bonus pay for amazing work on #OSS 00010001149 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001150 +705bonus pay for amazing work on #OSS 00010001150 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001151 +705bonus pay for amazing work on #OSS 00010001151 +62223138010481967038518 0000100000#LyWCFfgwoNhPk#Emily Davis 1121042880001152 +705bonus pay for amazing work on #OSS 00010001152 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001153 +705bonus pay for amazing work on #OSS 00010001153 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001154 +705bonus pay for amazing work on #OSS 00010001154 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001155 +705bonus pay for amazing work on #OSS 00010001155 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001156 +705bonus pay for amazing work on #OSS 00010001156 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001157 +705bonus pay for amazing work on #OSS 00010001157 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001158 +705bonus pay for amazing work on #OSS 00010001158 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001159 +705bonus pay for amazing work on #OSS 00010001159 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001160 +705bonus pay for amazing work on #OSS 00010001160 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001161 +705bonus pay for amazing work on #OSS 00010001161 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001162 +705bonus pay for amazing work on #OSS 00010001162 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001163 +705bonus pay for amazing work on #OSS 00010001163 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001164 +705bonus pay for amazing work on #OSS 00010001164 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001165 +705bonus pay for amazing work on #OSS 00010001165 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001166 +705bonus pay for amazing work on #OSS 00010001166 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001167 +705bonus pay for amazing work on #OSS 00010001167 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001168 +705bonus pay for amazing work on #OSS 00010001168 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001169 +705bonus pay for amazing work on #OSS 00010001169 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001170 +705bonus pay for amazing work on #OSS 00010001170 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001171 +705bonus pay for amazing work on #OSS 00010001171 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001172 +705bonus pay for amazing work on #OSS 00010001172 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001173 +705bonus pay for amazing work on #OSS 00010001173 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001174 +705bonus pay for amazing work on #OSS 00010001174 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001175 +705bonus pay for amazing work on #OSS 00010001175 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001176 +705bonus pay for amazing work on #OSS 00010001176 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001177 +705bonus pay for amazing work on #OSS 00010001177 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001178 +705bonus pay for amazing work on #OSS 00010001178 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001179 +705bonus pay for amazing work on #OSS 00010001179 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001180 +705bonus pay for amazing work on #OSS 00010001180 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001181 +705bonus pay for amazing work on #OSS 00010001181 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001182 +705bonus pay for amazing work on #OSS 00010001182 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001183 +705bonus pay for amazing work on #OSS 00010001183 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001184 +705bonus pay for amazing work on #OSS 00010001184 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001185 +705bonus pay for amazing work on #OSS 00010001185 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001186 +705bonus pay for amazing work on #OSS 00010001186 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001187 +705bonus pay for amazing work on #OSS 00010001187 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001188 +705bonus pay for amazing work on #OSS 00010001188 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001189 +705bonus pay for amazing work on #OSS 00010001189 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001190 +705bonus pay for amazing work on #OSS 00010001190 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001191 +705bonus pay for amazing work on #OSS 00010001191 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001192 +705bonus pay for amazing work on #OSS 00010001192 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001193 +705bonus pay for amazing work on #OSS 00010001193 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001194 +705bonus pay for amazing work on #OSS 00010001194 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001195 +705bonus pay for amazing work on #OSS 00010001195 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001196 +705bonus pay for amazing work on #OSS 00010001196 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001197 +705bonus pay for amazing work on #OSS 00010001197 +62223138010481967038518 0000100000#FnM5tBsNggfp5#Elizabeth Taylor 1121042880001198 +705bonus pay for amazing work on #OSS 00010001198 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001199 +705bonus pay for amazing work on #OSS 00010001199 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001200 +705bonus pay for amazing work on #OSS 00010001200 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001201 +705bonus pay for amazing work on #OSS 00010001201 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001202 +705bonus pay for amazing work on #OSS 00010001202 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001203 +705bonus pay for amazing work on #OSS 00010001203 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001204 +705bonus pay for amazing work on #OSS 00010001204 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001205 +705bonus pay for amazing work on #OSS 00010001205 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001206 +705bonus pay for amazing work on #OSS 00010001206 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001207 +705bonus pay for amazing work on #OSS 00010001207 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001208 +705bonus pay for amazing work on #OSS 00010001208 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001209 +705bonus pay for amazing work on #OSS 00010001209 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001210 +705bonus pay for amazing work on #OSS 00010001210 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001211 +705bonus pay for amazing work on #OSS 00010001211 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001212 +705bonus pay for amazing work on #OSS 00010001212 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001213 +705bonus pay for amazing work on #OSS 00010001213 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001214 +705bonus pay for amazing work on #OSS 00010001214 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001215 +705bonus pay for amazing work on #OSS 00010001215 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001216 +705bonus pay for amazing work on #OSS 00010001216 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001217 +705bonus pay for amazing work on #OSS 00010001217 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001218 +705bonus pay for amazing work on #OSS 00010001218 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001219 +705bonus pay for amazing work on #OSS 00010001219 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001220 +705bonus pay for amazing work on #OSS 00010001220 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001221 +705bonus pay for amazing work on #OSS 00010001221 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001222 +705bonus pay for amazing work on #OSS 00010001222 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001223 +705bonus pay for amazing work on #OSS 00010001223 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001224 +705bonus pay for amazing work on #OSS 00010001224 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001225 +705bonus pay for amazing work on #OSS 00010001225 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001226 +705bonus pay for amazing work on #OSS 00010001226 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001227 +705bonus pay for amazing work on #OSS 00010001227 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001228 +705bonus pay for amazing work on #OSS 00010001228 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001229 +705bonus pay for amazing work on #OSS 00010001229 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001230 +705bonus pay for amazing work on #OSS 00010001230 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001231 +705bonus pay for amazing work on #OSS 00010001231 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001232 +705bonus pay for amazing work on #OSS 00010001232 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001233 +705bonus pay for amazing work on #OSS 00010001233 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001234 +705bonus pay for amazing work on #OSS 00010001234 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001235 +705bonus pay for amazing work on #OSS 00010001235 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001236 +705bonus pay for amazing work on #OSS 00010001236 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001237 +705bonus pay for amazing work on #OSS 00010001237 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001238 +705bonus pay for amazing work on #OSS 00010001238 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001239 +705bonus pay for amazing work on #OSS 00010001239 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001240 +705bonus pay for amazing work on #OSS 00010001240 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001241 +705bonus pay for amazing work on #OSS 00010001241 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001242 +705bonus pay for amazing work on #OSS 00010001242 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001243 +705bonus pay for amazing work on #OSS 00010001243 +62223138010481967038518 0000100000#ptsoeILPGIxTL#Sofia Garcia 1121042880001244 +705bonus pay for amazing work on #OSS 00010001244 +62223138010481967038518 0000100000#z4XwTkKCrOeul#Sofia Williams 1121042880001245 +705bonus pay for amazing work on #OSS 00010001245 +62223138010481967038518 0000100000#z4XwTkKCrOeul#Ethan Williams 1121042880001246 +705bonus pay for amazing work on #OSS 00010001246 +62223138010481967038518 0000100000#z4XwTkKCrOeul#Ethan Williams 1121042880001247 +705bonus pay for amazing work on #OSS 00010001247 +62223138010481967038518 0000100000#z4XwTkKCrOeul#Ethan Williams 1121042880001248 +705bonus pay for amazing work on #OSS 00010001248 +62223138010481967038518 0000100000#z4XwTkKCrOeul#Ethan Williams 1121042880001249 +705bonus pay for amazing work on #OSS 00010001249 +62223138010481967038518 0000100000#z4XwTkKCrOeul#Ethan Williams 1121042880001250 +705bonus pay for amazing work on #OSS 00010001250 +82000025008922512500000000000000000125000000121042882 121042880000001 +5200Wells Fargo 121042882 PPDTrans. Des 180511 0121042880000002 +62223138010481967038518 0000100000#3vKiBFiGxVHwO#Ethan Williams 1121042880000001 +705bonus pay for amazing work on #OSS 00010000001 +62223138010481967038518 0000100000#3vKiBFiGxVHwO#Ethan Williams 1121042880000002 +705bonus pay for amazing work on #OSS 00010000002 +62223138010481967038518 0000100000#3vKiBFiGxVHwO#Ethan Williams 1121042880000003 +705bonus pay for amazing work on #OSS 00010000003 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000004 +705bonus pay for amazing work on #OSS 00010000004 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000005 +705bonus pay for amazing work on #OSS 00010000005 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000006 +705bonus pay for amazing work on #OSS 00010000006 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000007 +705bonus pay for amazing work on #OSS 00010000007 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000008 +705bonus pay for amazing work on #OSS 00010000008 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000009 +705bonus pay for amazing work on #OSS 00010000009 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000010 +705bonus pay for amazing work on #OSS 00010000010 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000011 +705bonus pay for amazing work on #OSS 00010000011 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000012 +705bonus pay for amazing work on #OSS 00010000012 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000013 +705bonus pay for amazing work on #OSS 00010000013 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000014 +705bonus pay for amazing work on #OSS 00010000014 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000015 +705bonus pay for amazing work on #OSS 00010000015 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000016 +705bonus pay for amazing work on #OSS 00010000016 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000017 +705bonus pay for amazing work on #OSS 00010000017 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000018 +705bonus pay for amazing work on #OSS 00010000018 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000019 +705bonus pay for amazing work on #OSS 00010000019 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000020 +705bonus pay for amazing work on #OSS 00010000020 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000021 +705bonus pay for amazing work on #OSS 00010000021 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000022 +705bonus pay for amazing work on #OSS 00010000022 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000023 +705bonus pay for amazing work on #OSS 00010000023 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000024 +705bonus pay for amazing work on #OSS 00010000024 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000025 +705bonus pay for amazing work on #OSS 00010000025 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000026 +705bonus pay for amazing work on #OSS 00010000026 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000027 +705bonus pay for amazing work on #OSS 00010000027 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000028 +705bonus pay for amazing work on #OSS 00010000028 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000029 +705bonus pay for amazing work on #OSS 00010000029 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000030 +705bonus pay for amazing work on #OSS 00010000030 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000031 +705bonus pay for amazing work on #OSS 00010000031 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000032 +705bonus pay for amazing work on #OSS 00010000032 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000033 +705bonus pay for amazing work on #OSS 00010000033 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000034 +705bonus pay for amazing work on #OSS 00010000034 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000035 +705bonus pay for amazing work on #OSS 00010000035 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000036 +705bonus pay for amazing work on #OSS 00010000036 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000037 +705bonus pay for amazing work on #OSS 00010000037 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000038 +705bonus pay for amazing work on #OSS 00010000038 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000039 +705bonus pay for amazing work on #OSS 00010000039 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000040 +705bonus pay for amazing work on #OSS 00010000040 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000041 +705bonus pay for amazing work on #OSS 00010000041 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000042 +705bonus pay for amazing work on #OSS 00010000042 +62223138010481967038518 0000100000#FgpSck5eoiHaN#William Brown 1121042880000043 +705bonus pay for amazing work on #OSS 00010000043 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000044 +705bonus pay for amazing work on #OSS 00010000044 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000045 +705bonus pay for amazing work on #OSS 00010000045 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000046 +705bonus pay for amazing work on #OSS 00010000046 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000047 +705bonus pay for amazing work on #OSS 00010000047 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000048 +705bonus pay for amazing work on #OSS 00010000048 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000049 +705bonus pay for amazing work on #OSS 00010000049 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000050 +705bonus pay for amazing work on #OSS 00010000050 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000051 +705bonus pay for amazing work on #OSS 00010000051 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000052 +705bonus pay for amazing work on #OSS 00010000052 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000053 +705bonus pay for amazing work on #OSS 00010000053 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000054 +705bonus pay for amazing work on #OSS 00010000054 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000055 +705bonus pay for amazing work on #OSS 00010000055 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000056 +705bonus pay for amazing work on #OSS 00010000056 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000057 +705bonus pay for amazing work on #OSS 00010000057 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000058 +705bonus pay for amazing work on #OSS 00010000058 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000059 +705bonus pay for amazing work on #OSS 00010000059 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000060 +705bonus pay for amazing work on #OSS 00010000060 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000061 +705bonus pay for amazing work on #OSS 00010000061 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000062 +705bonus pay for amazing work on #OSS 00010000062 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000063 +705bonus pay for amazing work on #OSS 00010000063 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000064 +705bonus pay for amazing work on #OSS 00010000064 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000065 +705bonus pay for amazing work on #OSS 00010000065 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000066 +705bonus pay for amazing work on #OSS 00010000066 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000067 +705bonus pay for amazing work on #OSS 00010000067 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000068 +705bonus pay for amazing work on #OSS 00010000068 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000069 +705bonus pay for amazing work on #OSS 00010000069 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000070 +705bonus pay for amazing work on #OSS 00010000070 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000071 +705bonus pay for amazing work on #OSS 00010000071 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000072 +705bonus pay for amazing work on #OSS 00010000072 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000073 +705bonus pay for amazing work on #OSS 00010000073 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000074 +705bonus pay for amazing work on #OSS 00010000074 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000075 +705bonus pay for amazing work on #OSS 00010000075 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000076 +705bonus pay for amazing work on #OSS 00010000076 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000077 +705bonus pay for amazing work on #OSS 00010000077 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000078 +705bonus pay for amazing work on #OSS 00010000078 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000079 +705bonus pay for amazing work on #OSS 00010000079 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000080 +705bonus pay for amazing work on #OSS 00010000080 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000081 +705bonus pay for amazing work on #OSS 00010000081 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000082 +705bonus pay for amazing work on #OSS 00010000082 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000083 +705bonus pay for amazing work on #OSS 00010000083 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000084 +705bonus pay for amazing work on #OSS 00010000084 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000085 +705bonus pay for amazing work on #OSS 00010000085 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000086 +705bonus pay for amazing work on #OSS 00010000086 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000087 +705bonus pay for amazing work on #OSS 00010000087 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000088 +705bonus pay for amazing work on #OSS 00010000088 +62223138010481967038518 0000100000#KHLGE7vFlyb6K#William Brown 1121042880000089 +705bonus pay for amazing work on #OSS 00010000089 +62223138010481967038518 0000100000#9Yz8CRYozKku9#William Martin 1121042880000090 +705bonus pay for amazing work on #OSS 00010000090 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000091 +705bonus pay for amazing work on #OSS 00010000091 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000092 +705bonus pay for amazing work on #OSS 00010000092 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000093 +705bonus pay for amazing work on #OSS 00010000093 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000094 +705bonus pay for amazing work on #OSS 00010000094 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000095 +705bonus pay for amazing work on #OSS 00010000095 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000096 +705bonus pay for amazing work on #OSS 00010000096 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000097 +705bonus pay for amazing work on #OSS 00010000097 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000098 +705bonus pay for amazing work on #OSS 00010000098 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000099 +705bonus pay for amazing work on #OSS 00010000099 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000100 +705bonus pay for amazing work on #OSS 00010000100 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000101 +705bonus pay for amazing work on #OSS 00010000101 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000102 +705bonus pay for amazing work on #OSS 00010000102 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000103 +705bonus pay for amazing work on #OSS 00010000103 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000104 +705bonus pay for amazing work on #OSS 00010000104 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000105 +705bonus pay for amazing work on #OSS 00010000105 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000106 +705bonus pay for amazing work on #OSS 00010000106 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000107 +705bonus pay for amazing work on #OSS 00010000107 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000108 +705bonus pay for amazing work on #OSS 00010000108 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000109 +705bonus pay for amazing work on #OSS 00010000109 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000110 +705bonus pay for amazing work on #OSS 00010000110 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000111 +705bonus pay for amazing work on #OSS 00010000111 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000112 +705bonus pay for amazing work on #OSS 00010000112 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000113 +705bonus pay for amazing work on #OSS 00010000113 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000114 +705bonus pay for amazing work on #OSS 00010000114 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000115 +705bonus pay for amazing work on #OSS 00010000115 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000116 +705bonus pay for amazing work on #OSS 00010000116 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000117 +705bonus pay for amazing work on #OSS 00010000117 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000118 +705bonus pay for amazing work on #OSS 00010000118 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000119 +705bonus pay for amazing work on #OSS 00010000119 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000120 +705bonus pay for amazing work on #OSS 00010000120 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000121 +705bonus pay for amazing work on #OSS 00010000121 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000122 +705bonus pay for amazing work on #OSS 00010000122 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000123 +705bonus pay for amazing work on #OSS 00010000123 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000124 +705bonus pay for amazing work on #OSS 00010000124 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000125 +705bonus pay for amazing work on #OSS 00010000125 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000126 +705bonus pay for amazing work on #OSS 00010000126 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000127 +705bonus pay for amazing work on #OSS 00010000127 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000128 +705bonus pay for amazing work on #OSS 00010000128 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000129 +705bonus pay for amazing work on #OSS 00010000129 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000130 +705bonus pay for amazing work on #OSS 00010000130 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000131 +705bonus pay for amazing work on #OSS 00010000131 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000132 +705bonus pay for amazing work on #OSS 00010000132 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000133 +705bonus pay for amazing work on #OSS 00010000133 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000134 +705bonus pay for amazing work on #OSS 00010000134 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000135 +705bonus pay for amazing work on #OSS 00010000135 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000136 +705bonus pay for amazing work on #OSS 00010000136 +62223138010481967038518 0000100000#9Yz8CRYozKku9#Lily Martin 1121042880000137 +705bonus pay for amazing work on #OSS 00010000137 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000138 +705bonus pay for amazing work on #OSS 00010000138 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000139 +705bonus pay for amazing work on #OSS 00010000139 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000140 +705bonus pay for amazing work on #OSS 00010000140 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000141 +705bonus pay for amazing work on #OSS 00010000141 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000142 +705bonus pay for amazing work on #OSS 00010000142 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000143 +705bonus pay for amazing work on #OSS 00010000143 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000144 +705bonus pay for amazing work on #OSS 00010000144 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000145 +705bonus pay for amazing work on #OSS 00010000145 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000146 +705bonus pay for amazing work on #OSS 00010000146 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000147 +705bonus pay for amazing work on #OSS 00010000147 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000148 +705bonus pay for amazing work on #OSS 00010000148 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000149 +705bonus pay for amazing work on #OSS 00010000149 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000150 +705bonus pay for amazing work on #OSS 00010000150 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000151 +705bonus pay for amazing work on #OSS 00010000151 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000152 +705bonus pay for amazing work on #OSS 00010000152 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000153 +705bonus pay for amazing work on #OSS 00010000153 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000154 +705bonus pay for amazing work on #OSS 00010000154 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000155 +705bonus pay for amazing work on #OSS 00010000155 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000156 +705bonus pay for amazing work on #OSS 00010000156 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000157 +705bonus pay for amazing work on #OSS 00010000157 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000158 +705bonus pay for amazing work on #OSS 00010000158 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000159 +705bonus pay for amazing work on #OSS 00010000159 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000160 +705bonus pay for amazing work on #OSS 00010000160 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000161 +705bonus pay for amazing work on #OSS 00010000161 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000162 +705bonus pay for amazing work on #OSS 00010000162 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000163 +705bonus pay for amazing work on #OSS 00010000163 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000164 +705bonus pay for amazing work on #OSS 00010000164 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000165 +705bonus pay for amazing work on #OSS 00010000165 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000166 +705bonus pay for amazing work on #OSS 00010000166 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000167 +705bonus pay for amazing work on #OSS 00010000167 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000168 +705bonus pay for amazing work on #OSS 00010000168 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000169 +705bonus pay for amazing work on #OSS 00010000169 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000170 +705bonus pay for amazing work on #OSS 00010000170 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000171 +705bonus pay for amazing work on #OSS 00010000171 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000172 +705bonus pay for amazing work on #OSS 00010000172 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000173 +705bonus pay for amazing work on #OSS 00010000173 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000174 +705bonus pay for amazing work on #OSS 00010000174 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000175 +705bonus pay for amazing work on #OSS 00010000175 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000176 +705bonus pay for amazing work on #OSS 00010000176 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000177 +705bonus pay for amazing work on #OSS 00010000177 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000178 +705bonus pay for amazing work on #OSS 00010000178 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000179 +705bonus pay for amazing work on #OSS 00010000179 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000180 +705bonus pay for amazing work on #OSS 00010000180 +62223138010481967038518 0000100000#kNWslfnAm0HAt#Zoey Robinson 1121042880000181 +705bonus pay for amazing work on #OSS 00010000181 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Zoey Johnson 1121042880000182 +705bonus pay for amazing work on #OSS 00010000182 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000183 +705bonus pay for amazing work on #OSS 00010000183 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000184 +705bonus pay for amazing work on #OSS 00010000184 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000185 +705bonus pay for amazing work on #OSS 00010000185 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000186 +705bonus pay for amazing work on #OSS 00010000186 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000187 +705bonus pay for amazing work on #OSS 00010000187 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000188 +705bonus pay for amazing work on #OSS 00010000188 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000189 +705bonus pay for amazing work on #OSS 00010000189 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000190 +705bonus pay for amazing work on #OSS 00010000190 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000191 +705bonus pay for amazing work on #OSS 00010000191 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000192 +705bonus pay for amazing work on #OSS 00010000192 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000193 +705bonus pay for amazing work on #OSS 00010000193 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000194 +705bonus pay for amazing work on #OSS 00010000194 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000195 +705bonus pay for amazing work on #OSS 00010000195 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000196 +705bonus pay for amazing work on #OSS 00010000196 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000197 +705bonus pay for amazing work on #OSS 00010000197 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000198 +705bonus pay for amazing work on #OSS 00010000198 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000199 +705bonus pay for amazing work on #OSS 00010000199 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000200 +705bonus pay for amazing work on #OSS 00010000200 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000201 +705bonus pay for amazing work on #OSS 00010000201 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000202 +705bonus pay for amazing work on #OSS 00010000202 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000203 +705bonus pay for amazing work on #OSS 00010000203 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000204 +705bonus pay for amazing work on #OSS 00010000204 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000205 +705bonus pay for amazing work on #OSS 00010000205 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000206 +705bonus pay for amazing work on #OSS 00010000206 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000207 +705bonus pay for amazing work on #OSS 00010000207 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000208 +705bonus pay for amazing work on #OSS 00010000208 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000209 +705bonus pay for amazing work on #OSS 00010000209 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000210 +705bonus pay for amazing work on #OSS 00010000210 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000211 +705bonus pay for amazing work on #OSS 00010000211 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000212 +705bonus pay for amazing work on #OSS 00010000212 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000213 +705bonus pay for amazing work on #OSS 00010000213 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000214 +705bonus pay for amazing work on #OSS 00010000214 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000215 +705bonus pay for amazing work on #OSS 00010000215 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000216 +705bonus pay for amazing work on #OSS 00010000216 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000217 +705bonus pay for amazing work on #OSS 00010000217 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000218 +705bonus pay for amazing work on #OSS 00010000218 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000219 +705bonus pay for amazing work on #OSS 00010000219 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000220 +705bonus pay for amazing work on #OSS 00010000220 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000221 +705bonus pay for amazing work on #OSS 00010000221 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000222 +705bonus pay for amazing work on #OSS 00010000222 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000223 +705bonus pay for amazing work on #OSS 00010000223 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000224 +705bonus pay for amazing work on #OSS 00010000224 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000225 +705bonus pay for amazing work on #OSS 00010000225 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000226 +705bonus pay for amazing work on #OSS 00010000226 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000227 +705bonus pay for amazing work on #OSS 00010000227 +62223138010481967038518 0000100000#HDjHFVH5XnIFJ#Emma Johnson 1121042880000228 +705bonus pay for amazing work on #OSS 00010000228 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Isabella Williams 1121042880000229 +705bonus pay for amazing work on #OSS 00010000229 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000230 +705bonus pay for amazing work on #OSS 00010000230 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000231 +705bonus pay for amazing work on #OSS 00010000231 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000232 +705bonus pay for amazing work on #OSS 00010000232 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000233 +705bonus pay for amazing work on #OSS 00010000233 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000234 +705bonus pay for amazing work on #OSS 00010000234 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000235 +705bonus pay for amazing work on #OSS 00010000235 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000236 +705bonus pay for amazing work on #OSS 00010000236 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000237 +705bonus pay for amazing work on #OSS 00010000237 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000238 +705bonus pay for amazing work on #OSS 00010000238 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000239 +705bonus pay for amazing work on #OSS 00010000239 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000240 +705bonus pay for amazing work on #OSS 00010000240 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000241 +705bonus pay for amazing work on #OSS 00010000241 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000242 +705bonus pay for amazing work on #OSS 00010000242 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000243 +705bonus pay for amazing work on #OSS 00010000243 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000244 +705bonus pay for amazing work on #OSS 00010000244 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000245 +705bonus pay for amazing work on #OSS 00010000245 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000246 +705bonus pay for amazing work on #OSS 00010000246 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000247 +705bonus pay for amazing work on #OSS 00010000247 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000248 +705bonus pay for amazing work on #OSS 00010000248 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000249 +705bonus pay for amazing work on #OSS 00010000249 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000250 +705bonus pay for amazing work on #OSS 00010000250 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000251 +705bonus pay for amazing work on #OSS 00010000251 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000252 +705bonus pay for amazing work on #OSS 00010000252 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000253 +705bonus pay for amazing work on #OSS 00010000253 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000254 +705bonus pay for amazing work on #OSS 00010000254 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000255 +705bonus pay for amazing work on #OSS 00010000255 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000256 +705bonus pay for amazing work on #OSS 00010000256 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000257 +705bonus pay for amazing work on #OSS 00010000257 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000258 +705bonus pay for amazing work on #OSS 00010000258 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000259 +705bonus pay for amazing work on #OSS 00010000259 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000260 +705bonus pay for amazing work on #OSS 00010000260 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000261 +705bonus pay for amazing work on #OSS 00010000261 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000262 +705bonus pay for amazing work on #OSS 00010000262 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000263 +705bonus pay for amazing work on #OSS 00010000263 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000264 +705bonus pay for amazing work on #OSS 00010000264 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000265 +705bonus pay for amazing work on #OSS 00010000265 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000266 +705bonus pay for amazing work on #OSS 00010000266 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000267 +705bonus pay for amazing work on #OSS 00010000267 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000268 +705bonus pay for amazing work on #OSS 00010000268 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000269 +705bonus pay for amazing work on #OSS 00010000269 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000270 +705bonus pay for amazing work on #OSS 00010000270 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000271 +705bonus pay for amazing work on #OSS 00010000271 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000272 +705bonus pay for amazing work on #OSS 00010000272 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000273 +705bonus pay for amazing work on #OSS 00010000273 +62223138010481967038518 0000100000#Y1tPhAyZcU04W#Ethan Williams 1121042880000274 +705bonus pay for amazing work on #OSS 00010000274 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Ethan Thompson 1121042880000275 +705bonus pay for amazing work on #OSS 00010000275 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000276 +705bonus pay for amazing work on #OSS 00010000276 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000277 +705bonus pay for amazing work on #OSS 00010000277 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000278 +705bonus pay for amazing work on #OSS 00010000278 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000279 +705bonus pay for amazing work on #OSS 00010000279 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000280 +705bonus pay for amazing work on #OSS 00010000280 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000281 +705bonus pay for amazing work on #OSS 00010000281 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000282 +705bonus pay for amazing work on #OSS 00010000282 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000283 +705bonus pay for amazing work on #OSS 00010000283 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000284 +705bonus pay for amazing work on #OSS 00010000284 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000285 +705bonus pay for amazing work on #OSS 00010000285 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000286 +705bonus pay for amazing work on #OSS 00010000286 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000287 +705bonus pay for amazing work on #OSS 00010000287 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000288 +705bonus pay for amazing work on #OSS 00010000288 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000289 +705bonus pay for amazing work on #OSS 00010000289 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000290 +705bonus pay for amazing work on #OSS 00010000290 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000291 +705bonus pay for amazing work on #OSS 00010000291 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000292 +705bonus pay for amazing work on #OSS 00010000292 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000293 +705bonus pay for amazing work on #OSS 00010000293 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000294 +705bonus pay for amazing work on #OSS 00010000294 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000295 +705bonus pay for amazing work on #OSS 00010000295 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000296 +705bonus pay for amazing work on #OSS 00010000296 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000297 +705bonus pay for amazing work on #OSS 00010000297 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000298 +705bonus pay for amazing work on #OSS 00010000298 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000299 +705bonus pay for amazing work on #OSS 00010000299 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000300 +705bonus pay for amazing work on #OSS 00010000300 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000301 +705bonus pay for amazing work on #OSS 00010000301 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000302 +705bonus pay for amazing work on #OSS 00010000302 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000303 +705bonus pay for amazing work on #OSS 00010000303 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000304 +705bonus pay for amazing work on #OSS 00010000304 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000305 +705bonus pay for amazing work on #OSS 00010000305 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000306 +705bonus pay for amazing work on #OSS 00010000306 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000307 +705bonus pay for amazing work on #OSS 00010000307 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000308 +705bonus pay for amazing work on #OSS 00010000308 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000309 +705bonus pay for amazing work on #OSS 00010000309 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000310 +705bonus pay for amazing work on #OSS 00010000310 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000311 +705bonus pay for amazing work on #OSS 00010000311 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000312 +705bonus pay for amazing work on #OSS 00010000312 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000313 +705bonus pay for amazing work on #OSS 00010000313 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000314 +705bonus pay for amazing work on #OSS 00010000314 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000315 +705bonus pay for amazing work on #OSS 00010000315 +62223138010481967038518 0000100000#7TU8vZeSUYhUO#Joshua Thompson 1121042880000316 +705bonus pay for amazing work on #OSS 00010000316 +62223138010481967038518 0000100000#6GCinaos17WMn#Joshua Martinez 1121042880000317 +705bonus pay for amazing work on #OSS 00010000317 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000318 +705bonus pay for amazing work on #OSS 00010000318 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000319 +705bonus pay for amazing work on #OSS 00010000319 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000320 +705bonus pay for amazing work on #OSS 00010000320 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000321 +705bonus pay for amazing work on #OSS 00010000321 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000322 +705bonus pay for amazing work on #OSS 00010000322 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000323 +705bonus pay for amazing work on #OSS 00010000323 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000324 +705bonus pay for amazing work on #OSS 00010000324 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000325 +705bonus pay for amazing work on #OSS 00010000325 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000326 +705bonus pay for amazing work on #OSS 00010000326 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000327 +705bonus pay for amazing work on #OSS 00010000327 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000328 +705bonus pay for amazing work on #OSS 00010000328 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000329 +705bonus pay for amazing work on #OSS 00010000329 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000330 +705bonus pay for amazing work on #OSS 00010000330 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000331 +705bonus pay for amazing work on #OSS 00010000331 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000332 +705bonus pay for amazing work on #OSS 00010000332 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000333 +705bonus pay for amazing work on #OSS 00010000333 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000334 +705bonus pay for amazing work on #OSS 00010000334 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000335 +705bonus pay for amazing work on #OSS 00010000335 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000336 +705bonus pay for amazing work on #OSS 00010000336 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000337 +705bonus pay for amazing work on #OSS 00010000337 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000338 +705bonus pay for amazing work on #OSS 00010000338 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000339 +705bonus pay for amazing work on #OSS 00010000339 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000340 +705bonus pay for amazing work on #OSS 00010000340 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000341 +705bonus pay for amazing work on #OSS 00010000341 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000342 +705bonus pay for amazing work on #OSS 00010000342 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000343 +705bonus pay for amazing work on #OSS 00010000343 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000344 +705bonus pay for amazing work on #OSS 00010000344 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000345 +705bonus pay for amazing work on #OSS 00010000345 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000346 +705bonus pay for amazing work on #OSS 00010000346 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000347 +705bonus pay for amazing work on #OSS 00010000347 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000348 +705bonus pay for amazing work on #OSS 00010000348 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000349 +705bonus pay for amazing work on #OSS 00010000349 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000350 +705bonus pay for amazing work on #OSS 00010000350 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000351 +705bonus pay for amazing work on #OSS 00010000351 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000352 +705bonus pay for amazing work on #OSS 00010000352 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000353 +705bonus pay for amazing work on #OSS 00010000353 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000354 +705bonus pay for amazing work on #OSS 00010000354 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000355 +705bonus pay for amazing work on #OSS 00010000355 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000356 +705bonus pay for amazing work on #OSS 00010000356 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000357 +705bonus pay for amazing work on #OSS 00010000357 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000358 +705bonus pay for amazing work on #OSS 00010000358 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000359 +705bonus pay for amazing work on #OSS 00010000359 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000360 +705bonus pay for amazing work on #OSS 00010000360 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000361 +705bonus pay for amazing work on #OSS 00010000361 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000362 +705bonus pay for amazing work on #OSS 00010000362 +62223138010481967038518 0000100000#6GCinaos17WMn#David Martinez 1121042880000363 +705bonus pay for amazing work on #OSS 00010000363 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000364 +705bonus pay for amazing work on #OSS 00010000364 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000365 +705bonus pay for amazing work on #OSS 00010000365 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000366 +705bonus pay for amazing work on #OSS 00010000366 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000367 +705bonus pay for amazing work on #OSS 00010000367 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000368 +705bonus pay for amazing work on #OSS 00010000368 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000369 +705bonus pay for amazing work on #OSS 00010000369 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000370 +705bonus pay for amazing work on #OSS 00010000370 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000371 +705bonus pay for amazing work on #OSS 00010000371 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000372 +705bonus pay for amazing work on #OSS 00010000372 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000373 +705bonus pay for amazing work on #OSS 00010000373 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000374 +705bonus pay for amazing work on #OSS 00010000374 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000375 +705bonus pay for amazing work on #OSS 00010000375 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000376 +705bonus pay for amazing work on #OSS 00010000376 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000377 +705bonus pay for amazing work on #OSS 00010000377 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000378 +705bonus pay for amazing work on #OSS 00010000378 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000379 +705bonus pay for amazing work on #OSS 00010000379 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000380 +705bonus pay for amazing work on #OSS 00010000380 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000381 +705bonus pay for amazing work on #OSS 00010000381 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000382 +705bonus pay for amazing work on #OSS 00010000382 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000383 +705bonus pay for amazing work on #OSS 00010000383 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000384 +705bonus pay for amazing work on #OSS 00010000384 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000385 +705bonus pay for amazing work on #OSS 00010000385 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000386 +705bonus pay for amazing work on #OSS 00010000386 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000387 +705bonus pay for amazing work on #OSS 00010000387 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000388 +705bonus pay for amazing work on #OSS 00010000388 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000389 +705bonus pay for amazing work on #OSS 00010000389 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000390 +705bonus pay for amazing work on #OSS 00010000390 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000391 +705bonus pay for amazing work on #OSS 00010000391 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000392 +705bonus pay for amazing work on #OSS 00010000392 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000393 +705bonus pay for amazing work on #OSS 00010000393 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000394 +705bonus pay for amazing work on #OSS 00010000394 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000395 +705bonus pay for amazing work on #OSS 00010000395 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000396 +705bonus pay for amazing work on #OSS 00010000396 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000397 +705bonus pay for amazing work on #OSS 00010000397 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000398 +705bonus pay for amazing work on #OSS 00010000398 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000399 +705bonus pay for amazing work on #OSS 00010000399 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000400 +705bonus pay for amazing work on #OSS 00010000400 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000401 +705bonus pay for amazing work on #OSS 00010000401 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000402 +705bonus pay for amazing work on #OSS 00010000402 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000403 +705bonus pay for amazing work on #OSS 00010000403 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000404 +705bonus pay for amazing work on #OSS 00010000404 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000405 +705bonus pay for amazing work on #OSS 00010000405 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000406 +705bonus pay for amazing work on #OSS 00010000406 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000407 +705bonus pay for amazing work on #OSS 00010000407 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000408 +705bonus pay for amazing work on #OSS 00010000408 +62223138010481967038518 0000100000#aRl8w7fy4KslB#Lily Martin 1121042880000409 +705bonus pay for amazing work on #OSS 00010000409 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000410 +705bonus pay for amazing work on #OSS 00010000410 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000411 +705bonus pay for amazing work on #OSS 00010000411 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000412 +705bonus pay for amazing work on #OSS 00010000412 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000413 +705bonus pay for amazing work on #OSS 00010000413 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000414 +705bonus pay for amazing work on #OSS 00010000414 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000415 +705bonus pay for amazing work on #OSS 00010000415 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000416 +705bonus pay for amazing work on #OSS 00010000416 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000417 +705bonus pay for amazing work on #OSS 00010000417 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000418 +705bonus pay for amazing work on #OSS 00010000418 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000419 +705bonus pay for amazing work on #OSS 00010000419 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000420 +705bonus pay for amazing work on #OSS 00010000420 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000421 +705bonus pay for amazing work on #OSS 00010000421 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000422 +705bonus pay for amazing work on #OSS 00010000422 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000423 +705bonus pay for amazing work on #OSS 00010000423 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000424 +705bonus pay for amazing work on #OSS 00010000424 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000425 +705bonus pay for amazing work on #OSS 00010000425 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000426 +705bonus pay for amazing work on #OSS 00010000426 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000427 +705bonus pay for amazing work on #OSS 00010000427 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000428 +705bonus pay for amazing work on #OSS 00010000428 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000429 +705bonus pay for amazing work on #OSS 00010000429 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000430 +705bonus pay for amazing work on #OSS 00010000430 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000431 +705bonus pay for amazing work on #OSS 00010000431 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000432 +705bonus pay for amazing work on #OSS 00010000432 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000433 +705bonus pay for amazing work on #OSS 00010000433 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000434 +705bonus pay for amazing work on #OSS 00010000434 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000435 +705bonus pay for amazing work on #OSS 00010000435 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000436 +705bonus pay for amazing work on #OSS 00010000436 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000437 +705bonus pay for amazing work on #OSS 00010000437 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000438 +705bonus pay for amazing work on #OSS 00010000438 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000439 +705bonus pay for amazing work on #OSS 00010000439 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000440 +705bonus pay for amazing work on #OSS 00010000440 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000441 +705bonus pay for amazing work on #OSS 00010000441 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000442 +705bonus pay for amazing work on #OSS 00010000442 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000443 +705bonus pay for amazing work on #OSS 00010000443 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000444 +705bonus pay for amazing work on #OSS 00010000444 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000445 +705bonus pay for amazing work on #OSS 00010000445 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000446 +705bonus pay for amazing work on #OSS 00010000446 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000447 +705bonus pay for amazing work on #OSS 00010000447 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000448 +705bonus pay for amazing work on #OSS 00010000448 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000449 +705bonus pay for amazing work on #OSS 00010000449 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000450 +705bonus pay for amazing work on #OSS 00010000450 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000451 +705bonus pay for amazing work on #OSS 00010000451 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000452 +705bonus pay for amazing work on #OSS 00010000452 +62223138010481967038518 0000100000#COPODfOzBYFbH#Emily Davis 1121042880000453 +705bonus pay for amazing work on #OSS 00010000453 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Emily Moore 1121042880000454 +705bonus pay for amazing work on #OSS 00010000454 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000455 +705bonus pay for amazing work on #OSS 00010000455 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000456 +705bonus pay for amazing work on #OSS 00010000456 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000457 +705bonus pay for amazing work on #OSS 00010000457 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000458 +705bonus pay for amazing work on #OSS 00010000458 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000459 +705bonus pay for amazing work on #OSS 00010000459 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000460 +705bonus pay for amazing work on #OSS 00010000460 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000461 +705bonus pay for amazing work on #OSS 00010000461 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000462 +705bonus pay for amazing work on #OSS 00010000462 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000463 +705bonus pay for amazing work on #OSS 00010000463 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000464 +705bonus pay for amazing work on #OSS 00010000464 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000465 +705bonus pay for amazing work on #OSS 00010000465 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000466 +705bonus pay for amazing work on #OSS 00010000466 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000467 +705bonus pay for amazing work on #OSS 00010000467 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000468 +705bonus pay for amazing work on #OSS 00010000468 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000469 +705bonus pay for amazing work on #OSS 00010000469 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000470 +705bonus pay for amazing work on #OSS 00010000470 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000471 +705bonus pay for amazing work on #OSS 00010000471 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000472 +705bonus pay for amazing work on #OSS 00010000472 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000473 +705bonus pay for amazing work on #OSS 00010000473 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000474 +705bonus pay for amazing work on #OSS 00010000474 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000475 +705bonus pay for amazing work on #OSS 00010000475 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000476 +705bonus pay for amazing work on #OSS 00010000476 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000477 +705bonus pay for amazing work on #OSS 00010000477 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000478 +705bonus pay for amazing work on #OSS 00010000478 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000479 +705bonus pay for amazing work on #OSS 00010000479 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000480 +705bonus pay for amazing work on #OSS 00010000480 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000481 +705bonus pay for amazing work on #OSS 00010000481 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000482 +705bonus pay for amazing work on #OSS 00010000482 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000483 +705bonus pay for amazing work on #OSS 00010000483 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000484 +705bonus pay for amazing work on #OSS 00010000484 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000485 +705bonus pay for amazing work on #OSS 00010000485 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000486 +705bonus pay for amazing work on #OSS 00010000486 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000487 +705bonus pay for amazing work on #OSS 00010000487 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000488 +705bonus pay for amazing work on #OSS 00010000488 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000489 +705bonus pay for amazing work on #OSS 00010000489 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000490 +705bonus pay for amazing work on #OSS 00010000490 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000491 +705bonus pay for amazing work on #OSS 00010000491 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000492 +705bonus pay for amazing work on #OSS 00010000492 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000493 +705bonus pay for amazing work on #OSS 00010000493 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000494 +705bonus pay for amazing work on #OSS 00010000494 +62223138010481967038518 0000100000#rApdrY6YfuPYg#Alexander Moore 1121042880000495 +705bonus pay for amazing work on #OSS 00010000495 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000496 +705bonus pay for amazing work on #OSS 00010000496 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000497 +705bonus pay for amazing work on #OSS 00010000497 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000498 +705bonus pay for amazing work on #OSS 00010000498 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000499 +705bonus pay for amazing work on #OSS 00010000499 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000500 +705bonus pay for amazing work on #OSS 00010000500 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000501 +705bonus pay for amazing work on #OSS 00010000501 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000502 +705bonus pay for amazing work on #OSS 00010000502 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000503 +705bonus pay for amazing work on #OSS 00010000503 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000504 +705bonus pay for amazing work on #OSS 00010000504 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000505 +705bonus pay for amazing work on #OSS 00010000505 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000506 +705bonus pay for amazing work on #OSS 00010000506 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000507 +705bonus pay for amazing work on #OSS 00010000507 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000508 +705bonus pay for amazing work on #OSS 00010000508 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000509 +705bonus pay for amazing work on #OSS 00010000509 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000510 +705bonus pay for amazing work on #OSS 00010000510 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000511 +705bonus pay for amazing work on #OSS 00010000511 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000512 +705bonus pay for amazing work on #OSS 00010000512 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000513 +705bonus pay for amazing work on #OSS 00010000513 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000514 +705bonus pay for amazing work on #OSS 00010000514 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000515 +705bonus pay for amazing work on #OSS 00010000515 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000516 +705bonus pay for amazing work on #OSS 00010000516 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000517 +705bonus pay for amazing work on #OSS 00010000517 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000518 +705bonus pay for amazing work on #OSS 00010000518 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000519 +705bonus pay for amazing work on #OSS 00010000519 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000520 +705bonus pay for amazing work on #OSS 00010000520 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000521 +705bonus pay for amazing work on #OSS 00010000521 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000522 +705bonus pay for amazing work on #OSS 00010000522 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000523 +705bonus pay for amazing work on #OSS 00010000523 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000524 +705bonus pay for amazing work on #OSS 00010000524 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000525 +705bonus pay for amazing work on #OSS 00010000525 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000526 +705bonus pay for amazing work on #OSS 00010000526 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000527 +705bonus pay for amazing work on #OSS 00010000527 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000528 +705bonus pay for amazing work on #OSS 00010000528 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000529 +705bonus pay for amazing work on #OSS 00010000529 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000530 +705bonus pay for amazing work on #OSS 00010000530 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000531 +705bonus pay for amazing work on #OSS 00010000531 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000532 +705bonus pay for amazing work on #OSS 00010000532 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000533 +705bonus pay for amazing work on #OSS 00010000533 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000534 +705bonus pay for amazing work on #OSS 00010000534 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000535 +705bonus pay for amazing work on #OSS 00010000535 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000536 +705bonus pay for amazing work on #OSS 00010000536 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000537 +705bonus pay for amazing work on #OSS 00010000537 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000538 +705bonus pay for amazing work on #OSS 00010000538 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000539 +705bonus pay for amazing work on #OSS 00010000539 +62223138010481967038518 0000100000#1dCiTwfhUEFgu#Olivia Jones 1121042880000540 +705bonus pay for amazing work on #OSS 00010000540 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Olivia Thomas 1121042880000541 +705bonus pay for amazing work on #OSS 00010000541 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000542 +705bonus pay for amazing work on #OSS 00010000542 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000543 +705bonus pay for amazing work on #OSS 00010000543 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000544 +705bonus pay for amazing work on #OSS 00010000544 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000545 +705bonus pay for amazing work on #OSS 00010000545 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000546 +705bonus pay for amazing work on #OSS 00010000546 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000547 +705bonus pay for amazing work on #OSS 00010000547 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000548 +705bonus pay for amazing work on #OSS 00010000548 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000549 +705bonus pay for amazing work on #OSS 00010000549 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000550 +705bonus pay for amazing work on #OSS 00010000550 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000551 +705bonus pay for amazing work on #OSS 00010000551 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000552 +705bonus pay for amazing work on #OSS 00010000552 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000553 +705bonus pay for amazing work on #OSS 00010000553 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000554 +705bonus pay for amazing work on #OSS 00010000554 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000555 +705bonus pay for amazing work on #OSS 00010000555 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000556 +705bonus pay for amazing work on #OSS 00010000556 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000557 +705bonus pay for amazing work on #OSS 00010000557 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000558 +705bonus pay for amazing work on #OSS 00010000558 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000559 +705bonus pay for amazing work on #OSS 00010000559 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000560 +705bonus pay for amazing work on #OSS 00010000560 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000561 +705bonus pay for amazing work on #OSS 00010000561 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000562 +705bonus pay for amazing work on #OSS 00010000562 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000563 +705bonus pay for amazing work on #OSS 00010000563 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000564 +705bonus pay for amazing work on #OSS 00010000564 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000565 +705bonus pay for amazing work on #OSS 00010000565 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000566 +705bonus pay for amazing work on #OSS 00010000566 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000567 +705bonus pay for amazing work on #OSS 00010000567 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000568 +705bonus pay for amazing work on #OSS 00010000568 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000569 +705bonus pay for amazing work on #OSS 00010000569 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000570 +705bonus pay for amazing work on #OSS 00010000570 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000571 +705bonus pay for amazing work on #OSS 00010000571 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000572 +705bonus pay for amazing work on #OSS 00010000572 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000573 +705bonus pay for amazing work on #OSS 00010000573 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000574 +705bonus pay for amazing work on #OSS 00010000574 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000575 +705bonus pay for amazing work on #OSS 00010000575 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000576 +705bonus pay for amazing work on #OSS 00010000576 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000577 +705bonus pay for amazing work on #OSS 00010000577 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000578 +705bonus pay for amazing work on #OSS 00010000578 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000579 +705bonus pay for amazing work on #OSS 00010000579 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000580 +705bonus pay for amazing work on #OSS 00010000580 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000581 +705bonus pay for amazing work on #OSS 00010000581 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000582 +705bonus pay for amazing work on #OSS 00010000582 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000583 +705bonus pay for amazing work on #OSS 00010000583 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000584 +705bonus pay for amazing work on #OSS 00010000584 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000585 +705bonus pay for amazing work on #OSS 00010000585 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000586 +705bonus pay for amazing work on #OSS 00010000586 +62223138010481967038518 0000100000#A6agcpDEE2SxJ#Ella Thomas 1121042880000587 +705bonus pay for amazing work on #OSS 00010000587 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000588 +705bonus pay for amazing work on #OSS 00010000588 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000589 +705bonus pay for amazing work on #OSS 00010000589 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000590 +705bonus pay for amazing work on #OSS 00010000590 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000591 +705bonus pay for amazing work on #OSS 00010000591 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000592 +705bonus pay for amazing work on #OSS 00010000592 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000593 +705bonus pay for amazing work on #OSS 00010000593 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000594 +705bonus pay for amazing work on #OSS 00010000594 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000595 +705bonus pay for amazing work on #OSS 00010000595 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000596 +705bonus pay for amazing work on #OSS 00010000596 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000597 +705bonus pay for amazing work on #OSS 00010000597 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000598 +705bonus pay for amazing work on #OSS 00010000598 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000599 +705bonus pay for amazing work on #OSS 00010000599 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000600 +705bonus pay for amazing work on #OSS 00010000600 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000601 +705bonus pay for amazing work on #OSS 00010000601 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000602 +705bonus pay for amazing work on #OSS 00010000602 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000603 +705bonus pay for amazing work on #OSS 00010000603 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000604 +705bonus pay for amazing work on #OSS 00010000604 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000605 +705bonus pay for amazing work on #OSS 00010000605 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000606 +705bonus pay for amazing work on #OSS 00010000606 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000607 +705bonus pay for amazing work on #OSS 00010000607 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000608 +705bonus pay for amazing work on #OSS 00010000608 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000609 +705bonus pay for amazing work on #OSS 00010000609 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000610 +705bonus pay for amazing work on #OSS 00010000610 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000611 +705bonus pay for amazing work on #OSS 00010000611 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000612 +705bonus pay for amazing work on #OSS 00010000612 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000613 +705bonus pay for amazing work on #OSS 00010000613 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000614 +705bonus pay for amazing work on #OSS 00010000614 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000615 +705bonus pay for amazing work on #OSS 00010000615 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000616 +705bonus pay for amazing work on #OSS 00010000616 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000617 +705bonus pay for amazing work on #OSS 00010000617 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000618 +705bonus pay for amazing work on #OSS 00010000618 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000619 +705bonus pay for amazing work on #OSS 00010000619 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000620 +705bonus pay for amazing work on #OSS 00010000620 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000621 +705bonus pay for amazing work on #OSS 00010000621 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000622 +705bonus pay for amazing work on #OSS 00010000622 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000623 +705bonus pay for amazing work on #OSS 00010000623 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000624 +705bonus pay for amazing work on #OSS 00010000624 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000625 +705bonus pay for amazing work on #OSS 00010000625 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000626 +705bonus pay for amazing work on #OSS 00010000626 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000627 +705bonus pay for amazing work on #OSS 00010000627 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000628 +705bonus pay for amazing work on #OSS 00010000628 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000629 +705bonus pay for amazing work on #OSS 00010000629 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000630 +705bonus pay for amazing work on #OSS 00010000630 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000631 +705bonus pay for amazing work on #OSS 00010000631 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000632 +705bonus pay for amazing work on #OSS 00010000632 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000633 +705bonus pay for amazing work on #OSS 00010000633 +62223138010481967038518 0000100000#POtziD6aUv2EV#William Brown 1121042880000634 +705bonus pay for amazing work on #OSS 00010000634 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000635 +705bonus pay for amazing work on #OSS 00010000635 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000636 +705bonus pay for amazing work on #OSS 00010000636 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000637 +705bonus pay for amazing work on #OSS 00010000637 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000638 +705bonus pay for amazing work on #OSS 00010000638 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000639 +705bonus pay for amazing work on #OSS 00010000639 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000640 +705bonus pay for amazing work on #OSS 00010000640 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000641 +705bonus pay for amazing work on #OSS 00010000641 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000642 +705bonus pay for amazing work on #OSS 00010000642 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000643 +705bonus pay for amazing work on #OSS 00010000643 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000644 +705bonus pay for amazing work on #OSS 00010000644 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000645 +705bonus pay for amazing work on #OSS 00010000645 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000646 +705bonus pay for amazing work on #OSS 00010000646 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000647 +705bonus pay for amazing work on #OSS 00010000647 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000648 +705bonus pay for amazing work on #OSS 00010000648 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000649 +705bonus pay for amazing work on #OSS 00010000649 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000650 +705bonus pay for amazing work on #OSS 00010000650 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000651 +705bonus pay for amazing work on #OSS 00010000651 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000652 +705bonus pay for amazing work on #OSS 00010000652 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000653 +705bonus pay for amazing work on #OSS 00010000653 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000654 +705bonus pay for amazing work on #OSS 00010000654 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000655 +705bonus pay for amazing work on #OSS 00010000655 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000656 +705bonus pay for amazing work on #OSS 00010000656 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000657 +705bonus pay for amazing work on #OSS 00010000657 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000658 +705bonus pay for amazing work on #OSS 00010000658 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000659 +705bonus pay for amazing work on #OSS 00010000659 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000660 +705bonus pay for amazing work on #OSS 00010000660 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000661 +705bonus pay for amazing work on #OSS 00010000661 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000662 +705bonus pay for amazing work on #OSS 00010000662 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000663 +705bonus pay for amazing work on #OSS 00010000663 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000664 +705bonus pay for amazing work on #OSS 00010000664 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000665 +705bonus pay for amazing work on #OSS 00010000665 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000666 +705bonus pay for amazing work on #OSS 00010000666 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000667 +705bonus pay for amazing work on #OSS 00010000667 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000668 +705bonus pay for amazing work on #OSS 00010000668 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000669 +705bonus pay for amazing work on #OSS 00010000669 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000670 +705bonus pay for amazing work on #OSS 00010000670 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000671 +705bonus pay for amazing work on #OSS 00010000671 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000672 +705bonus pay for amazing work on #OSS 00010000672 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000673 +705bonus pay for amazing work on #OSS 00010000673 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000674 +705bonus pay for amazing work on #OSS 00010000674 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000675 +705bonus pay for amazing work on #OSS 00010000675 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000676 +705bonus pay for amazing work on #OSS 00010000676 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000677 +705bonus pay for amazing work on #OSS 00010000677 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000678 +705bonus pay for amazing work on #OSS 00010000678 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000679 +705bonus pay for amazing work on #OSS 00010000679 +62223138010481967038518 0000100000#DDicHH92JTVzl#Jayden Miller 1121042880000680 +705bonus pay for amazing work on #OSS 00010000680 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Aiden Taylor 1121042880000681 +705bonus pay for amazing work on #OSS 00010000681 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000682 +705bonus pay for amazing work on #OSS 00010000682 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000683 +705bonus pay for amazing work on #OSS 00010000683 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000684 +705bonus pay for amazing work on #OSS 00010000684 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000685 +705bonus pay for amazing work on #OSS 00010000685 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000686 +705bonus pay for amazing work on #OSS 00010000686 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000687 +705bonus pay for amazing work on #OSS 00010000687 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000688 +705bonus pay for amazing work on #OSS 00010000688 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000689 +705bonus pay for amazing work on #OSS 00010000689 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000690 +705bonus pay for amazing work on #OSS 00010000690 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000691 +705bonus pay for amazing work on #OSS 00010000691 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000692 +705bonus pay for amazing work on #OSS 00010000692 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000693 +705bonus pay for amazing work on #OSS 00010000693 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000694 +705bonus pay for amazing work on #OSS 00010000694 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000695 +705bonus pay for amazing work on #OSS 00010000695 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000696 +705bonus pay for amazing work on #OSS 00010000696 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000697 +705bonus pay for amazing work on #OSS 00010000697 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000698 +705bonus pay for amazing work on #OSS 00010000698 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000699 +705bonus pay for amazing work on #OSS 00010000699 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000700 +705bonus pay for amazing work on #OSS 00010000700 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000701 +705bonus pay for amazing work on #OSS 00010000701 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000702 +705bonus pay for amazing work on #OSS 00010000702 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000703 +705bonus pay for amazing work on #OSS 00010000703 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000704 +705bonus pay for amazing work on #OSS 00010000704 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000705 +705bonus pay for amazing work on #OSS 00010000705 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000706 +705bonus pay for amazing work on #OSS 00010000706 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000707 +705bonus pay for amazing work on #OSS 00010000707 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000708 +705bonus pay for amazing work on #OSS 00010000708 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000709 +705bonus pay for amazing work on #OSS 00010000709 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000710 +705bonus pay for amazing work on #OSS 00010000710 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000711 +705bonus pay for amazing work on #OSS 00010000711 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000712 +705bonus pay for amazing work on #OSS 00010000712 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000713 +705bonus pay for amazing work on #OSS 00010000713 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000714 +705bonus pay for amazing work on #OSS 00010000714 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000715 +705bonus pay for amazing work on #OSS 00010000715 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000716 +705bonus pay for amazing work on #OSS 00010000716 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000717 +705bonus pay for amazing work on #OSS 00010000717 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000718 +705bonus pay for amazing work on #OSS 00010000718 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000719 +705bonus pay for amazing work on #OSS 00010000719 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000720 +705bonus pay for amazing work on #OSS 00010000720 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000721 +705bonus pay for amazing work on #OSS 00010000721 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000722 +705bonus pay for amazing work on #OSS 00010000722 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000723 +705bonus pay for amazing work on #OSS 00010000723 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000724 +705bonus pay for amazing work on #OSS 00010000724 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000725 +705bonus pay for amazing work on #OSS 00010000725 +62223138010481967038518 0000100000#5mgzNGI4oQeA7#Elizabeth Taylor 1121042880000726 +705bonus pay for amazing work on #OSS 00010000726 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Elizabeth Davis 1121042880000727 +705bonus pay for amazing work on #OSS 00010000727 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000728 +705bonus pay for amazing work on #OSS 00010000728 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000729 +705bonus pay for amazing work on #OSS 00010000729 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000730 +705bonus pay for amazing work on #OSS 00010000730 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000731 +705bonus pay for amazing work on #OSS 00010000731 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000732 +705bonus pay for amazing work on #OSS 00010000732 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000733 +705bonus pay for amazing work on #OSS 00010000733 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000734 +705bonus pay for amazing work on #OSS 00010000734 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000735 +705bonus pay for amazing work on #OSS 00010000735 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000736 +705bonus pay for amazing work on #OSS 00010000736 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000737 +705bonus pay for amazing work on #OSS 00010000737 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000738 +705bonus pay for amazing work on #OSS 00010000738 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000739 +705bonus pay for amazing work on #OSS 00010000739 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000740 +705bonus pay for amazing work on #OSS 00010000740 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000741 +705bonus pay for amazing work on #OSS 00010000741 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000742 +705bonus pay for amazing work on #OSS 00010000742 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000743 +705bonus pay for amazing work on #OSS 00010000743 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000744 +705bonus pay for amazing work on #OSS 00010000744 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000745 +705bonus pay for amazing work on #OSS 00010000745 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000746 +705bonus pay for amazing work on #OSS 00010000746 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000747 +705bonus pay for amazing work on #OSS 00010000747 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000748 +705bonus pay for amazing work on #OSS 00010000748 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000749 +705bonus pay for amazing work on #OSS 00010000749 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000750 +705bonus pay for amazing work on #OSS 00010000750 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000751 +705bonus pay for amazing work on #OSS 00010000751 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000752 +705bonus pay for amazing work on #OSS 00010000752 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000753 +705bonus pay for amazing work on #OSS 00010000753 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000754 +705bonus pay for amazing work on #OSS 00010000754 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000755 +705bonus pay for amazing work on #OSS 00010000755 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000756 +705bonus pay for amazing work on #OSS 00010000756 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000757 +705bonus pay for amazing work on #OSS 00010000757 +62223138010481967038518 0000100000#rJZzKcRZ3kOFp#Emily Davis 1121042880000758 +705bonus pay for amazing work on #OSS 00010000758 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000759 +705bonus pay for amazing work on #OSS 00010000759 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000760 +705bonus pay for amazing work on #OSS 00010000760 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000761 +705bonus pay for amazing work on #OSS 00010000761 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000762 +705bonus pay for amazing work on #OSS 00010000762 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000763 +705bonus pay for amazing work on #OSS 00010000763 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000764 +705bonus pay for amazing work on #OSS 00010000764 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000765 +705bonus pay for amazing work on #OSS 00010000765 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000766 +705bonus pay for amazing work on #OSS 00010000766 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000767 +705bonus pay for amazing work on #OSS 00010000767 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000768 +705bonus pay for amazing work on #OSS 00010000768 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000769 +705bonus pay for amazing work on #OSS 00010000769 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000770 +705bonus pay for amazing work on #OSS 00010000770 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000771 +705bonus pay for amazing work on #OSS 00010000771 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000772 +705bonus pay for amazing work on #OSS 00010000772 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000773 +705bonus pay for amazing work on #OSS 00010000773 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000774 +705bonus pay for amazing work on #OSS 00010000774 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000775 +705bonus pay for amazing work on #OSS 00010000775 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000776 +705bonus pay for amazing work on #OSS 00010000776 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000777 +705bonus pay for amazing work on #OSS 00010000777 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000778 +705bonus pay for amazing work on #OSS 00010000778 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000779 +705bonus pay for amazing work on #OSS 00010000779 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000780 +705bonus pay for amazing work on #OSS 00010000780 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000781 +705bonus pay for amazing work on #OSS 00010000781 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000782 +705bonus pay for amazing work on #OSS 00010000782 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000783 +705bonus pay for amazing work on #OSS 00010000783 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000784 +705bonus pay for amazing work on #OSS 00010000784 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000785 +705bonus pay for amazing work on #OSS 00010000785 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000786 +705bonus pay for amazing work on #OSS 00010000786 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000787 +705bonus pay for amazing work on #OSS 00010000787 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000788 +705bonus pay for amazing work on #OSS 00010000788 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000789 +705bonus pay for amazing work on #OSS 00010000789 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000790 +705bonus pay for amazing work on #OSS 00010000790 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000791 +705bonus pay for amazing work on #OSS 00010000791 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000792 +705bonus pay for amazing work on #OSS 00010000792 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000793 +705bonus pay for amazing work on #OSS 00010000793 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000794 +705bonus pay for amazing work on #OSS 00010000794 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000795 +705bonus pay for amazing work on #OSS 00010000795 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000796 +705bonus pay for amazing work on #OSS 00010000796 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000797 +705bonus pay for amazing work on #OSS 00010000797 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000798 +705bonus pay for amazing work on #OSS 00010000798 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000799 +705bonus pay for amazing work on #OSS 00010000799 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000800 +705bonus pay for amazing work on #OSS 00010000800 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000801 +705bonus pay for amazing work on #OSS 00010000801 +62223138010481967038518 0000100000#2XnXwH9aORiBf#Mia Wilson 1121042880000802 +705bonus pay for amazing work on #OSS 00010000802 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000803 +705bonus pay for amazing work on #OSS 00010000803 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000804 +705bonus pay for amazing work on #OSS 00010000804 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000805 +705bonus pay for amazing work on #OSS 00010000805 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000806 +705bonus pay for amazing work on #OSS 00010000806 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000807 +705bonus pay for amazing work on #OSS 00010000807 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000808 +705bonus pay for amazing work on #OSS 00010000808 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000809 +705bonus pay for amazing work on #OSS 00010000809 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000810 +705bonus pay for amazing work on #OSS 00010000810 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000811 +705bonus pay for amazing work on #OSS 00010000811 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000812 +705bonus pay for amazing work on #OSS 00010000812 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000813 +705bonus pay for amazing work on #OSS 00010000813 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000814 +705bonus pay for amazing work on #OSS 00010000814 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000815 +705bonus pay for amazing work on #OSS 00010000815 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000816 +705bonus pay for amazing work on #OSS 00010000816 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000817 +705bonus pay for amazing work on #OSS 00010000817 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000818 +705bonus pay for amazing work on #OSS 00010000818 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000819 +705bonus pay for amazing work on #OSS 00010000819 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000820 +705bonus pay for amazing work on #OSS 00010000820 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000821 +705bonus pay for amazing work on #OSS 00010000821 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000822 +705bonus pay for amazing work on #OSS 00010000822 +62223138010481967038518 0000100000#ny5kRfeOxny5K#Sofia Garcia 1121042880000823 +705bonus pay for amazing work on #OSS 00010000823 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Chloe Anderson 1121042880000824 +705bonus pay for amazing work on #OSS 00010000824 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000825 +705bonus pay for amazing work on #OSS 00010000825 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000826 +705bonus pay for amazing work on #OSS 00010000826 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000827 +705bonus pay for amazing work on #OSS 00010000827 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000828 +705bonus pay for amazing work on #OSS 00010000828 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000829 +705bonus pay for amazing work on #OSS 00010000829 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000830 +705bonus pay for amazing work on #OSS 00010000830 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000831 +705bonus pay for amazing work on #OSS 00010000831 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000832 +705bonus pay for amazing work on #OSS 00010000832 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000833 +705bonus pay for amazing work on #OSS 00010000833 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000834 +705bonus pay for amazing work on #OSS 00010000834 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000835 +705bonus pay for amazing work on #OSS 00010000835 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000836 +705bonus pay for amazing work on #OSS 00010000836 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000837 +705bonus pay for amazing work on #OSS 00010000837 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000838 +705bonus pay for amazing work on #OSS 00010000838 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000839 +705bonus pay for amazing work on #OSS 00010000839 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000840 +705bonus pay for amazing work on #OSS 00010000840 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000841 +705bonus pay for amazing work on #OSS 00010000841 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000842 +705bonus pay for amazing work on #OSS 00010000842 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000843 +705bonus pay for amazing work on #OSS 00010000843 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000844 +705bonus pay for amazing work on #OSS 00010000844 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000845 +705bonus pay for amazing work on #OSS 00010000845 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000846 +705bonus pay for amazing work on #OSS 00010000846 +62223138010481967038518 0000100000#8A51ft2CdoHGI#Daniel Anderson 1121042880000847 +705bonus pay for amazing work on #OSS 00010000847 +62223138010481967038518 0000100000#xLbDDXNecS1XW#Mason Johnson 1121042880000848 +705bonus pay for amazing work on #OSS 00010000848 +62223138010481967038518 0000100000#xLbDDXNecS1XW#Emma Johnson 1121042880000849 +705bonus pay for amazing work on #OSS 00010000849 +62223138010481967038518 0000100000#xLbDDXNecS1XW#Emma Johnson 1121042880000850 +705bonus pay for amazing work on #OSS 00010000850 +62223138010481967038518 0000100000#xLbDDXNecS1XW#Emma Johnson 1121042880000851 +705bonus pay for amazing work on #OSS 00010000851 +62223138010481967038518 0000100000#xLbDDXNecS1XW#Emma Johnson 1121042880000852 +705bonus pay for amazing work on #OSS 00010000852 +62223138010481967038518 0000100000#xLbDDXNecS1XW#Emma Johnson 1121042880000853 +705bonus pay for amazing work on #OSS 00010000853 +62223138010481967038518 0000100000#xLbDDXNecS1XW#Emma Johnson 1121042880000854 +705bonus pay for amazing work on #OSS 00010000854 +62223138010481967038518 0000100000#xLbDDXNecS1XW#Emma Johnson 1121042880000855 +705bonus pay for amazing work on #OSS 00010000855 +62223138010481967038518 0000100000#xLbDDXNecS1XW#Emma Johnson 1121042880000856 +705bonus pay for amazing work on #OSS 00010000856 +62223138010481967038518 0000100000#xLbDDXNecS1XW#Emma Johnson 1121042880000857 +705bonus pay for amazing work on #OSS 00010000857 +62223138010481967038518 0000100000#xLbDDXNecS1XW#Emma Johnson 1121042880000858 +705bonus pay for amazing work on #OSS 00010000858 +62223138010481967038518 0000100000#xLbDDXNecS1XW#Emma Johnson 1121042880000859 +705bonus pay for amazing work on #OSS 00010000859 +62223138010481967038518 0000100000#xLbDDXNecS1XW#Emma Johnson 1121042880000860 +705bonus pay for amazing work on #OSS 00010000860 +62223138010481967038518 0000100000#xLbDDXNecS1XW#Emma Johnson 1121042880000861 +705bonus pay for amazing work on #OSS 00010000861 +62223138010481967038518 0000100000#xLbDDXNecS1XW#Emma Johnson 1121042880000862 +705bonus pay for amazing work on #OSS 00010000862 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Emma Garcia 1121042880000863 +705bonus pay for amazing work on #OSS 00010000863 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000864 +705bonus pay for amazing work on #OSS 00010000864 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000865 +705bonus pay for amazing work on #OSS 00010000865 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000866 +705bonus pay for amazing work on #OSS 00010000866 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000867 +705bonus pay for amazing work on #OSS 00010000867 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000868 +705bonus pay for amazing work on #OSS 00010000868 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000869 +705bonus pay for amazing work on #OSS 00010000869 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000870 +705bonus pay for amazing work on #OSS 00010000870 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000871 +705bonus pay for amazing work on #OSS 00010000871 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000872 +705bonus pay for amazing work on #OSS 00010000872 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000873 +705bonus pay for amazing work on #OSS 00010000873 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000874 +705bonus pay for amazing work on #OSS 00010000874 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000875 +705bonus pay for amazing work on #OSS 00010000875 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000876 +705bonus pay for amazing work on #OSS 00010000876 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000877 +705bonus pay for amazing work on #OSS 00010000877 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000878 +705bonus pay for amazing work on #OSS 00010000878 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000879 +705bonus pay for amazing work on #OSS 00010000879 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000880 +705bonus pay for amazing work on #OSS 00010000880 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000881 +705bonus pay for amazing work on #OSS 00010000881 +62223138010481967038518 0000100000#DHu7cITnNKaM6#Sofia Garcia 1121042880000882 +705bonus pay for amazing work on #OSS 00010000882 +62223138010481967038518 0000100000#W9k5pTgp350rB#Ava Brown 1121042880000883 +705bonus pay for amazing work on #OSS 00010000883 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000884 +705bonus pay for amazing work on #OSS 00010000884 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000885 +705bonus pay for amazing work on #OSS 00010000885 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000886 +705bonus pay for amazing work on #OSS 00010000886 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000887 +705bonus pay for amazing work on #OSS 00010000887 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000888 +705bonus pay for amazing work on #OSS 00010000888 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000889 +705bonus pay for amazing work on #OSS 00010000889 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000890 +705bonus pay for amazing work on #OSS 00010000890 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000891 +705bonus pay for amazing work on #OSS 00010000891 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000892 +705bonus pay for amazing work on #OSS 00010000892 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000893 +705bonus pay for amazing work on #OSS 00010000893 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000894 +705bonus pay for amazing work on #OSS 00010000894 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000895 +705bonus pay for amazing work on #OSS 00010000895 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000896 +705bonus pay for amazing work on #OSS 00010000896 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000897 +705bonus pay for amazing work on #OSS 00010000897 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000898 +705bonus pay for amazing work on #OSS 00010000898 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000899 +705bonus pay for amazing work on #OSS 00010000899 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000900 +705bonus pay for amazing work on #OSS 00010000900 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000901 +705bonus pay for amazing work on #OSS 00010000901 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000902 +705bonus pay for amazing work on #OSS 00010000902 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000903 +705bonus pay for amazing work on #OSS 00010000903 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000904 +705bonus pay for amazing work on #OSS 00010000904 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000905 +705bonus pay for amazing work on #OSS 00010000905 +62223138010481967038518 0000100000#W9k5pTgp350rB#William Brown 1121042880000906 +705bonus pay for amazing work on #OSS 00010000906 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000907 +705bonus pay for amazing work on #OSS 00010000907 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000908 +705bonus pay for amazing work on #OSS 00010000908 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000909 +705bonus pay for amazing work on #OSS 00010000909 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000910 +705bonus pay for amazing work on #OSS 00010000910 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000911 +705bonus pay for amazing work on #OSS 00010000911 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000912 +705bonus pay for amazing work on #OSS 00010000912 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000913 +705bonus pay for amazing work on #OSS 00010000913 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000914 +705bonus pay for amazing work on #OSS 00010000914 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000915 +705bonus pay for amazing work on #OSS 00010000915 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000916 +705bonus pay for amazing work on #OSS 00010000916 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000917 +705bonus pay for amazing work on #OSS 00010000917 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000918 +705bonus pay for amazing work on #OSS 00010000918 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000919 +705bonus pay for amazing work on #OSS 00010000919 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000920 +705bonus pay for amazing work on #OSS 00010000920 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000921 +705bonus pay for amazing work on #OSS 00010000921 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000922 +705bonus pay for amazing work on #OSS 00010000922 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000923 +705bonus pay for amazing work on #OSS 00010000923 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000924 +705bonus pay for amazing work on #OSS 00010000924 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000925 +705bonus pay for amazing work on #OSS 00010000925 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000926 +705bonus pay for amazing work on #OSS 00010000926 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000927 +705bonus pay for amazing work on #OSS 00010000927 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000928 +705bonus pay for amazing work on #OSS 00010000928 +62223138010481967038518 0000100000#ylfzXyDdE05MM#Joshua Thompson 1121042880000929 +705bonus pay for amazing work on #OSS 00010000929 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Joshua Taylor 1121042880000930 +705bonus pay for amazing work on #OSS 00010000930 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000931 +705bonus pay for amazing work on #OSS 00010000931 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000932 +705bonus pay for amazing work on #OSS 00010000932 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000933 +705bonus pay for amazing work on #OSS 00010000933 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000934 +705bonus pay for amazing work on #OSS 00010000934 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000935 +705bonus pay for amazing work on #OSS 00010000935 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000936 +705bonus pay for amazing work on #OSS 00010000936 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000937 +705bonus pay for amazing work on #OSS 00010000937 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000938 +705bonus pay for amazing work on #OSS 00010000938 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000939 +705bonus pay for amazing work on #OSS 00010000939 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000940 +705bonus pay for amazing work on #OSS 00010000940 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000941 +705bonus pay for amazing work on #OSS 00010000941 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000942 +705bonus pay for amazing work on #OSS 00010000942 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000943 +705bonus pay for amazing work on #OSS 00010000943 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000944 +705bonus pay for amazing work on #OSS 00010000944 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000945 +705bonus pay for amazing work on #OSS 00010000945 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000946 +705bonus pay for amazing work on #OSS 00010000946 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000947 +705bonus pay for amazing work on #OSS 00010000947 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000948 +705bonus pay for amazing work on #OSS 00010000948 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000949 +705bonus pay for amazing work on #OSS 00010000949 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000950 +705bonus pay for amazing work on #OSS 00010000950 +62223138010481967038518 0000100000#SEPMKcDjlXmdz#Elizabeth Taylor 1121042880000951 +705bonus pay for amazing work on #OSS 00010000951 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Elizabeth Wilson 1121042880000952 +705bonus pay for amazing work on #OSS 00010000952 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000953 +705bonus pay for amazing work on #OSS 00010000953 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000954 +705bonus pay for amazing work on #OSS 00010000954 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000955 +705bonus pay for amazing work on #OSS 00010000955 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000956 +705bonus pay for amazing work on #OSS 00010000956 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000957 +705bonus pay for amazing work on #OSS 00010000957 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000958 +705bonus pay for amazing work on #OSS 00010000958 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000959 +705bonus pay for amazing work on #OSS 00010000959 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000960 +705bonus pay for amazing work on #OSS 00010000960 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000961 +705bonus pay for amazing work on #OSS 00010000961 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000962 +705bonus pay for amazing work on #OSS 00010000962 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000963 +705bonus pay for amazing work on #OSS 00010000963 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000964 +705bonus pay for amazing work on #OSS 00010000964 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000965 +705bonus pay for amazing work on #OSS 00010000965 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000966 +705bonus pay for amazing work on #OSS 00010000966 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000967 +705bonus pay for amazing work on #OSS 00010000967 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000968 +705bonus pay for amazing work on #OSS 00010000968 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000969 +705bonus pay for amazing work on #OSS 00010000969 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000970 +705bonus pay for amazing work on #OSS 00010000970 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000971 +705bonus pay for amazing work on #OSS 00010000971 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000972 +705bonus pay for amazing work on #OSS 00010000972 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000973 +705bonus pay for amazing work on #OSS 00010000973 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000974 +705bonus pay for amazing work on #OSS 00010000974 +62223138010481967038518 0000100000#PEKmKjcZEu6xL#Mia Wilson 1121042880000975 +705bonus pay for amazing work on #OSS 00010000975 +62223138010481967038518 0000100000#MMnbqxV85siJF#Charlotte Martinez 1121042880000976 +705bonus pay for amazing work on #OSS 00010000976 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000977 +705bonus pay for amazing work on #OSS 00010000977 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000978 +705bonus pay for amazing work on #OSS 00010000978 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000979 +705bonus pay for amazing work on #OSS 00010000979 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000980 +705bonus pay for amazing work on #OSS 00010000980 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000981 +705bonus pay for amazing work on #OSS 00010000981 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000982 +705bonus pay for amazing work on #OSS 00010000982 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000983 +705bonus pay for amazing work on #OSS 00010000983 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000984 +705bonus pay for amazing work on #OSS 00010000984 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000985 +705bonus pay for amazing work on #OSS 00010000985 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000986 +705bonus pay for amazing work on #OSS 00010000986 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000987 +705bonus pay for amazing work on #OSS 00010000987 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000988 +705bonus pay for amazing work on #OSS 00010000988 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000989 +705bonus pay for amazing work on #OSS 00010000989 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000990 +705bonus pay for amazing work on #OSS 00010000990 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000991 +705bonus pay for amazing work on #OSS 00010000991 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000992 +705bonus pay for amazing work on #OSS 00010000992 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000993 +705bonus pay for amazing work on #OSS 00010000993 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000994 +705bonus pay for amazing work on #OSS 00010000994 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000995 +705bonus pay for amazing work on #OSS 00010000995 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000996 +705bonus pay for amazing work on #OSS 00010000996 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000997 +705bonus pay for amazing work on #OSS 00010000997 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000998 +705bonus pay for amazing work on #OSS 00010000998 +62223138010481967038518 0000100000#MMnbqxV85siJF#David Martinez 1121042880000999 +705bonus pay for amazing work on #OSS 00010000999 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001000 +705bonus pay for amazing work on #OSS 00010001000 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001001 +705bonus pay for amazing work on #OSS 00010001001 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001002 +705bonus pay for amazing work on #OSS 00010001002 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001003 +705bonus pay for amazing work on #OSS 00010001003 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001004 +705bonus pay for amazing work on #OSS 00010001004 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001005 +705bonus pay for amazing work on #OSS 00010001005 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001006 +705bonus pay for amazing work on #OSS 00010001006 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001007 +705bonus pay for amazing work on #OSS 00010001007 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001008 +705bonus pay for amazing work on #OSS 00010001008 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001009 +705bonus pay for amazing work on #OSS 00010001009 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001010 +705bonus pay for amazing work on #OSS 00010001010 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001011 +705bonus pay for amazing work on #OSS 00010001011 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001012 +705bonus pay for amazing work on #OSS 00010001012 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001013 +705bonus pay for amazing work on #OSS 00010001013 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001014 +705bonus pay for amazing work on #OSS 00010001014 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001015 +705bonus pay for amazing work on #OSS 00010001015 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001016 +705bonus pay for amazing work on #OSS 00010001016 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001017 +705bonus pay for amazing work on #OSS 00010001017 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001018 +705bonus pay for amazing work on #OSS 00010001018 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001019 +705bonus pay for amazing work on #OSS 00010001019 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001020 +705bonus pay for amazing work on #OSS 00010001020 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001021 +705bonus pay for amazing work on #OSS 00010001021 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001022 +705bonus pay for amazing work on #OSS 00010001022 +62223138010481967038518 0000100000#duqXFsoZeq7R4#Daniel Anderson 1121042880001023 +705bonus pay for amazing work on #OSS 00010001023 +62223138010481967038518 0000100000#VPNWYBxbq1cV0#Sofia Garcia 1121042880001024 +705bonus pay for amazing work on #OSS 00010001024 +62223138010481967038518 0000100000#VPNWYBxbq1cV0#Sofia Garcia 1121042880001025 +705bonus pay for amazing work on #OSS 00010001025 +62223138010481967038518 0000100000#VPNWYBxbq1cV0#Sofia Garcia 1121042880001026 +705bonus pay for amazing work on #OSS 00010001026 +62223138010481967038518 0000100000#VPNWYBxbq1cV0#Sofia Garcia 1121042880001027 +705bonus pay for amazing work on #OSS 00010001027 +62223138010481967038518 0000100000#VPNWYBxbq1cV0#Sofia Garcia 1121042880001028 +705bonus pay for amazing work on #OSS 00010001028 +62223138010481967038518 0000100000#VPNWYBxbq1cV0#Sofia Garcia 1121042880001029 +705bonus pay for amazing work on #OSS 00010001029 +62223138010481967038518 0000100000#VPNWYBxbq1cV0#Sofia Garcia 1121042880001030 +705bonus pay for amazing work on #OSS 00010001030 +62223138010481967038518 0000100000#VPNWYBxbq1cV0#Sofia Garcia 1121042880001031 +705bonus pay for amazing work on #OSS 00010001031 +62223138010481967038518 0000100000#VPNWYBxbq1cV0#Sofia Garcia 1121042880001032 +705bonus pay for amazing work on #OSS 00010001032 +62223138010481967038518 0000100000#VPNWYBxbq1cV0#Sofia Garcia 1121042880001033 +705bonus pay for amazing work on #OSS 00010001033 +62223138010481967038518 0000100000#VPNWYBxbq1cV0#Sofia Garcia 1121042880001034 +705bonus pay for amazing work on #OSS 00010001034 +62223138010481967038518 0000100000#VPNWYBxbq1cV0#Sofia Garcia 1121042880001035 +705bonus pay for amazing work on #OSS 00010001035 +62223138010481967038518 0000100000#VPNWYBxbq1cV0#Sofia Garcia 1121042880001036 +705bonus pay for amazing work on #OSS 00010001036 +62223138010481967038518 0000100000#VPNWYBxbq1cV0#Sofia Garcia 1121042880001037 +705bonus pay for amazing work on #OSS 00010001037 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Sofia Moore 1121042880001038 +705bonus pay for amazing work on #OSS 00010001038 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001039 +705bonus pay for amazing work on #OSS 00010001039 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001040 +705bonus pay for amazing work on #OSS 00010001040 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001041 +705bonus pay for amazing work on #OSS 00010001041 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001042 +705bonus pay for amazing work on #OSS 00010001042 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001043 +705bonus pay for amazing work on #OSS 00010001043 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001044 +705bonus pay for amazing work on #OSS 00010001044 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001045 +705bonus pay for amazing work on #OSS 00010001045 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001046 +705bonus pay for amazing work on #OSS 00010001046 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001047 +705bonus pay for amazing work on #OSS 00010001047 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001048 +705bonus pay for amazing work on #OSS 00010001048 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001049 +705bonus pay for amazing work on #OSS 00010001049 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001050 +705bonus pay for amazing work on #OSS 00010001050 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001051 +705bonus pay for amazing work on #OSS 00010001051 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001052 +705bonus pay for amazing work on #OSS 00010001052 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001053 +705bonus pay for amazing work on #OSS 00010001053 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001054 +705bonus pay for amazing work on #OSS 00010001054 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001055 +705bonus pay for amazing work on #OSS 00010001055 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001056 +705bonus pay for amazing work on #OSS 00010001056 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001057 +705bonus pay for amazing work on #OSS 00010001057 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001058 +705bonus pay for amazing work on #OSS 00010001058 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001059 +705bonus pay for amazing work on #OSS 00010001059 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001060 +705bonus pay for amazing work on #OSS 00010001060 +62223138010481967038518 0000100000#XVQb0DJy22jLF#Alexander Moore 1121042880001061 +705bonus pay for amazing work on #OSS 00010001061 +62223138010481967038518 0000100000#f9LmvDTl2RGEb#Joshua Thompson 1121042880001062 +705bonus pay for amazing work on #OSS 00010001062 +62223138010481967038518 0000100000#f9LmvDTl2RGEb#Joshua Thompson 1121042880001063 +705bonus pay for amazing work on #OSS 00010001063 +62223138010481967038518 0000100000#f9LmvDTl2RGEb#Joshua Thompson 1121042880001064 +705bonus pay for amazing work on #OSS 00010001064 +62223138010481967038518 0000100000#f9LmvDTl2RGEb#Joshua Thompson 1121042880001065 +705bonus pay for amazing work on #OSS 00010001065 +62223138010481967038518 0000100000#f9LmvDTl2RGEb#Joshua Thompson 1121042880001066 +705bonus pay for amazing work on #OSS 00010001066 +62223138010481967038518 0000100000#f9LmvDTl2RGEb#Joshua Thompson 1121042880001067 +705bonus pay for amazing work on #OSS 00010001067 +62223138010481967038518 0000100000#f9LmvDTl2RGEb#Joshua Thompson 1121042880001068 +705bonus pay for amazing work on #OSS 00010001068 +62223138010481967038518 0000100000#f9LmvDTl2RGEb#Joshua Thompson 1121042880001069 +705bonus pay for amazing work on #OSS 00010001069 +62223138010481967038518 0000100000#f9LmvDTl2RGEb#Joshua Thompson 1121042880001070 +705bonus pay for amazing work on #OSS 00010001070 +62223138010481967038518 0000100000#f9LmvDTl2RGEb#Joshua Thompson 1121042880001071 +705bonus pay for amazing work on #OSS 00010001071 +62223138010481967038518 0000100000#f9LmvDTl2RGEb#Joshua Thompson 1121042880001072 +705bonus pay for amazing work on #OSS 00010001072 +62223138010481967038518 0000100000#f9LmvDTl2RGEb#Joshua Thompson 1121042880001073 +705bonus pay for amazing work on #OSS 00010001073 +62223138010481967038518 0000100000#f9LmvDTl2RGEb#Joshua Thompson 1121042880001074 +705bonus pay for amazing work on #OSS 00010001074 +62223138010481967038518 0000100000#f9LmvDTl2RGEb#Joshua Thompson 1121042880001075 +705bonus pay for amazing work on #OSS 00010001075 +62223138010481967038518 0000100000#f9LmvDTl2RGEb#Joshua Thompson 1121042880001076 +705bonus pay for amazing work on #OSS 00010001076 +62223138010481967038518 0000100000#f9LmvDTl2RGEb#Joshua Thompson 1121042880001077 +705bonus pay for amazing work on #OSS 00010001077 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001078 +705bonus pay for amazing work on #OSS 00010001078 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001079 +705bonus pay for amazing work on #OSS 00010001079 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001080 +705bonus pay for amazing work on #OSS 00010001080 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001081 +705bonus pay for amazing work on #OSS 00010001081 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001082 +705bonus pay for amazing work on #OSS 00010001082 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001083 +705bonus pay for amazing work on #OSS 00010001083 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001084 +705bonus pay for amazing work on #OSS 00010001084 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001085 +705bonus pay for amazing work on #OSS 00010001085 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001086 +705bonus pay for amazing work on #OSS 00010001086 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001087 +705bonus pay for amazing work on #OSS 00010001087 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001088 +705bonus pay for amazing work on #OSS 00010001088 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001089 +705bonus pay for amazing work on #OSS 00010001089 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001090 +705bonus pay for amazing work on #OSS 00010001090 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001091 +705bonus pay for amazing work on #OSS 00010001091 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001092 +705bonus pay for amazing work on #OSS 00010001092 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001093 +705bonus pay for amazing work on #OSS 00010001093 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001094 +705bonus pay for amazing work on #OSS 00010001094 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001095 +705bonus pay for amazing work on #OSS 00010001095 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001096 +705bonus pay for amazing work on #OSS 00010001096 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001097 +705bonus pay for amazing work on #OSS 00010001097 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001098 +705bonus pay for amazing work on #OSS 00010001098 +62223138010481967038518 0000100000#bqSMS2gv0lFQA#Emma Johnson 1121042880001099 +705bonus pay for amazing work on #OSS 00010001099 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#Ava Brown 1121042880001100 +705bonus pay for amazing work on #OSS 00010001100 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001101 +705bonus pay for amazing work on #OSS 00010001101 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001102 +705bonus pay for amazing work on #OSS 00010001102 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001103 +705bonus pay for amazing work on #OSS 00010001103 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001104 +705bonus pay for amazing work on #OSS 00010001104 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001105 +705bonus pay for amazing work on #OSS 00010001105 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001106 +705bonus pay for amazing work on #OSS 00010001106 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001107 +705bonus pay for amazing work on #OSS 00010001107 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001108 +705bonus pay for amazing work on #OSS 00010001108 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001109 +705bonus pay for amazing work on #OSS 00010001109 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001110 +705bonus pay for amazing work on #OSS 00010001110 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001111 +705bonus pay for amazing work on #OSS 00010001111 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001112 +705bonus pay for amazing work on #OSS 00010001112 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001113 +705bonus pay for amazing work on #OSS 00010001113 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001114 +705bonus pay for amazing work on #OSS 00010001114 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001115 +705bonus pay for amazing work on #OSS 00010001115 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001116 +705bonus pay for amazing work on #OSS 00010001116 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001117 +705bonus pay for amazing work on #OSS 00010001117 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001118 +705bonus pay for amazing work on #OSS 00010001118 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001119 +705bonus pay for amazing work on #OSS 00010001119 +62223138010481967038518 0000100000#ZpqPerIgaRxcD#William Brown 1121042880001120 +705bonus pay for amazing work on #OSS 00010001120 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Benjamin Martin 1121042880001121 +705bonus pay for amazing work on #OSS 00010001121 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001122 +705bonus pay for amazing work on #OSS 00010001122 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001123 +705bonus pay for amazing work on #OSS 00010001123 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001124 +705bonus pay for amazing work on #OSS 00010001124 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001125 +705bonus pay for amazing work on #OSS 00010001125 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001126 +705bonus pay for amazing work on #OSS 00010001126 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001127 +705bonus pay for amazing work on #OSS 00010001127 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001128 +705bonus pay for amazing work on #OSS 00010001128 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001129 +705bonus pay for amazing work on #OSS 00010001129 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001130 +705bonus pay for amazing work on #OSS 00010001130 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001131 +705bonus pay for amazing work on #OSS 00010001131 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001132 +705bonus pay for amazing work on #OSS 00010001132 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001133 +705bonus pay for amazing work on #OSS 00010001133 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001134 +705bonus pay for amazing work on #OSS 00010001134 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001135 +705bonus pay for amazing work on #OSS 00010001135 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001136 +705bonus pay for amazing work on #OSS 00010001136 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001137 +705bonus pay for amazing work on #OSS 00010001137 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001138 +705bonus pay for amazing work on #OSS 00010001138 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001139 +705bonus pay for amazing work on #OSS 00010001139 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001140 +705bonus pay for amazing work on #OSS 00010001140 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001141 +705bonus pay for amazing work on #OSS 00010001141 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001142 +705bonus pay for amazing work on #OSS 00010001142 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001143 +705bonus pay for amazing work on #OSS 00010001143 +62223138010481967038518 0000100000#NDXlpqjNXfmKe#Lily Martin 1121042880001144 +705bonus pay for amazing work on #OSS 00010001144 +62223138010481967038518 0000100000#tUox6ECWq6HnU#Ella Thomas 1121042880001145 +705bonus pay for amazing work on #OSS 00010001145 +62223138010481967038518 0000100000#tUox6ECWq6HnU#Ella Thomas 1121042880001146 +705bonus pay for amazing work on #OSS 00010001146 +62223138010481967038518 0000100000#tUox6ECWq6HnU#Ella Thomas 1121042880001147 +705bonus pay for amazing work on #OSS 00010001147 +62223138010481967038518 0000100000#tUox6ECWq6HnU#Ella Thomas 1121042880001148 +705bonus pay for amazing work on #OSS 00010001148 +62223138010481967038518 0000100000#tUox6ECWq6HnU#Ella Thomas 1121042880001149 +705bonus pay for amazing work on #OSS 00010001149 +62223138010481967038518 0000100000#tUox6ECWq6HnU#Ella Thomas 1121042880001150 +705bonus pay for amazing work on #OSS 00010001150 +62223138010481967038518 0000100000#tUox6ECWq6HnU#Ella Thomas 1121042880001151 +705bonus pay for amazing work on #OSS 00010001151 +62223138010481967038518 0000100000#tUox6ECWq6HnU#Ella Thomas 1121042880001152 +705bonus pay for amazing work on #OSS 00010001152 +62223138010481967038518 0000100000#tUox6ECWq6HnU#Ella Thomas 1121042880001153 +705bonus pay for amazing work on #OSS 00010001153 +62223138010481967038518 0000100000#tUox6ECWq6HnU#Ella Thomas 1121042880001154 +705bonus pay for amazing work on #OSS 00010001154 +62223138010481967038518 0000100000#tUox6ECWq6HnU#Ella Thomas 1121042880001155 +705bonus pay for amazing work on #OSS 00010001155 +62223138010481967038518 0000100000#tUox6ECWq6HnU#Ella Thomas 1121042880001156 +705bonus pay for amazing work on #OSS 00010001156 +62223138010481967038518 0000100000#tUox6ECWq6HnU#Ella Thomas 1121042880001157 +705bonus pay for amazing work on #OSS 00010001157 +62223138010481967038518 0000100000#tUox6ECWq6HnU#Ella Thomas 1121042880001158 +705bonus pay for amazing work on #OSS 00010001158 +62223138010481967038518 0000100000#tUox6ECWq6HnU#Ella Thomas 1121042880001159 +705bonus pay for amazing work on #OSS 00010001159 +62223138010481967038518 0000100000#tUox6ECWq6HnU#Ella Thomas 1121042880001160 +705bonus pay for amazing work on #OSS 00010001160 +62223138010481967038518 0000100000#tUox6ECWq6HnU#Ella Thomas 1121042880001161 +705bonus pay for amazing work on #OSS 00010001161 +62223138010481967038518 0000100000#wBch3otsORgxX#Ava Brown 1121042880001162 +705bonus pay for amazing work on #OSS 00010001162 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001163 +705bonus pay for amazing work on #OSS 00010001163 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001164 +705bonus pay for amazing work on #OSS 00010001164 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001165 +705bonus pay for amazing work on #OSS 00010001165 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001166 +705bonus pay for amazing work on #OSS 00010001166 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001167 +705bonus pay for amazing work on #OSS 00010001167 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001168 +705bonus pay for amazing work on #OSS 00010001168 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001169 +705bonus pay for amazing work on #OSS 00010001169 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001170 +705bonus pay for amazing work on #OSS 00010001170 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001171 +705bonus pay for amazing work on #OSS 00010001171 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001172 +705bonus pay for amazing work on #OSS 00010001172 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001173 +705bonus pay for amazing work on #OSS 00010001173 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001174 +705bonus pay for amazing work on #OSS 00010001174 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001175 +705bonus pay for amazing work on #OSS 00010001175 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001176 +705bonus pay for amazing work on #OSS 00010001176 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001177 +705bonus pay for amazing work on #OSS 00010001177 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001178 +705bonus pay for amazing work on #OSS 00010001178 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001179 +705bonus pay for amazing work on #OSS 00010001179 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001180 +705bonus pay for amazing work on #OSS 00010001180 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001181 +705bonus pay for amazing work on #OSS 00010001181 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001182 +705bonus pay for amazing work on #OSS 00010001182 +62223138010481967038518 0000100000#wBch3otsORgxX#William Brown 1121042880001183 +705bonus pay for amazing work on #OSS 00010001183 +62223138010481967038518 0000100000#qRxYDnBse1idd#William Taylor 1121042880001184 +705bonus pay for amazing work on #OSS 00010001184 +62223138010481967038518 0000100000#qRxYDnBse1idd#Elizabeth Taylor 1121042880001185 +705bonus pay for amazing work on #OSS 00010001185 +62223138010481967038518 0000100000#qRxYDnBse1idd#Elizabeth Taylor 1121042880001186 +705bonus pay for amazing work on #OSS 00010001186 +62223138010481967038518 0000100000#qRxYDnBse1idd#Elizabeth Taylor 1121042880001187 +705bonus pay for amazing work on #OSS 00010001187 +62223138010481967038518 0000100000#qRxYDnBse1idd#Elizabeth Taylor 1121042880001188 +705bonus pay for amazing work on #OSS 00010001188 +62223138010481967038518 0000100000#qRxYDnBse1idd#Elizabeth Taylor 1121042880001189 +705bonus pay for amazing work on #OSS 00010001189 +62223138010481967038518 0000100000#qRxYDnBse1idd#Elizabeth Taylor 1121042880001190 +705bonus pay for amazing work on #OSS 00010001190 +62223138010481967038518 0000100000#qRxYDnBse1idd#Elizabeth Taylor 1121042880001191 +705bonus pay for amazing work on #OSS 00010001191 +62223138010481967038518 0000100000#qRxYDnBse1idd#Elizabeth Taylor 1121042880001192 +705bonus pay for amazing work on #OSS 00010001192 +62223138010481967038518 0000100000#qRxYDnBse1idd#Elizabeth Taylor 1121042880001193 +705bonus pay for amazing work on #OSS 00010001193 +62223138010481967038518 0000100000#qRxYDnBse1idd#Elizabeth Taylor 1121042880001194 +705bonus pay for amazing work on #OSS 00010001194 +62223138010481967038518 0000100000#qRxYDnBse1idd#Elizabeth Taylor 1121042880001195 +705bonus pay for amazing work on #OSS 00010001195 +62223138010481967038518 0000100000#qRxYDnBse1idd#Elizabeth Taylor 1121042880001196 +705bonus pay for amazing work on #OSS 00010001196 +62223138010481967038518 0000100000#qRxYDnBse1idd#Elizabeth Taylor 1121042880001197 +705bonus pay for amazing work on #OSS 00010001197 +62223138010481967038518 0000100000#qRxYDnBse1idd#Elizabeth Taylor 1121042880001198 +705bonus pay for amazing work on #OSS 00010001198 +62223138010481967038518 0000100000#qRxYDnBse1idd#Elizabeth Taylor 1121042880001199 +705bonus pay for amazing work on #OSS 00010001199 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001200 +705bonus pay for amazing work on #OSS 00010001200 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001201 +705bonus pay for amazing work on #OSS 00010001201 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001202 +705bonus pay for amazing work on #OSS 00010001202 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001203 +705bonus pay for amazing work on #OSS 00010001203 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001204 +705bonus pay for amazing work on #OSS 00010001204 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001205 +705bonus pay for amazing work on #OSS 00010001205 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001206 +705bonus pay for amazing work on #OSS 00010001206 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001207 +705bonus pay for amazing work on #OSS 00010001207 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001208 +705bonus pay for amazing work on #OSS 00010001208 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001209 +705bonus pay for amazing work on #OSS 00010001209 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001210 +705bonus pay for amazing work on #OSS 00010001210 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001211 +705bonus pay for amazing work on #OSS 00010001211 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001212 +705bonus pay for amazing work on #OSS 00010001212 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001213 +705bonus pay for amazing work on #OSS 00010001213 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001214 +705bonus pay for amazing work on #OSS 00010001214 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001215 +705bonus pay for amazing work on #OSS 00010001215 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001216 +705bonus pay for amazing work on #OSS 00010001216 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001217 +705bonus pay for amazing work on #OSS 00010001217 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001218 +705bonus pay for amazing work on #OSS 00010001218 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001219 +705bonus pay for amazing work on #OSS 00010001219 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001220 +705bonus pay for amazing work on #OSS 00010001220 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001221 +705bonus pay for amazing work on #OSS 00010001221 +62223138010481967038518 0000100000#7pPQBzh0bKcYO#Ella Thomas 1121042880001222 +705bonus pay for amazing work on #OSS 00010001222 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001223 +705bonus pay for amazing work on #OSS 00010001223 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001224 +705bonus pay for amazing work on #OSS 00010001224 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001225 +705bonus pay for amazing work on #OSS 00010001225 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001226 +705bonus pay for amazing work on #OSS 00010001226 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001227 +705bonus pay for amazing work on #OSS 00010001227 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001228 +705bonus pay for amazing work on #OSS 00010001228 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001229 +705bonus pay for amazing work on #OSS 00010001229 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001230 +705bonus pay for amazing work on #OSS 00010001230 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001231 +705bonus pay for amazing work on #OSS 00010001231 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001232 +705bonus pay for amazing work on #OSS 00010001232 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001233 +705bonus pay for amazing work on #OSS 00010001233 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001234 +705bonus pay for amazing work on #OSS 00010001234 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001235 +705bonus pay for amazing work on #OSS 00010001235 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001236 +705bonus pay for amazing work on #OSS 00010001236 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001237 +705bonus pay for amazing work on #OSS 00010001237 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001238 +705bonus pay for amazing work on #OSS 00010001238 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001239 +705bonus pay for amazing work on #OSS 00010001239 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001240 +705bonus pay for amazing work on #OSS 00010001240 +62223138010481967038518 0000100000#2FcuepymRrlFR#Daniel Anderson 1121042880001241 +705bonus pay for amazing work on #OSS 00010001241 +62223138010481967038518 0000100000#DRCGmhLz3UDC6#Elijah Jackson 1121042880001242 +705bonus pay for amazing work on #OSS 00010001242 +62223138010481967038518 0000100000#DRCGmhLz3UDC6#Elijah Jackson 1121042880001243 +705bonus pay for amazing work on #OSS 00010001243 +62223138010481967038518 0000100000#DRCGmhLz3UDC6#Elijah Jackson 1121042880001244 +705bonus pay for amazing work on #OSS 00010001244 +62223138010481967038518 0000100000#DRCGmhLz3UDC6#Elijah Jackson 1121042880001245 +705bonus pay for amazing work on #OSS 00010001245 +62223138010481967038518 0000100000#DRCGmhLz3UDC6#Elijah Jackson 1121042880001246 +705bonus pay for amazing work on #OSS 00010001246 +62223138010481967038518 0000100000#DRCGmhLz3UDC6#Elijah Jackson 1121042880001247 +705bonus pay for amazing work on #OSS 00010001247 +62223138010481967038518 0000100000#DRCGmhLz3UDC6#Elijah Jackson 1121042880001248 +705bonus pay for amazing work on #OSS 00010001248 +62223138010481967038518 0000100000#DRCGmhLz3UDC6#Elijah Jackson 1121042880001249 +705bonus pay for amazing work on #OSS 00010001249 +62223138010481967038518 0000100000#DRCGmhLz3UDC6#Elijah Jackson 1121042880001250 +705bonus pay for amazing work on #OSS 00010001250 +82000025008922512500000000000000000125000000121042882 121042880000002 +5200Wells Fargo 121042882 PPDTrans. Des 180511 0121042880000003 +62223138010481967038518 0000100000#fFoB2FbyNAFTy#Emma Johnson 1121042880000001 +705bonus pay for amazing work on #OSS 00010000001 +62223138010481967038518 0000100000#fFoB2FbyNAFTy#Emma Johnson 1121042880000002 +705bonus pay for amazing work on #OSS 00010000002 +62223138010481967038518 0000100000#fFoB2FbyNAFTy#Emma Johnson 1121042880000003 +705bonus pay for amazing work on #OSS 00010000003 +62223138010481967038518 0000100000#fFoB2FbyNAFTy#Emma Johnson 1121042880000004 +705bonus pay for amazing work on #OSS 00010000004 +62223138010481967038518 0000100000#fFoB2FbyNAFTy#Emma Johnson 1121042880000005 +705bonus pay for amazing work on #OSS 00010000005 +62223138010481967038518 0000100000#fFoB2FbyNAFTy#Emma Johnson 1121042880000006 +705bonus pay for amazing work on #OSS 00010000006 +62223138010481967038518 0000100000#fFoB2FbyNAFTy#Emma Johnson 1121042880000007 +705bonus pay for amazing work on #OSS 00010000007 +62223138010481967038518 0000100000#fFoB2FbyNAFTy#Emma Johnson 1121042880000008 +705bonus pay for amazing work on #OSS 00010000008 +62223138010481967038518 0000100000#fFoB2FbyNAFTy#Emma Johnson 1121042880000009 +705bonus pay for amazing work on #OSS 00010000009 +62223138010481967038518 0000100000#fFoB2FbyNAFTy#Emma Johnson 1121042880000010 +705bonus pay for amazing work on #OSS 00010000010 +62223138010481967038518 0000100000#fFoB2FbyNAFTy#Emma Johnson 1121042880000011 +705bonus pay for amazing work on #OSS 00010000011 +62223138010481967038518 0000100000#fFoB2FbyNAFTy#Emma Johnson 1121042880000012 +705bonus pay for amazing work on #OSS 00010000012 +62223138010481967038518 0000100000#fFoB2FbyNAFTy#Emma Johnson 1121042880000013 +705bonus pay for amazing work on #OSS 00010000013 +62223138010481967038518 0000100000#fFoB2FbyNAFTy#Emma Johnson 1121042880000014 +705bonus pay for amazing work on #OSS 00010000014 +62223138010481967038518 0000100000#QZCX0FUPKnCAa#Sofia Garcia 1121042880000015 +705bonus pay for amazing work on #OSS 00010000015 +62223138010481967038518 0000100000#QZCX0FUPKnCAa#Sofia Garcia 1121042880000016 +705bonus pay for amazing work on #OSS 00010000016 +62223138010481967038518 0000100000#QZCX0FUPKnCAa#Sofia Garcia 1121042880000017 +705bonus pay for amazing work on #OSS 00010000017 +62223138010481967038518 0000100000#QZCX0FUPKnCAa#Sofia Garcia 1121042880000018 +705bonus pay for amazing work on #OSS 00010000018 +62223138010481967038518 0000100000#QZCX0FUPKnCAa#Sofia Garcia 1121042880000019 +705bonus pay for amazing work on #OSS 00010000019 +62223138010481967038518 0000100000#QZCX0FUPKnCAa#Sofia Garcia 1121042880000020 +705bonus pay for amazing work on #OSS 00010000020 +62223138010481967038518 0000100000#QZCX0FUPKnCAa#Sofia Garcia 1121042880000021 +705bonus pay for amazing work on #OSS 00010000021 +62223138010481967038518 0000100000#QZCX0FUPKnCAa#Sofia Garcia 1121042880000022 +705bonus pay for amazing work on #OSS 00010000022 +62223138010481967038518 0000100000#QZCX0FUPKnCAa#Sofia Garcia 1121042880000023 +705bonus pay for amazing work on #OSS 00010000023 +62223138010481967038518 0000100000#QZCX0FUPKnCAa#Sofia Garcia 1121042880000024 +705bonus pay for amazing work on #OSS 00010000024 +62223138010481967038518 0000100000#QZCX0FUPKnCAa#Sofia Garcia 1121042880000025 +705bonus pay for amazing work on #OSS 00010000025 +62223138010481967038518 0000100000#QZCX0FUPKnCAa#Sofia Garcia 1121042880000026 +705bonus pay for amazing work on #OSS 00010000026 +62223138010481967038518 0000100000#QZCX0FUPKnCAa#Sofia Garcia 1121042880000027 +705bonus pay for amazing work on #OSS 00010000027 +62223138010481967038518 0000100000#QZCX0FUPKnCAa#Sofia Garcia 1121042880000028 +705bonus pay for amazing work on #OSS 00010000028 +62223138010481967038518 0000100000#QZCX0FUPKnCAa#Sofia Garcia 1121042880000029 +705bonus pay for amazing work on #OSS 00010000029 +62223138010481967038518 0000100000#QZCX0FUPKnCAa#Sofia Garcia 1121042880000030 +705bonus pay for amazing work on #OSS 00010000030 +62223138010481967038518 0000100000#clkNfFCXHhThO#Sofia Harris 1121042880000031 +705bonus pay for amazing work on #OSS 00010000031 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000032 +705bonus pay for amazing work on #OSS 00010000032 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000033 +705bonus pay for amazing work on #OSS 00010000033 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000034 +705bonus pay for amazing work on #OSS 00010000034 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000035 +705bonus pay for amazing work on #OSS 00010000035 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000036 +705bonus pay for amazing work on #OSS 00010000036 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000037 +705bonus pay for amazing work on #OSS 00010000037 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000038 +705bonus pay for amazing work on #OSS 00010000038 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000039 +705bonus pay for amazing work on #OSS 00010000039 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000040 +705bonus pay for amazing work on #OSS 00010000040 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000041 +705bonus pay for amazing work on #OSS 00010000041 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000042 +705bonus pay for amazing work on #OSS 00010000042 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000043 +705bonus pay for amazing work on #OSS 00010000043 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000044 +705bonus pay for amazing work on #OSS 00010000044 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000045 +705bonus pay for amazing work on #OSS 00010000045 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000046 +705bonus pay for amazing work on #OSS 00010000046 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000047 +705bonus pay for amazing work on #OSS 00010000047 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000048 +705bonus pay for amazing work on #OSS 00010000048 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000049 +705bonus pay for amazing work on #OSS 00010000049 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000050 +705bonus pay for amazing work on #OSS 00010000050 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000051 +705bonus pay for amazing work on #OSS 00010000051 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000052 +705bonus pay for amazing work on #OSS 00010000052 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000053 +705bonus pay for amazing work on #OSS 00010000053 +62223138010481967038518 0000100000#clkNfFCXHhThO#Anthony Harris 1121042880000054 +705bonus pay for amazing work on #OSS 00010000054 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Aiden Taylor 1121042880000055 +705bonus pay for amazing work on #OSS 00010000055 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000056 +705bonus pay for amazing work on #OSS 00010000056 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000057 +705bonus pay for amazing work on #OSS 00010000057 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000058 +705bonus pay for amazing work on #OSS 00010000058 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000059 +705bonus pay for amazing work on #OSS 00010000059 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000060 +705bonus pay for amazing work on #OSS 00010000060 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000061 +705bonus pay for amazing work on #OSS 00010000061 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000062 +705bonus pay for amazing work on #OSS 00010000062 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000063 +705bonus pay for amazing work on #OSS 00010000063 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000064 +705bonus pay for amazing work on #OSS 00010000064 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000065 +705bonus pay for amazing work on #OSS 00010000065 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000066 +705bonus pay for amazing work on #OSS 00010000066 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000067 +705bonus pay for amazing work on #OSS 00010000067 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000068 +705bonus pay for amazing work on #OSS 00010000068 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000069 +705bonus pay for amazing work on #OSS 00010000069 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000070 +705bonus pay for amazing work on #OSS 00010000070 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000071 +705bonus pay for amazing work on #OSS 00010000071 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000072 +705bonus pay for amazing work on #OSS 00010000072 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000073 +705bonus pay for amazing work on #OSS 00010000073 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000074 +705bonus pay for amazing work on #OSS 00010000074 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000075 +705bonus pay for amazing work on #OSS 00010000075 +62223138010481967038518 0000100000#OdD4so0Wkv63L#Elizabeth Taylor 1121042880000076 +705bonus pay for amazing work on #OSS 00010000076 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Elizabeth Anderson 1121042880000077 +705bonus pay for amazing work on #OSS 00010000077 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000078 +705bonus pay for amazing work on #OSS 00010000078 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000079 +705bonus pay for amazing work on #OSS 00010000079 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000080 +705bonus pay for amazing work on #OSS 00010000080 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000081 +705bonus pay for amazing work on #OSS 00010000081 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000082 +705bonus pay for amazing work on #OSS 00010000082 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000083 +705bonus pay for amazing work on #OSS 00010000083 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000084 +705bonus pay for amazing work on #OSS 00010000084 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000085 +705bonus pay for amazing work on #OSS 00010000085 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000086 +705bonus pay for amazing work on #OSS 00010000086 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000087 +705bonus pay for amazing work on #OSS 00010000087 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000088 +705bonus pay for amazing work on #OSS 00010000088 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000089 +705bonus pay for amazing work on #OSS 00010000089 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000090 +705bonus pay for amazing work on #OSS 00010000090 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000091 +705bonus pay for amazing work on #OSS 00010000091 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000092 +705bonus pay for amazing work on #OSS 00010000092 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000093 +705bonus pay for amazing work on #OSS 00010000093 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000094 +705bonus pay for amazing work on #OSS 00010000094 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000095 +705bonus pay for amazing work on #OSS 00010000095 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000096 +705bonus pay for amazing work on #OSS 00010000096 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000097 +705bonus pay for amazing work on #OSS 00010000097 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000098 +705bonus pay for amazing work on #OSS 00010000098 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000099 +705bonus pay for amazing work on #OSS 00010000099 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000100 +705bonus pay for amazing work on #OSS 00010000100 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000101 +705bonus pay for amazing work on #OSS 00010000101 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000102 +705bonus pay for amazing work on #OSS 00010000102 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000103 +705bonus pay for amazing work on #OSS 00010000103 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000104 +705bonus pay for amazing work on #OSS 00010000104 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000105 +705bonus pay for amazing work on #OSS 00010000105 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000106 +705bonus pay for amazing work on #OSS 00010000106 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000107 +705bonus pay for amazing work on #OSS 00010000107 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000108 +705bonus pay for amazing work on #OSS 00010000108 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000109 +705bonus pay for amazing work on #OSS 00010000109 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000110 +705bonus pay for amazing work on #OSS 00010000110 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000111 +705bonus pay for amazing work on #OSS 00010000111 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000112 +705bonus pay for amazing work on #OSS 00010000112 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000113 +705bonus pay for amazing work on #OSS 00010000113 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000114 +705bonus pay for amazing work on #OSS 00010000114 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000115 +705bonus pay for amazing work on #OSS 00010000115 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000116 +705bonus pay for amazing work on #OSS 00010000116 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000117 +705bonus pay for amazing work on #OSS 00010000117 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000118 +705bonus pay for amazing work on #OSS 00010000118 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000119 +705bonus pay for amazing work on #OSS 00010000119 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000120 +705bonus pay for amazing work on #OSS 00010000120 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000121 +705bonus pay for amazing work on #OSS 00010000121 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000122 +705bonus pay for amazing work on #OSS 00010000122 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000123 +705bonus pay for amazing work on #OSS 00010000123 +62223138010481967038518 0000100000#aTDFftwcUd6tw#Daniel Anderson 1121042880000124 +705bonus pay for amazing work on #OSS 00010000124 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Daniel Thomas 1121042880000125 +705bonus pay for amazing work on #OSS 00010000125 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000126 +705bonus pay for amazing work on #OSS 00010000126 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000127 +705bonus pay for amazing work on #OSS 00010000127 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000128 +705bonus pay for amazing work on #OSS 00010000128 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000129 +705bonus pay for amazing work on #OSS 00010000129 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000130 +705bonus pay for amazing work on #OSS 00010000130 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000131 +705bonus pay for amazing work on #OSS 00010000131 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000132 +705bonus pay for amazing work on #OSS 00010000132 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000133 +705bonus pay for amazing work on #OSS 00010000133 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000134 +705bonus pay for amazing work on #OSS 00010000134 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000135 +705bonus pay for amazing work on #OSS 00010000135 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000136 +705bonus pay for amazing work on #OSS 00010000136 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000137 +705bonus pay for amazing work on #OSS 00010000137 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000138 +705bonus pay for amazing work on #OSS 00010000138 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000139 +705bonus pay for amazing work on #OSS 00010000139 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000140 +705bonus pay for amazing work on #OSS 00010000140 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000141 +705bonus pay for amazing work on #OSS 00010000141 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000142 +705bonus pay for amazing work on #OSS 00010000142 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000143 +705bonus pay for amazing work on #OSS 00010000143 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000144 +705bonus pay for amazing work on #OSS 00010000144 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000145 +705bonus pay for amazing work on #OSS 00010000145 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000146 +705bonus pay for amazing work on #OSS 00010000146 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000147 +705bonus pay for amazing work on #OSS 00010000147 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000148 +705bonus pay for amazing work on #OSS 00010000148 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000149 +705bonus pay for amazing work on #OSS 00010000149 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000150 +705bonus pay for amazing work on #OSS 00010000150 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000151 +705bonus pay for amazing work on #OSS 00010000151 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000152 +705bonus pay for amazing work on #OSS 00010000152 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000153 +705bonus pay for amazing work on #OSS 00010000153 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000154 +705bonus pay for amazing work on #OSS 00010000154 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000155 +705bonus pay for amazing work on #OSS 00010000155 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000156 +705bonus pay for amazing work on #OSS 00010000156 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000157 +705bonus pay for amazing work on #OSS 00010000157 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000158 +705bonus pay for amazing work on #OSS 00010000158 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000159 +705bonus pay for amazing work on #OSS 00010000159 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000160 +705bonus pay for amazing work on #OSS 00010000160 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000161 +705bonus pay for amazing work on #OSS 00010000161 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000162 +705bonus pay for amazing work on #OSS 00010000162 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000163 +705bonus pay for amazing work on #OSS 00010000163 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000164 +705bonus pay for amazing work on #OSS 00010000164 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000165 +705bonus pay for amazing work on #OSS 00010000165 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000166 +705bonus pay for amazing work on #OSS 00010000166 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000167 +705bonus pay for amazing work on #OSS 00010000167 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000168 +705bonus pay for amazing work on #OSS 00010000168 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000169 +705bonus pay for amazing work on #OSS 00010000169 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000170 +705bonus pay for amazing work on #OSS 00010000170 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000171 +705bonus pay for amazing work on #OSS 00010000171 +62223138010481967038518 0000100000#6sLjFKzW9wlij#Ella Thomas 1121042880000172 +705bonus pay for amazing work on #OSS 00010000172 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000173 +705bonus pay for amazing work on #OSS 00010000173 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000174 +705bonus pay for amazing work on #OSS 00010000174 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000175 +705bonus pay for amazing work on #OSS 00010000175 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000176 +705bonus pay for amazing work on #OSS 00010000176 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000177 +705bonus pay for amazing work on #OSS 00010000177 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000178 +705bonus pay for amazing work on #OSS 00010000178 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000179 +705bonus pay for amazing work on #OSS 00010000179 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000180 +705bonus pay for amazing work on #OSS 00010000180 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000181 +705bonus pay for amazing work on #OSS 00010000181 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000182 +705bonus pay for amazing work on #OSS 00010000182 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000183 +705bonus pay for amazing work on #OSS 00010000183 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000184 +705bonus pay for amazing work on #OSS 00010000184 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000185 +705bonus pay for amazing work on #OSS 00010000185 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000186 +705bonus pay for amazing work on #OSS 00010000186 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000187 +705bonus pay for amazing work on #OSS 00010000187 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000188 +705bonus pay for amazing work on #OSS 00010000188 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000189 +705bonus pay for amazing work on #OSS 00010000189 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000190 +705bonus pay for amazing work on #OSS 00010000190 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000191 +705bonus pay for amazing work on #OSS 00010000191 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000192 +705bonus pay for amazing work on #OSS 00010000192 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000193 +705bonus pay for amazing work on #OSS 00010000193 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000194 +705bonus pay for amazing work on #OSS 00010000194 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000195 +705bonus pay for amazing work on #OSS 00010000195 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000196 +705bonus pay for amazing work on #OSS 00010000196 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000197 +705bonus pay for amazing work on #OSS 00010000197 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000198 +705bonus pay for amazing work on #OSS 00010000198 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000199 +705bonus pay for amazing work on #OSS 00010000199 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000200 +705bonus pay for amazing work on #OSS 00010000200 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000201 +705bonus pay for amazing work on #OSS 00010000201 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000202 +705bonus pay for amazing work on #OSS 00010000202 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000203 +705bonus pay for amazing work on #OSS 00010000203 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000204 +705bonus pay for amazing work on #OSS 00010000204 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000205 +705bonus pay for amazing work on #OSS 00010000205 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000206 +705bonus pay for amazing work on #OSS 00010000206 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000207 +705bonus pay for amazing work on #OSS 00010000207 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000208 +705bonus pay for amazing work on #OSS 00010000208 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000209 +705bonus pay for amazing work on #OSS 00010000209 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000210 +705bonus pay for amazing work on #OSS 00010000210 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000211 +705bonus pay for amazing work on #OSS 00010000211 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000212 +705bonus pay for amazing work on #OSS 00010000212 +62223138010481967038518 0000100000#uk3SngQPJ420i#Elijah Jackson 1121042880000213 +705bonus pay for amazing work on #OSS 00010000213 +62223138010481967038518 0000100000#9fymHdW38k1tP#Elijah Thomas 1121042880000214 +705bonus pay for amazing work on #OSS 00010000214 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000215 +705bonus pay for amazing work on #OSS 00010000215 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000216 +705bonus pay for amazing work on #OSS 00010000216 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000217 +705bonus pay for amazing work on #OSS 00010000217 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000218 +705bonus pay for amazing work on #OSS 00010000218 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000219 +705bonus pay for amazing work on #OSS 00010000219 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000220 +705bonus pay for amazing work on #OSS 00010000220 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000221 +705bonus pay for amazing work on #OSS 00010000221 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000222 +705bonus pay for amazing work on #OSS 00010000222 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000223 +705bonus pay for amazing work on #OSS 00010000223 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000224 +705bonus pay for amazing work on #OSS 00010000224 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000225 +705bonus pay for amazing work on #OSS 00010000225 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000226 +705bonus pay for amazing work on #OSS 00010000226 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000227 +705bonus pay for amazing work on #OSS 00010000227 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000228 +705bonus pay for amazing work on #OSS 00010000228 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000229 +705bonus pay for amazing work on #OSS 00010000229 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000230 +705bonus pay for amazing work on #OSS 00010000230 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000231 +705bonus pay for amazing work on #OSS 00010000231 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000232 +705bonus pay for amazing work on #OSS 00010000232 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000233 +705bonus pay for amazing work on #OSS 00010000233 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000234 +705bonus pay for amazing work on #OSS 00010000234 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000235 +705bonus pay for amazing work on #OSS 00010000235 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000236 +705bonus pay for amazing work on #OSS 00010000236 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000237 +705bonus pay for amazing work on #OSS 00010000237 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000238 +705bonus pay for amazing work on #OSS 00010000238 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000239 +705bonus pay for amazing work on #OSS 00010000239 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000240 +705bonus pay for amazing work on #OSS 00010000240 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000241 +705bonus pay for amazing work on #OSS 00010000241 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000242 +705bonus pay for amazing work on #OSS 00010000242 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000243 +705bonus pay for amazing work on #OSS 00010000243 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000244 +705bonus pay for amazing work on #OSS 00010000244 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000245 +705bonus pay for amazing work on #OSS 00010000245 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000246 +705bonus pay for amazing work on #OSS 00010000246 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000247 +705bonus pay for amazing work on #OSS 00010000247 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000248 +705bonus pay for amazing work on #OSS 00010000248 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000249 +705bonus pay for amazing work on #OSS 00010000249 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000250 +705bonus pay for amazing work on #OSS 00010000250 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000251 +705bonus pay for amazing work on #OSS 00010000251 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000252 +705bonus pay for amazing work on #OSS 00010000252 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000253 +705bonus pay for amazing work on #OSS 00010000253 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000254 +705bonus pay for amazing work on #OSS 00010000254 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000255 +705bonus pay for amazing work on #OSS 00010000255 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000256 +705bonus pay for amazing work on #OSS 00010000256 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000257 +705bonus pay for amazing work on #OSS 00010000257 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000258 +705bonus pay for amazing work on #OSS 00010000258 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000259 +705bonus pay for amazing work on #OSS 00010000259 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000260 +705bonus pay for amazing work on #OSS 00010000260 +62223138010481967038518 0000100000#9fymHdW38k1tP#Ella Thomas 1121042880000261 +705bonus pay for amazing work on #OSS 00010000261 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000262 +705bonus pay for amazing work on #OSS 00010000262 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000263 +705bonus pay for amazing work on #OSS 00010000263 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000264 +705bonus pay for amazing work on #OSS 00010000264 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000265 +705bonus pay for amazing work on #OSS 00010000265 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000266 +705bonus pay for amazing work on #OSS 00010000266 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000267 +705bonus pay for amazing work on #OSS 00010000267 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000268 +705bonus pay for amazing work on #OSS 00010000268 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000269 +705bonus pay for amazing work on #OSS 00010000269 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000270 +705bonus pay for amazing work on #OSS 00010000270 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000271 +705bonus pay for amazing work on #OSS 00010000271 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000272 +705bonus pay for amazing work on #OSS 00010000272 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000273 +705bonus pay for amazing work on #OSS 00010000273 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000274 +705bonus pay for amazing work on #OSS 00010000274 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000275 +705bonus pay for amazing work on #OSS 00010000275 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000276 +705bonus pay for amazing work on #OSS 00010000276 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000277 +705bonus pay for amazing work on #OSS 00010000277 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000278 +705bonus pay for amazing work on #OSS 00010000278 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000279 +705bonus pay for amazing work on #OSS 00010000279 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000280 +705bonus pay for amazing work on #OSS 00010000280 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000281 +705bonus pay for amazing work on #OSS 00010000281 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000282 +705bonus pay for amazing work on #OSS 00010000282 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000283 +705bonus pay for amazing work on #OSS 00010000283 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000284 +705bonus pay for amazing work on #OSS 00010000284 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000285 +705bonus pay for amazing work on #OSS 00010000285 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000286 +705bonus pay for amazing work on #OSS 00010000286 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000287 +705bonus pay for amazing work on #OSS 00010000287 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000288 +705bonus pay for amazing work on #OSS 00010000288 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000289 +705bonus pay for amazing work on #OSS 00010000289 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000290 +705bonus pay for amazing work on #OSS 00010000290 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000291 +705bonus pay for amazing work on #OSS 00010000291 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000292 +705bonus pay for amazing work on #OSS 00010000292 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000293 +705bonus pay for amazing work on #OSS 00010000293 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000294 +705bonus pay for amazing work on #OSS 00010000294 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000295 +705bonus pay for amazing work on #OSS 00010000295 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000296 +705bonus pay for amazing work on #OSS 00010000296 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000297 +705bonus pay for amazing work on #OSS 00010000297 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000298 +705bonus pay for amazing work on #OSS 00010000298 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000299 +705bonus pay for amazing work on #OSS 00010000299 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000300 +705bonus pay for amazing work on #OSS 00010000300 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000301 +705bonus pay for amazing work on #OSS 00010000301 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000302 +705bonus pay for amazing work on #OSS 00010000302 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000303 +705bonus pay for amazing work on #OSS 00010000303 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000304 +705bonus pay for amazing work on #OSS 00010000304 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000305 +705bonus pay for amazing work on #OSS 00010000305 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000306 +705bonus pay for amazing work on #OSS 00010000306 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000307 +705bonus pay for amazing work on #OSS 00010000307 +62223138010481967038518 0000100000#cMg8ZWs4kjclh#Emma Johnson 1121042880000308 +705bonus pay for amazing work on #OSS 00010000308 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000309 +705bonus pay for amazing work on #OSS 00010000309 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000310 +705bonus pay for amazing work on #OSS 00010000310 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000311 +705bonus pay for amazing work on #OSS 00010000311 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000312 +705bonus pay for amazing work on #OSS 00010000312 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000313 +705bonus pay for amazing work on #OSS 00010000313 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000314 +705bonus pay for amazing work on #OSS 00010000314 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000315 +705bonus pay for amazing work on #OSS 00010000315 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000316 +705bonus pay for amazing work on #OSS 00010000316 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000317 +705bonus pay for amazing work on #OSS 00010000317 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000318 +705bonus pay for amazing work on #OSS 00010000318 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000319 +705bonus pay for amazing work on #OSS 00010000319 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000320 +705bonus pay for amazing work on #OSS 00010000320 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000321 +705bonus pay for amazing work on #OSS 00010000321 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000322 +705bonus pay for amazing work on #OSS 00010000322 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000323 +705bonus pay for amazing work on #OSS 00010000323 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000324 +705bonus pay for amazing work on #OSS 00010000324 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000325 +705bonus pay for amazing work on #OSS 00010000325 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000326 +705bonus pay for amazing work on #OSS 00010000326 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000327 +705bonus pay for amazing work on #OSS 00010000327 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000328 +705bonus pay for amazing work on #OSS 00010000328 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000329 +705bonus pay for amazing work on #OSS 00010000329 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000330 +705bonus pay for amazing work on #OSS 00010000330 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000331 +705bonus pay for amazing work on #OSS 00010000331 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000332 +705bonus pay for amazing work on #OSS 00010000332 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000333 +705bonus pay for amazing work on #OSS 00010000333 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000334 +705bonus pay for amazing work on #OSS 00010000334 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000335 +705bonus pay for amazing work on #OSS 00010000335 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000336 +705bonus pay for amazing work on #OSS 00010000336 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000337 +705bonus pay for amazing work on #OSS 00010000337 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000338 +705bonus pay for amazing work on #OSS 00010000338 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000339 +705bonus pay for amazing work on #OSS 00010000339 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000340 +705bonus pay for amazing work on #OSS 00010000340 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000341 +705bonus pay for amazing work on #OSS 00010000341 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000342 +705bonus pay for amazing work on #OSS 00010000342 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000343 +705bonus pay for amazing work on #OSS 00010000343 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000344 +705bonus pay for amazing work on #OSS 00010000344 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000345 +705bonus pay for amazing work on #OSS 00010000345 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000346 +705bonus pay for amazing work on #OSS 00010000346 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000347 +705bonus pay for amazing work on #OSS 00010000347 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000348 +705bonus pay for amazing work on #OSS 00010000348 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000349 +705bonus pay for amazing work on #OSS 00010000349 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000350 +705bonus pay for amazing work on #OSS 00010000350 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000351 +705bonus pay for amazing work on #OSS 00010000351 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000352 +705bonus pay for amazing work on #OSS 00010000352 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000353 +705bonus pay for amazing work on #OSS 00010000353 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000354 +705bonus pay for amazing work on #OSS 00010000354 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000355 +705bonus pay for amazing work on #OSS 00010000355 +62223138010481967038518 0000100000#kbSjaIDNtkEZC#Emily Davis 1121042880000356 +705bonus pay for amazing work on #OSS 00010000356 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000357 +705bonus pay for amazing work on #OSS 00010000357 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000358 +705bonus pay for amazing work on #OSS 00010000358 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000359 +705bonus pay for amazing work on #OSS 00010000359 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000360 +705bonus pay for amazing work on #OSS 00010000360 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000361 +705bonus pay for amazing work on #OSS 00010000361 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000362 +705bonus pay for amazing work on #OSS 00010000362 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000363 +705bonus pay for amazing work on #OSS 00010000363 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000364 +705bonus pay for amazing work on #OSS 00010000364 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000365 +705bonus pay for amazing work on #OSS 00010000365 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000366 +705bonus pay for amazing work on #OSS 00010000366 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000367 +705bonus pay for amazing work on #OSS 00010000367 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000368 +705bonus pay for amazing work on #OSS 00010000368 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000369 +705bonus pay for amazing work on #OSS 00010000369 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000370 +705bonus pay for amazing work on #OSS 00010000370 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000371 +705bonus pay for amazing work on #OSS 00010000371 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000372 +705bonus pay for amazing work on #OSS 00010000372 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000373 +705bonus pay for amazing work on #OSS 00010000373 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000374 +705bonus pay for amazing work on #OSS 00010000374 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000375 +705bonus pay for amazing work on #OSS 00010000375 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000376 +705bonus pay for amazing work on #OSS 00010000376 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000377 +705bonus pay for amazing work on #OSS 00010000377 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000378 +705bonus pay for amazing work on #OSS 00010000378 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000379 +705bonus pay for amazing work on #OSS 00010000379 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000380 +705bonus pay for amazing work on #OSS 00010000380 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000381 +705bonus pay for amazing work on #OSS 00010000381 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000382 +705bonus pay for amazing work on #OSS 00010000382 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000383 +705bonus pay for amazing work on #OSS 00010000383 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000384 +705bonus pay for amazing work on #OSS 00010000384 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000385 +705bonus pay for amazing work on #OSS 00010000385 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000386 +705bonus pay for amazing work on #OSS 00010000386 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000387 +705bonus pay for amazing work on #OSS 00010000387 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000388 +705bonus pay for amazing work on #OSS 00010000388 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000389 +705bonus pay for amazing work on #OSS 00010000389 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000390 +705bonus pay for amazing work on #OSS 00010000390 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000391 +705bonus pay for amazing work on #OSS 00010000391 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000392 +705bonus pay for amazing work on #OSS 00010000392 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000393 +705bonus pay for amazing work on #OSS 00010000393 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000394 +705bonus pay for amazing work on #OSS 00010000394 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000395 +705bonus pay for amazing work on #OSS 00010000395 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000396 +705bonus pay for amazing work on #OSS 00010000396 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000397 +705bonus pay for amazing work on #OSS 00010000397 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000398 +705bonus pay for amazing work on #OSS 00010000398 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000399 +705bonus pay for amazing work on #OSS 00010000399 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000400 +705bonus pay for amazing work on #OSS 00010000400 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000401 +705bonus pay for amazing work on #OSS 00010000401 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000402 +705bonus pay for amazing work on #OSS 00010000402 +62223138010481967038518 0000100000#U0IpLWUXjSEZ1#Emma Johnson 1121042880000403 +705bonus pay for amazing work on #OSS 00010000403 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000404 +705bonus pay for amazing work on #OSS 00010000404 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000405 +705bonus pay for amazing work on #OSS 00010000405 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000406 +705bonus pay for amazing work on #OSS 00010000406 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000407 +705bonus pay for amazing work on #OSS 00010000407 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000408 +705bonus pay for amazing work on #OSS 00010000408 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000409 +705bonus pay for amazing work on #OSS 00010000409 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000410 +705bonus pay for amazing work on #OSS 00010000410 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000411 +705bonus pay for amazing work on #OSS 00010000411 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000412 +705bonus pay for amazing work on #OSS 00010000412 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000413 +705bonus pay for amazing work on #OSS 00010000413 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000414 +705bonus pay for amazing work on #OSS 00010000414 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000415 +705bonus pay for amazing work on #OSS 00010000415 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000416 +705bonus pay for amazing work on #OSS 00010000416 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000417 +705bonus pay for amazing work on #OSS 00010000417 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000418 +705bonus pay for amazing work on #OSS 00010000418 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000419 +705bonus pay for amazing work on #OSS 00010000419 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000420 +705bonus pay for amazing work on #OSS 00010000420 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000421 +705bonus pay for amazing work on #OSS 00010000421 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000422 +705bonus pay for amazing work on #OSS 00010000422 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000423 +705bonus pay for amazing work on #OSS 00010000423 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000424 +705bonus pay for amazing work on #OSS 00010000424 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000425 +705bonus pay for amazing work on #OSS 00010000425 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000426 +705bonus pay for amazing work on #OSS 00010000426 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000427 +705bonus pay for amazing work on #OSS 00010000427 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000428 +705bonus pay for amazing work on #OSS 00010000428 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000429 +705bonus pay for amazing work on #OSS 00010000429 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000430 +705bonus pay for amazing work on #OSS 00010000430 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000431 +705bonus pay for amazing work on #OSS 00010000431 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000432 +705bonus pay for amazing work on #OSS 00010000432 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000433 +705bonus pay for amazing work on #OSS 00010000433 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000434 +705bonus pay for amazing work on #OSS 00010000434 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000435 +705bonus pay for amazing work on #OSS 00010000435 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000436 +705bonus pay for amazing work on #OSS 00010000436 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000437 +705bonus pay for amazing work on #OSS 00010000437 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000438 +705bonus pay for amazing work on #OSS 00010000438 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000439 +705bonus pay for amazing work on #OSS 00010000439 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000440 +705bonus pay for amazing work on #OSS 00010000440 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000441 +705bonus pay for amazing work on #OSS 00010000441 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000442 +705bonus pay for amazing work on #OSS 00010000442 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000443 +705bonus pay for amazing work on #OSS 00010000443 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000444 +705bonus pay for amazing work on #OSS 00010000444 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000445 +705bonus pay for amazing work on #OSS 00010000445 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000446 +705bonus pay for amazing work on #OSS 00010000446 +62223138010481967038518 0000100000#cvMYzs72MOl9M#Emma Johnson 1121042880000447 +705bonus pay for amazing work on #OSS 00010000447 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Emma Jackson 1121042880000448 +705bonus pay for amazing work on #OSS 00010000448 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000449 +705bonus pay for amazing work on #OSS 00010000449 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000450 +705bonus pay for amazing work on #OSS 00010000450 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000451 +705bonus pay for amazing work on #OSS 00010000451 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000452 +705bonus pay for amazing work on #OSS 00010000452 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000453 +705bonus pay for amazing work on #OSS 00010000453 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000454 +705bonus pay for amazing work on #OSS 00010000454 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000455 +705bonus pay for amazing work on #OSS 00010000455 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000456 +705bonus pay for amazing work on #OSS 00010000456 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000457 +705bonus pay for amazing work on #OSS 00010000457 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000458 +705bonus pay for amazing work on #OSS 00010000458 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000459 +705bonus pay for amazing work on #OSS 00010000459 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000460 +705bonus pay for amazing work on #OSS 00010000460 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000461 +705bonus pay for amazing work on #OSS 00010000461 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000462 +705bonus pay for amazing work on #OSS 00010000462 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000463 +705bonus pay for amazing work on #OSS 00010000463 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000464 +705bonus pay for amazing work on #OSS 00010000464 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000465 +705bonus pay for amazing work on #OSS 00010000465 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000466 +705bonus pay for amazing work on #OSS 00010000466 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000467 +705bonus pay for amazing work on #OSS 00010000467 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000468 +705bonus pay for amazing work on #OSS 00010000468 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000469 +705bonus pay for amazing work on #OSS 00010000469 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000470 +705bonus pay for amazing work on #OSS 00010000470 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000471 +705bonus pay for amazing work on #OSS 00010000471 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000472 +705bonus pay for amazing work on #OSS 00010000472 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000473 +705bonus pay for amazing work on #OSS 00010000473 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000474 +705bonus pay for amazing work on #OSS 00010000474 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000475 +705bonus pay for amazing work on #OSS 00010000475 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000476 +705bonus pay for amazing work on #OSS 00010000476 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000477 +705bonus pay for amazing work on #OSS 00010000477 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000478 +705bonus pay for amazing work on #OSS 00010000478 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000479 +705bonus pay for amazing work on #OSS 00010000479 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000480 +705bonus pay for amazing work on #OSS 00010000480 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000481 +705bonus pay for amazing work on #OSS 00010000481 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000482 +705bonus pay for amazing work on #OSS 00010000482 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000483 +705bonus pay for amazing work on #OSS 00010000483 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000484 +705bonus pay for amazing work on #OSS 00010000484 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000485 +705bonus pay for amazing work on #OSS 00010000485 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000486 +705bonus pay for amazing work on #OSS 00010000486 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000487 +705bonus pay for amazing work on #OSS 00010000487 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000488 +705bonus pay for amazing work on #OSS 00010000488 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000489 +705bonus pay for amazing work on #OSS 00010000489 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000490 +705bonus pay for amazing work on #OSS 00010000490 +62223138010481967038518 0000100000#skuhX7AXMmYb8#Elijah Jackson 1121042880000491 +705bonus pay for amazing work on #OSS 00010000491 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#Elijah Brown 1121042880000492 +705bonus pay for amazing work on #OSS 00010000492 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000493 +705bonus pay for amazing work on #OSS 00010000493 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000494 +705bonus pay for amazing work on #OSS 00010000494 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000495 +705bonus pay for amazing work on #OSS 00010000495 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000496 +705bonus pay for amazing work on #OSS 00010000496 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000497 +705bonus pay for amazing work on #OSS 00010000497 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000498 +705bonus pay for amazing work on #OSS 00010000498 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000499 +705bonus pay for amazing work on #OSS 00010000499 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000500 +705bonus pay for amazing work on #OSS 00010000500 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000501 +705bonus pay for amazing work on #OSS 00010000501 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000502 +705bonus pay for amazing work on #OSS 00010000502 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000503 +705bonus pay for amazing work on #OSS 00010000503 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000504 +705bonus pay for amazing work on #OSS 00010000504 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000505 +705bonus pay for amazing work on #OSS 00010000505 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000506 +705bonus pay for amazing work on #OSS 00010000506 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000507 +705bonus pay for amazing work on #OSS 00010000507 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000508 +705bonus pay for amazing work on #OSS 00010000508 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000509 +705bonus pay for amazing work on #OSS 00010000509 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000510 +705bonus pay for amazing work on #OSS 00010000510 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000511 +705bonus pay for amazing work on #OSS 00010000511 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000512 +705bonus pay for amazing work on #OSS 00010000512 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000513 +705bonus pay for amazing work on #OSS 00010000513 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000514 +705bonus pay for amazing work on #OSS 00010000514 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000515 +705bonus pay for amazing work on #OSS 00010000515 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000516 +705bonus pay for amazing work on #OSS 00010000516 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000517 +705bonus pay for amazing work on #OSS 00010000517 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000518 +705bonus pay for amazing work on #OSS 00010000518 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000519 +705bonus pay for amazing work on #OSS 00010000519 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000520 +705bonus pay for amazing work on #OSS 00010000520 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000521 +705bonus pay for amazing work on #OSS 00010000521 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000522 +705bonus pay for amazing work on #OSS 00010000522 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000523 +705bonus pay for amazing work on #OSS 00010000523 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000524 +705bonus pay for amazing work on #OSS 00010000524 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000525 +705bonus pay for amazing work on #OSS 00010000525 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000526 +705bonus pay for amazing work on #OSS 00010000526 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000527 +705bonus pay for amazing work on #OSS 00010000527 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000528 +705bonus pay for amazing work on #OSS 00010000528 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000529 +705bonus pay for amazing work on #OSS 00010000529 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000530 +705bonus pay for amazing work on #OSS 00010000530 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000531 +705bonus pay for amazing work on #OSS 00010000531 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000532 +705bonus pay for amazing work on #OSS 00010000532 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000533 +705bonus pay for amazing work on #OSS 00010000533 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000534 +705bonus pay for amazing work on #OSS 00010000534 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000535 +705bonus pay for amazing work on #OSS 00010000535 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000536 +705bonus pay for amazing work on #OSS 00010000536 +62223138010481967038518 0000100000#b8pIyAGCdVzNj#William Brown 1121042880000537 +705bonus pay for amazing work on #OSS 00010000537 +62223138010481967038518 0000100000#e6lK8hokrBoGK#William Smith 1121042880000538 +705bonus pay for amazing work on #OSS 00010000538 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000539 +705bonus pay for amazing work on #OSS 00010000539 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000540 +705bonus pay for amazing work on #OSS 00010000540 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000541 +705bonus pay for amazing work on #OSS 00010000541 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000542 +705bonus pay for amazing work on #OSS 00010000542 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000543 +705bonus pay for amazing work on #OSS 00010000543 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000544 +705bonus pay for amazing work on #OSS 00010000544 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000545 +705bonus pay for amazing work on #OSS 00010000545 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000546 +705bonus pay for amazing work on #OSS 00010000546 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000547 +705bonus pay for amazing work on #OSS 00010000547 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000548 +705bonus pay for amazing work on #OSS 00010000548 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000549 +705bonus pay for amazing work on #OSS 00010000549 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000550 +705bonus pay for amazing work on #OSS 00010000550 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000551 +705bonus pay for amazing work on #OSS 00010000551 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000552 +705bonus pay for amazing work on #OSS 00010000552 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000553 +705bonus pay for amazing work on #OSS 00010000553 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000554 +705bonus pay for amazing work on #OSS 00010000554 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000555 +705bonus pay for amazing work on #OSS 00010000555 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000556 +705bonus pay for amazing work on #OSS 00010000556 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000557 +705bonus pay for amazing work on #OSS 00010000557 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000558 +705bonus pay for amazing work on #OSS 00010000558 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000559 +705bonus pay for amazing work on #OSS 00010000559 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000560 +705bonus pay for amazing work on #OSS 00010000560 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000561 +705bonus pay for amazing work on #OSS 00010000561 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000562 +705bonus pay for amazing work on #OSS 00010000562 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000563 +705bonus pay for amazing work on #OSS 00010000563 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000564 +705bonus pay for amazing work on #OSS 00010000564 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000565 +705bonus pay for amazing work on #OSS 00010000565 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000566 +705bonus pay for amazing work on #OSS 00010000566 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000567 +705bonus pay for amazing work on #OSS 00010000567 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000568 +705bonus pay for amazing work on #OSS 00010000568 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000569 +705bonus pay for amazing work on #OSS 00010000569 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000570 +705bonus pay for amazing work on #OSS 00010000570 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000571 +705bonus pay for amazing work on #OSS 00010000571 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000572 +705bonus pay for amazing work on #OSS 00010000572 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000573 +705bonus pay for amazing work on #OSS 00010000573 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000574 +705bonus pay for amazing work on #OSS 00010000574 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000575 +705bonus pay for amazing work on #OSS 00010000575 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000576 +705bonus pay for amazing work on #OSS 00010000576 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000577 +705bonus pay for amazing work on #OSS 00010000577 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000578 +705bonus pay for amazing work on #OSS 00010000578 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000579 +705bonus pay for amazing work on #OSS 00010000579 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000580 +705bonus pay for amazing work on #OSS 00010000580 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000581 +705bonus pay for amazing work on #OSS 00010000581 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000582 +705bonus pay for amazing work on #OSS 00010000582 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000583 +705bonus pay for amazing work on #OSS 00010000583 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000584 +705bonus pay for amazing work on #OSS 00010000584 +62223138010481967038518 0000100000#e6lK8hokrBoGK#Jacob Smith 1121042880000585 +705bonus pay for amazing work on #OSS 00010000585 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Benjamin Martin 1121042880000586 +705bonus pay for amazing work on #OSS 00010000586 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000587 +705bonus pay for amazing work on #OSS 00010000587 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000588 +705bonus pay for amazing work on #OSS 00010000588 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000589 +705bonus pay for amazing work on #OSS 00010000589 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000590 +705bonus pay for amazing work on #OSS 00010000590 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000591 +705bonus pay for amazing work on #OSS 00010000591 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000592 +705bonus pay for amazing work on #OSS 00010000592 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000593 +705bonus pay for amazing work on #OSS 00010000593 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000594 +705bonus pay for amazing work on #OSS 00010000594 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000595 +705bonus pay for amazing work on #OSS 00010000595 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000596 +705bonus pay for amazing work on #OSS 00010000596 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000597 +705bonus pay for amazing work on #OSS 00010000597 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000598 +705bonus pay for amazing work on #OSS 00010000598 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000599 +705bonus pay for amazing work on #OSS 00010000599 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000600 +705bonus pay for amazing work on #OSS 00010000600 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000601 +705bonus pay for amazing work on #OSS 00010000601 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000602 +705bonus pay for amazing work on #OSS 00010000602 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000603 +705bonus pay for amazing work on #OSS 00010000603 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000604 +705bonus pay for amazing work on #OSS 00010000604 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000605 +705bonus pay for amazing work on #OSS 00010000605 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000606 +705bonus pay for amazing work on #OSS 00010000606 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000607 +705bonus pay for amazing work on #OSS 00010000607 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000608 +705bonus pay for amazing work on #OSS 00010000608 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000609 +705bonus pay for amazing work on #OSS 00010000609 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000610 +705bonus pay for amazing work on #OSS 00010000610 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000611 +705bonus pay for amazing work on #OSS 00010000611 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000612 +705bonus pay for amazing work on #OSS 00010000612 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000613 +705bonus pay for amazing work on #OSS 00010000613 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000614 +705bonus pay for amazing work on #OSS 00010000614 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000615 +705bonus pay for amazing work on #OSS 00010000615 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000616 +705bonus pay for amazing work on #OSS 00010000616 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000617 +705bonus pay for amazing work on #OSS 00010000617 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000618 +705bonus pay for amazing work on #OSS 00010000618 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000619 +705bonus pay for amazing work on #OSS 00010000619 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000620 +705bonus pay for amazing work on #OSS 00010000620 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000621 +705bonus pay for amazing work on #OSS 00010000621 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000622 +705bonus pay for amazing work on #OSS 00010000622 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000623 +705bonus pay for amazing work on #OSS 00010000623 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000624 +705bonus pay for amazing work on #OSS 00010000624 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000625 +705bonus pay for amazing work on #OSS 00010000625 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000626 +705bonus pay for amazing work on #OSS 00010000626 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000627 +705bonus pay for amazing work on #OSS 00010000627 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000628 +705bonus pay for amazing work on #OSS 00010000628 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000629 +705bonus pay for amazing work on #OSS 00010000629 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000630 +705bonus pay for amazing work on #OSS 00010000630 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000631 +705bonus pay for amazing work on #OSS 00010000631 +62223138010481967038518 0000100000#9CML5ISKlX9JO#Lily Martin 1121042880000632 +705bonus pay for amazing work on #OSS 00010000632 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Lily Jones 1121042880000633 +705bonus pay for amazing work on #OSS 00010000633 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000634 +705bonus pay for amazing work on #OSS 00010000634 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000635 +705bonus pay for amazing work on #OSS 00010000635 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000636 +705bonus pay for amazing work on #OSS 00010000636 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000637 +705bonus pay for amazing work on #OSS 00010000637 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000638 +705bonus pay for amazing work on #OSS 00010000638 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000639 +705bonus pay for amazing work on #OSS 00010000639 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000640 +705bonus pay for amazing work on #OSS 00010000640 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000641 +705bonus pay for amazing work on #OSS 00010000641 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000642 +705bonus pay for amazing work on #OSS 00010000642 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000643 +705bonus pay for amazing work on #OSS 00010000643 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000644 +705bonus pay for amazing work on #OSS 00010000644 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000645 +705bonus pay for amazing work on #OSS 00010000645 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000646 +705bonus pay for amazing work on #OSS 00010000646 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000647 +705bonus pay for amazing work on #OSS 00010000647 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000648 +705bonus pay for amazing work on #OSS 00010000648 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000649 +705bonus pay for amazing work on #OSS 00010000649 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000650 +705bonus pay for amazing work on #OSS 00010000650 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000651 +705bonus pay for amazing work on #OSS 00010000651 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000652 +705bonus pay for amazing work on #OSS 00010000652 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000653 +705bonus pay for amazing work on #OSS 00010000653 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000654 +705bonus pay for amazing work on #OSS 00010000654 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000655 +705bonus pay for amazing work on #OSS 00010000655 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000656 +705bonus pay for amazing work on #OSS 00010000656 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000657 +705bonus pay for amazing work on #OSS 00010000657 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000658 +705bonus pay for amazing work on #OSS 00010000658 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000659 +705bonus pay for amazing work on #OSS 00010000659 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000660 +705bonus pay for amazing work on #OSS 00010000660 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000661 +705bonus pay for amazing work on #OSS 00010000661 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000662 +705bonus pay for amazing work on #OSS 00010000662 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000663 +705bonus pay for amazing work on #OSS 00010000663 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000664 +705bonus pay for amazing work on #OSS 00010000664 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000665 +705bonus pay for amazing work on #OSS 00010000665 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000666 +705bonus pay for amazing work on #OSS 00010000666 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000667 +705bonus pay for amazing work on #OSS 00010000667 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000668 +705bonus pay for amazing work on #OSS 00010000668 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000669 +705bonus pay for amazing work on #OSS 00010000669 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000670 +705bonus pay for amazing work on #OSS 00010000670 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000671 +705bonus pay for amazing work on #OSS 00010000671 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000672 +705bonus pay for amazing work on #OSS 00010000672 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000673 +705bonus pay for amazing work on #OSS 00010000673 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000674 +705bonus pay for amazing work on #OSS 00010000674 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000675 +705bonus pay for amazing work on #OSS 00010000675 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000676 +705bonus pay for amazing work on #OSS 00010000676 +62223138010481967038518 0000100000#3zYaj10ufgNOm#Olivia Jones 1121042880000677 +705bonus pay for amazing work on #OSS 00010000677 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000678 +705bonus pay for amazing work on #OSS 00010000678 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000679 +705bonus pay for amazing work on #OSS 00010000679 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000680 +705bonus pay for amazing work on #OSS 00010000680 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000681 +705bonus pay for amazing work on #OSS 00010000681 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000682 +705bonus pay for amazing work on #OSS 00010000682 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000683 +705bonus pay for amazing work on #OSS 00010000683 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000684 +705bonus pay for amazing work on #OSS 00010000684 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000685 +705bonus pay for amazing work on #OSS 00010000685 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000686 +705bonus pay for amazing work on #OSS 00010000686 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000687 +705bonus pay for amazing work on #OSS 00010000687 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000688 +705bonus pay for amazing work on #OSS 00010000688 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000689 +705bonus pay for amazing work on #OSS 00010000689 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000690 +705bonus pay for amazing work on #OSS 00010000690 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000691 +705bonus pay for amazing work on #OSS 00010000691 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000692 +705bonus pay for amazing work on #OSS 00010000692 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000693 +705bonus pay for amazing work on #OSS 00010000693 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000694 +705bonus pay for amazing work on #OSS 00010000694 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000695 +705bonus pay for amazing work on #OSS 00010000695 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000696 +705bonus pay for amazing work on #OSS 00010000696 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000697 +705bonus pay for amazing work on #OSS 00010000697 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000698 +705bonus pay for amazing work on #OSS 00010000698 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000699 +705bonus pay for amazing work on #OSS 00010000699 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000700 +705bonus pay for amazing work on #OSS 00010000700 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000701 +705bonus pay for amazing work on #OSS 00010000701 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000702 +705bonus pay for amazing work on #OSS 00010000702 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000703 +705bonus pay for amazing work on #OSS 00010000703 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000704 +705bonus pay for amazing work on #OSS 00010000704 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000705 +705bonus pay for amazing work on #OSS 00010000705 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000706 +705bonus pay for amazing work on #OSS 00010000706 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000707 +705bonus pay for amazing work on #OSS 00010000707 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000708 +705bonus pay for amazing work on #OSS 00010000708 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000709 +705bonus pay for amazing work on #OSS 00010000709 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000710 +705bonus pay for amazing work on #OSS 00010000710 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000711 +705bonus pay for amazing work on #OSS 00010000711 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000712 +705bonus pay for amazing work on #OSS 00010000712 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000713 +705bonus pay for amazing work on #OSS 00010000713 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000714 +705bonus pay for amazing work on #OSS 00010000714 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000715 +705bonus pay for amazing work on #OSS 00010000715 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000716 +705bonus pay for amazing work on #OSS 00010000716 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000717 +705bonus pay for amazing work on #OSS 00010000717 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000718 +705bonus pay for amazing work on #OSS 00010000718 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000719 +705bonus pay for amazing work on #OSS 00010000719 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000720 +705bonus pay for amazing work on #OSS 00010000720 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000721 +705bonus pay for amazing work on #OSS 00010000721 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000722 +705bonus pay for amazing work on #OSS 00010000722 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000723 +705bonus pay for amazing work on #OSS 00010000723 +62223138010481967038518 0000100000#h7caX3Uo7w5O5#Zoey Robinson 1121042880000724 +705bonus pay for amazing work on #OSS 00010000724 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#Zoey Brown 1121042880000725 +705bonus pay for amazing work on #OSS 00010000725 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000726 +705bonus pay for amazing work on #OSS 00010000726 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000727 +705bonus pay for amazing work on #OSS 00010000727 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000728 +705bonus pay for amazing work on #OSS 00010000728 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000729 +705bonus pay for amazing work on #OSS 00010000729 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000730 +705bonus pay for amazing work on #OSS 00010000730 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000731 +705bonus pay for amazing work on #OSS 00010000731 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000732 +705bonus pay for amazing work on #OSS 00010000732 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000733 +705bonus pay for amazing work on #OSS 00010000733 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000734 +705bonus pay for amazing work on #OSS 00010000734 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000735 +705bonus pay for amazing work on #OSS 00010000735 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000736 +705bonus pay for amazing work on #OSS 00010000736 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000737 +705bonus pay for amazing work on #OSS 00010000737 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000738 +705bonus pay for amazing work on #OSS 00010000738 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000739 +705bonus pay for amazing work on #OSS 00010000739 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000740 +705bonus pay for amazing work on #OSS 00010000740 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000741 +705bonus pay for amazing work on #OSS 00010000741 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000742 +705bonus pay for amazing work on #OSS 00010000742 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000743 +705bonus pay for amazing work on #OSS 00010000743 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000744 +705bonus pay for amazing work on #OSS 00010000744 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000745 +705bonus pay for amazing work on #OSS 00010000745 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000746 +705bonus pay for amazing work on #OSS 00010000746 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000747 +705bonus pay for amazing work on #OSS 00010000747 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000748 +705bonus pay for amazing work on #OSS 00010000748 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000749 +705bonus pay for amazing work on #OSS 00010000749 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000750 +705bonus pay for amazing work on #OSS 00010000750 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000751 +705bonus pay for amazing work on #OSS 00010000751 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000752 +705bonus pay for amazing work on #OSS 00010000752 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000753 +705bonus pay for amazing work on #OSS 00010000753 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000754 +705bonus pay for amazing work on #OSS 00010000754 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000755 +705bonus pay for amazing work on #OSS 00010000755 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000756 +705bonus pay for amazing work on #OSS 00010000756 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000757 +705bonus pay for amazing work on #OSS 00010000757 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000758 +705bonus pay for amazing work on #OSS 00010000758 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000759 +705bonus pay for amazing work on #OSS 00010000759 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000760 +705bonus pay for amazing work on #OSS 00010000760 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000761 +705bonus pay for amazing work on #OSS 00010000761 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000762 +705bonus pay for amazing work on #OSS 00010000762 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000763 +705bonus pay for amazing work on #OSS 00010000763 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000764 +705bonus pay for amazing work on #OSS 00010000764 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000765 +705bonus pay for amazing work on #OSS 00010000765 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000766 +705bonus pay for amazing work on #OSS 00010000766 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000767 +705bonus pay for amazing work on #OSS 00010000767 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000768 +705bonus pay for amazing work on #OSS 00010000768 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000769 +705bonus pay for amazing work on #OSS 00010000769 +62223138010481967038518 0000100000#9mcEFVyZNBqiG#William Brown 1121042880000770 +705bonus pay for amazing work on #OSS 00010000770 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#William Harris 1121042880000771 +705bonus pay for amazing work on #OSS 00010000771 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000772 +705bonus pay for amazing work on #OSS 00010000772 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000773 +705bonus pay for amazing work on #OSS 00010000773 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000774 +705bonus pay for amazing work on #OSS 00010000774 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000775 +705bonus pay for amazing work on #OSS 00010000775 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000776 +705bonus pay for amazing work on #OSS 00010000776 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000777 +705bonus pay for amazing work on #OSS 00010000777 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000778 +705bonus pay for amazing work on #OSS 00010000778 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000779 +705bonus pay for amazing work on #OSS 00010000779 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000780 +705bonus pay for amazing work on #OSS 00010000780 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000781 +705bonus pay for amazing work on #OSS 00010000781 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000782 +705bonus pay for amazing work on #OSS 00010000782 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000783 +705bonus pay for amazing work on #OSS 00010000783 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000784 +705bonus pay for amazing work on #OSS 00010000784 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000785 +705bonus pay for amazing work on #OSS 00010000785 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000786 +705bonus pay for amazing work on #OSS 00010000786 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000787 +705bonus pay for amazing work on #OSS 00010000787 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000788 +705bonus pay for amazing work on #OSS 00010000788 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000789 +705bonus pay for amazing work on #OSS 00010000789 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000790 +705bonus pay for amazing work on #OSS 00010000790 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000791 +705bonus pay for amazing work on #OSS 00010000791 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000792 +705bonus pay for amazing work on #OSS 00010000792 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000793 +705bonus pay for amazing work on #OSS 00010000793 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000794 +705bonus pay for amazing work on #OSS 00010000794 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000795 +705bonus pay for amazing work on #OSS 00010000795 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000796 +705bonus pay for amazing work on #OSS 00010000796 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000797 +705bonus pay for amazing work on #OSS 00010000797 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000798 +705bonus pay for amazing work on #OSS 00010000798 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000799 +705bonus pay for amazing work on #OSS 00010000799 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000800 +705bonus pay for amazing work on #OSS 00010000800 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000801 +705bonus pay for amazing work on #OSS 00010000801 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000802 +705bonus pay for amazing work on #OSS 00010000802 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000803 +705bonus pay for amazing work on #OSS 00010000803 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000804 +705bonus pay for amazing work on #OSS 00010000804 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000805 +705bonus pay for amazing work on #OSS 00010000805 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000806 +705bonus pay for amazing work on #OSS 00010000806 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000807 +705bonus pay for amazing work on #OSS 00010000807 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000808 +705bonus pay for amazing work on #OSS 00010000808 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000809 +705bonus pay for amazing work on #OSS 00010000809 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000810 +705bonus pay for amazing work on #OSS 00010000810 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000811 +705bonus pay for amazing work on #OSS 00010000811 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000812 +705bonus pay for amazing work on #OSS 00010000812 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000813 +705bonus pay for amazing work on #OSS 00010000813 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000814 +705bonus pay for amazing work on #OSS 00010000814 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000815 +705bonus pay for amazing work on #OSS 00010000815 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000816 +705bonus pay for amazing work on #OSS 00010000816 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000817 +705bonus pay for amazing work on #OSS 00010000817 +62223138010481967038518 0000100000#fgtSrfFAx4mUp#Anthony Harris 1121042880000818 +705bonus pay for amazing work on #OSS 00010000818 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Anthony Davis 1121042880000819 +705bonus pay for amazing work on #OSS 00010000819 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000820 +705bonus pay for amazing work on #OSS 00010000820 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000821 +705bonus pay for amazing work on #OSS 00010000821 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000822 +705bonus pay for amazing work on #OSS 00010000822 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000823 +705bonus pay for amazing work on #OSS 00010000823 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000824 +705bonus pay for amazing work on #OSS 00010000824 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000825 +705bonus pay for amazing work on #OSS 00010000825 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000826 +705bonus pay for amazing work on #OSS 00010000826 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000827 +705bonus pay for amazing work on #OSS 00010000827 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000828 +705bonus pay for amazing work on #OSS 00010000828 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000829 +705bonus pay for amazing work on #OSS 00010000829 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000830 +705bonus pay for amazing work on #OSS 00010000830 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000831 +705bonus pay for amazing work on #OSS 00010000831 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000832 +705bonus pay for amazing work on #OSS 00010000832 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000833 +705bonus pay for amazing work on #OSS 00010000833 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000834 +705bonus pay for amazing work on #OSS 00010000834 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000835 +705bonus pay for amazing work on #OSS 00010000835 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000836 +705bonus pay for amazing work on #OSS 00010000836 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000837 +705bonus pay for amazing work on #OSS 00010000837 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000838 +705bonus pay for amazing work on #OSS 00010000838 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000839 +705bonus pay for amazing work on #OSS 00010000839 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000840 +705bonus pay for amazing work on #OSS 00010000840 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000841 +705bonus pay for amazing work on #OSS 00010000841 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000842 +705bonus pay for amazing work on #OSS 00010000842 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000843 +705bonus pay for amazing work on #OSS 00010000843 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000844 +705bonus pay for amazing work on #OSS 00010000844 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000845 +705bonus pay for amazing work on #OSS 00010000845 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000846 +705bonus pay for amazing work on #OSS 00010000846 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000847 +705bonus pay for amazing work on #OSS 00010000847 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000848 +705bonus pay for amazing work on #OSS 00010000848 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000849 +705bonus pay for amazing work on #OSS 00010000849 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000850 +705bonus pay for amazing work on #OSS 00010000850 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000851 +705bonus pay for amazing work on #OSS 00010000851 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000852 +705bonus pay for amazing work on #OSS 00010000852 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000853 +705bonus pay for amazing work on #OSS 00010000853 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000854 +705bonus pay for amazing work on #OSS 00010000854 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000855 +705bonus pay for amazing work on #OSS 00010000855 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000856 +705bonus pay for amazing work on #OSS 00010000856 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000857 +705bonus pay for amazing work on #OSS 00010000857 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000858 +705bonus pay for amazing work on #OSS 00010000858 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000859 +705bonus pay for amazing work on #OSS 00010000859 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000860 +705bonus pay for amazing work on #OSS 00010000860 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000861 +705bonus pay for amazing work on #OSS 00010000861 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000862 +705bonus pay for amazing work on #OSS 00010000862 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000863 +705bonus pay for amazing work on #OSS 00010000863 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000864 +705bonus pay for amazing work on #OSS 00010000864 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000865 +705bonus pay for amazing work on #OSS 00010000865 +62223138010481967038518 0000100000#Ed6drEVxMb83r#Emily Davis 1121042880000866 +705bonus pay for amazing work on #OSS 00010000866 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000867 +705bonus pay for amazing work on #OSS 00010000867 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000868 +705bonus pay for amazing work on #OSS 00010000868 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000869 +705bonus pay for amazing work on #OSS 00010000869 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000870 +705bonus pay for amazing work on #OSS 00010000870 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000871 +705bonus pay for amazing work on #OSS 00010000871 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000872 +705bonus pay for amazing work on #OSS 00010000872 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000873 +705bonus pay for amazing work on #OSS 00010000873 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000874 +705bonus pay for amazing work on #OSS 00010000874 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000875 +705bonus pay for amazing work on #OSS 00010000875 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000876 +705bonus pay for amazing work on #OSS 00010000876 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000877 +705bonus pay for amazing work on #OSS 00010000877 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000878 +705bonus pay for amazing work on #OSS 00010000878 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000879 +705bonus pay for amazing work on #OSS 00010000879 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000880 +705bonus pay for amazing work on #OSS 00010000880 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000881 +705bonus pay for amazing work on #OSS 00010000881 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000882 +705bonus pay for amazing work on #OSS 00010000882 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000883 +705bonus pay for amazing work on #OSS 00010000883 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000884 +705bonus pay for amazing work on #OSS 00010000884 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000885 +705bonus pay for amazing work on #OSS 00010000885 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000886 +705bonus pay for amazing work on #OSS 00010000886 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000887 +705bonus pay for amazing work on #OSS 00010000887 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000888 +705bonus pay for amazing work on #OSS 00010000888 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000889 +705bonus pay for amazing work on #OSS 00010000889 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000890 +705bonus pay for amazing work on #OSS 00010000890 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000891 +705bonus pay for amazing work on #OSS 00010000891 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000892 +705bonus pay for amazing work on #OSS 00010000892 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000893 +705bonus pay for amazing work on #OSS 00010000893 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000894 +705bonus pay for amazing work on #OSS 00010000894 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000895 +705bonus pay for amazing work on #OSS 00010000895 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000896 +705bonus pay for amazing work on #OSS 00010000896 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000897 +705bonus pay for amazing work on #OSS 00010000897 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000898 +705bonus pay for amazing work on #OSS 00010000898 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000899 +705bonus pay for amazing work on #OSS 00010000899 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000900 +705bonus pay for amazing work on #OSS 00010000900 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000901 +705bonus pay for amazing work on #OSS 00010000901 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000902 +705bonus pay for amazing work on #OSS 00010000902 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000903 +705bonus pay for amazing work on #OSS 00010000903 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000904 +705bonus pay for amazing work on #OSS 00010000904 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000905 +705bonus pay for amazing work on #OSS 00010000905 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000906 +705bonus pay for amazing work on #OSS 00010000906 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000907 +705bonus pay for amazing work on #OSS 00010000907 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000908 +705bonus pay for amazing work on #OSS 00010000908 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000909 +705bonus pay for amazing work on #OSS 00010000909 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000910 +705bonus pay for amazing work on #OSS 00010000910 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000911 +705bonus pay for amazing work on #OSS 00010000911 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000912 +705bonus pay for amazing work on #OSS 00010000912 +62223138010481967038518 0000100000#3jgxuB3igPWmP#Elizabeth Taylor 1121042880000913 +705bonus pay for amazing work on #OSS 00010000913 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Avery Jackson 1121042880000914 +705bonus pay for amazing work on #OSS 00010000914 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000915 +705bonus pay for amazing work on #OSS 00010000915 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000916 +705bonus pay for amazing work on #OSS 00010000916 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000917 +705bonus pay for amazing work on #OSS 00010000917 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000918 +705bonus pay for amazing work on #OSS 00010000918 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000919 +705bonus pay for amazing work on #OSS 00010000919 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000920 +705bonus pay for amazing work on #OSS 00010000920 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000921 +705bonus pay for amazing work on #OSS 00010000921 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000922 +705bonus pay for amazing work on #OSS 00010000922 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000923 +705bonus pay for amazing work on #OSS 00010000923 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000924 +705bonus pay for amazing work on #OSS 00010000924 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000925 +705bonus pay for amazing work on #OSS 00010000925 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000926 +705bonus pay for amazing work on #OSS 00010000926 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000927 +705bonus pay for amazing work on #OSS 00010000927 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000928 +705bonus pay for amazing work on #OSS 00010000928 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000929 +705bonus pay for amazing work on #OSS 00010000929 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000930 +705bonus pay for amazing work on #OSS 00010000930 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000931 +705bonus pay for amazing work on #OSS 00010000931 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000932 +705bonus pay for amazing work on #OSS 00010000932 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000933 +705bonus pay for amazing work on #OSS 00010000933 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000934 +705bonus pay for amazing work on #OSS 00010000934 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000935 +705bonus pay for amazing work on #OSS 00010000935 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000936 +705bonus pay for amazing work on #OSS 00010000936 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000937 +705bonus pay for amazing work on #OSS 00010000937 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000938 +705bonus pay for amazing work on #OSS 00010000938 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000939 +705bonus pay for amazing work on #OSS 00010000939 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000940 +705bonus pay for amazing work on #OSS 00010000940 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000941 +705bonus pay for amazing work on #OSS 00010000941 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000942 +705bonus pay for amazing work on #OSS 00010000942 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000943 +705bonus pay for amazing work on #OSS 00010000943 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000944 +705bonus pay for amazing work on #OSS 00010000944 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000945 +705bonus pay for amazing work on #OSS 00010000945 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000946 +705bonus pay for amazing work on #OSS 00010000946 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000947 +705bonus pay for amazing work on #OSS 00010000947 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000948 +705bonus pay for amazing work on #OSS 00010000948 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000949 +705bonus pay for amazing work on #OSS 00010000949 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000950 +705bonus pay for amazing work on #OSS 00010000950 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000951 +705bonus pay for amazing work on #OSS 00010000951 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000952 +705bonus pay for amazing work on #OSS 00010000952 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000953 +705bonus pay for amazing work on #OSS 00010000953 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000954 +705bonus pay for amazing work on #OSS 00010000954 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000955 +705bonus pay for amazing work on #OSS 00010000955 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000956 +705bonus pay for amazing work on #OSS 00010000956 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000957 +705bonus pay for amazing work on #OSS 00010000957 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000958 +705bonus pay for amazing work on #OSS 00010000958 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000959 +705bonus pay for amazing work on #OSS 00010000959 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000960 +705bonus pay for amazing work on #OSS 00010000960 +62223138010481967038518 0000100000#Dl7Wa2FgRuN2S#Elijah Jackson 1121042880000961 +705bonus pay for amazing work on #OSS 00010000961 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000962 +705bonus pay for amazing work on #OSS 00010000962 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000963 +705bonus pay for amazing work on #OSS 00010000963 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000964 +705bonus pay for amazing work on #OSS 00010000964 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000965 +705bonus pay for amazing work on #OSS 00010000965 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000966 +705bonus pay for amazing work on #OSS 00010000966 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000967 +705bonus pay for amazing work on #OSS 00010000967 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000968 +705bonus pay for amazing work on #OSS 00010000968 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000969 +705bonus pay for amazing work on #OSS 00010000969 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000970 +705bonus pay for amazing work on #OSS 00010000970 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000971 +705bonus pay for amazing work on #OSS 00010000971 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000972 +705bonus pay for amazing work on #OSS 00010000972 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000973 +705bonus pay for amazing work on #OSS 00010000973 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000974 +705bonus pay for amazing work on #OSS 00010000974 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000975 +705bonus pay for amazing work on #OSS 00010000975 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000976 +705bonus pay for amazing work on #OSS 00010000976 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000977 +705bonus pay for amazing work on #OSS 00010000977 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000978 +705bonus pay for amazing work on #OSS 00010000978 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000979 +705bonus pay for amazing work on #OSS 00010000979 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000980 +705bonus pay for amazing work on #OSS 00010000980 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000981 +705bonus pay for amazing work on #OSS 00010000981 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000982 +705bonus pay for amazing work on #OSS 00010000982 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000983 +705bonus pay for amazing work on #OSS 00010000983 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000984 +705bonus pay for amazing work on #OSS 00010000984 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000985 +705bonus pay for amazing work on #OSS 00010000985 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000986 +705bonus pay for amazing work on #OSS 00010000986 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000987 +705bonus pay for amazing work on #OSS 00010000987 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000988 +705bonus pay for amazing work on #OSS 00010000988 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000989 +705bonus pay for amazing work on #OSS 00010000989 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000990 +705bonus pay for amazing work on #OSS 00010000990 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000991 +705bonus pay for amazing work on #OSS 00010000991 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000992 +705bonus pay for amazing work on #OSS 00010000992 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000993 +705bonus pay for amazing work on #OSS 00010000993 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000994 +705bonus pay for amazing work on #OSS 00010000994 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000995 +705bonus pay for amazing work on #OSS 00010000995 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000996 +705bonus pay for amazing work on #OSS 00010000996 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000997 +705bonus pay for amazing work on #OSS 00010000997 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000998 +705bonus pay for amazing work on #OSS 00010000998 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880000999 +705bonus pay for amazing work on #OSS 00010000999 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880001000 +705bonus pay for amazing work on #OSS 00010001000 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880001001 +705bonus pay for amazing work on #OSS 00010001001 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880001002 +705bonus pay for amazing work on #OSS 00010001002 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880001003 +705bonus pay for amazing work on #OSS 00010001003 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880001004 +705bonus pay for amazing work on #OSS 00010001004 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880001005 +705bonus pay for amazing work on #OSS 00010001005 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880001006 +705bonus pay for amazing work on #OSS 00010001006 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880001007 +705bonus pay for amazing work on #OSS 00010001007 +62223138010481967038518 0000100000#EOI6sG9rbGvbF#Mia Wilson 1121042880001008 +705bonus pay for amazing work on #OSS 00010001008 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Mia Jackson 1121042880001009 +705bonus pay for amazing work on #OSS 00010001009 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001010 +705bonus pay for amazing work on #OSS 00010001010 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001011 +705bonus pay for amazing work on #OSS 00010001011 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001012 +705bonus pay for amazing work on #OSS 00010001012 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001013 +705bonus pay for amazing work on #OSS 00010001013 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001014 +705bonus pay for amazing work on #OSS 00010001014 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001015 +705bonus pay for amazing work on #OSS 00010001015 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001016 +705bonus pay for amazing work on #OSS 00010001016 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001017 +705bonus pay for amazing work on #OSS 00010001017 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001018 +705bonus pay for amazing work on #OSS 00010001018 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001019 +705bonus pay for amazing work on #OSS 00010001019 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001020 +705bonus pay for amazing work on #OSS 00010001020 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001021 +705bonus pay for amazing work on #OSS 00010001021 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001022 +705bonus pay for amazing work on #OSS 00010001022 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001023 +705bonus pay for amazing work on #OSS 00010001023 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001024 +705bonus pay for amazing work on #OSS 00010001024 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001025 +705bonus pay for amazing work on #OSS 00010001025 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001026 +705bonus pay for amazing work on #OSS 00010001026 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001027 +705bonus pay for amazing work on #OSS 00010001027 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001028 +705bonus pay for amazing work on #OSS 00010001028 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001029 +705bonus pay for amazing work on #OSS 00010001029 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001030 +705bonus pay for amazing work on #OSS 00010001030 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001031 +705bonus pay for amazing work on #OSS 00010001031 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001032 +705bonus pay for amazing work on #OSS 00010001032 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001033 +705bonus pay for amazing work on #OSS 00010001033 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001034 +705bonus pay for amazing work on #OSS 00010001034 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001035 +705bonus pay for amazing work on #OSS 00010001035 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001036 +705bonus pay for amazing work on #OSS 00010001036 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001037 +705bonus pay for amazing work on #OSS 00010001037 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001038 +705bonus pay for amazing work on #OSS 00010001038 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001039 +705bonus pay for amazing work on #OSS 00010001039 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001040 +705bonus pay for amazing work on #OSS 00010001040 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001041 +705bonus pay for amazing work on #OSS 00010001041 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001042 +705bonus pay for amazing work on #OSS 00010001042 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001043 +705bonus pay for amazing work on #OSS 00010001043 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001044 +705bonus pay for amazing work on #OSS 00010001044 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001045 +705bonus pay for amazing work on #OSS 00010001045 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001046 +705bonus pay for amazing work on #OSS 00010001046 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001047 +705bonus pay for amazing work on #OSS 00010001047 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001048 +705bonus pay for amazing work on #OSS 00010001048 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001049 +705bonus pay for amazing work on #OSS 00010001049 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001050 +705bonus pay for amazing work on #OSS 00010001050 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001051 +705bonus pay for amazing work on #OSS 00010001051 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001052 +705bonus pay for amazing work on #OSS 00010001052 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001053 +705bonus pay for amazing work on #OSS 00010001053 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001054 +705bonus pay for amazing work on #OSS 00010001054 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001055 +705bonus pay for amazing work on #OSS 00010001055 +62223138010481967038518 0000100000#vAIRRZPGXc0yZ#Elijah Jackson 1121042880001056 +705bonus pay for amazing work on #OSS 00010001056 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001057 +705bonus pay for amazing work on #OSS 00010001057 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001058 +705bonus pay for amazing work on #OSS 00010001058 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001059 +705bonus pay for amazing work on #OSS 00010001059 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001060 +705bonus pay for amazing work on #OSS 00010001060 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001061 +705bonus pay for amazing work on #OSS 00010001061 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001062 +705bonus pay for amazing work on #OSS 00010001062 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001063 +705bonus pay for amazing work on #OSS 00010001063 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001064 +705bonus pay for amazing work on #OSS 00010001064 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001065 +705bonus pay for amazing work on #OSS 00010001065 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001066 +705bonus pay for amazing work on #OSS 00010001066 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001067 +705bonus pay for amazing work on #OSS 00010001067 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001068 +705bonus pay for amazing work on #OSS 00010001068 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001069 +705bonus pay for amazing work on #OSS 00010001069 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001070 +705bonus pay for amazing work on #OSS 00010001070 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001071 +705bonus pay for amazing work on #OSS 00010001071 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001072 +705bonus pay for amazing work on #OSS 00010001072 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001073 +705bonus pay for amazing work on #OSS 00010001073 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001074 +705bonus pay for amazing work on #OSS 00010001074 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001075 +705bonus pay for amazing work on #OSS 00010001075 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001076 +705bonus pay for amazing work on #OSS 00010001076 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001077 +705bonus pay for amazing work on #OSS 00010001077 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001078 +705bonus pay for amazing work on #OSS 00010001078 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001079 +705bonus pay for amazing work on #OSS 00010001079 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001080 +705bonus pay for amazing work on #OSS 00010001080 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001081 +705bonus pay for amazing work on #OSS 00010001081 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001082 +705bonus pay for amazing work on #OSS 00010001082 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001083 +705bonus pay for amazing work on #OSS 00010001083 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001084 +705bonus pay for amazing work on #OSS 00010001084 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001085 +705bonus pay for amazing work on #OSS 00010001085 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001086 +705bonus pay for amazing work on #OSS 00010001086 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001087 +705bonus pay for amazing work on #OSS 00010001087 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001088 +705bonus pay for amazing work on #OSS 00010001088 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001089 +705bonus pay for amazing work on #OSS 00010001089 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001090 +705bonus pay for amazing work on #OSS 00010001090 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001091 +705bonus pay for amazing work on #OSS 00010001091 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001092 +705bonus pay for amazing work on #OSS 00010001092 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001093 +705bonus pay for amazing work on #OSS 00010001093 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001094 +705bonus pay for amazing work on #OSS 00010001094 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001095 +705bonus pay for amazing work on #OSS 00010001095 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001096 +705bonus pay for amazing work on #OSS 00010001096 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001097 +705bonus pay for amazing work on #OSS 00010001097 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001098 +705bonus pay for amazing work on #OSS 00010001098 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001099 +705bonus pay for amazing work on #OSS 00010001099 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001100 +705bonus pay for amazing work on #OSS 00010001100 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001101 +705bonus pay for amazing work on #OSS 00010001101 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001102 +705bonus pay for amazing work on #OSS 00010001102 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001103 +705bonus pay for amazing work on #OSS 00010001103 +62223138010481967038518 0000100000#CPkyeasBgoHkX#Olivia Jones 1121042880001104 +705bonus pay for amazing work on #OSS 00010001104 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001105 +705bonus pay for amazing work on #OSS 00010001105 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001106 +705bonus pay for amazing work on #OSS 00010001106 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001107 +705bonus pay for amazing work on #OSS 00010001107 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001108 +705bonus pay for amazing work on #OSS 00010001108 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001109 +705bonus pay for amazing work on #OSS 00010001109 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001110 +705bonus pay for amazing work on #OSS 00010001110 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001111 +705bonus pay for amazing work on #OSS 00010001111 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001112 +705bonus pay for amazing work on #OSS 00010001112 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001113 +705bonus pay for amazing work on #OSS 00010001113 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001114 +705bonus pay for amazing work on #OSS 00010001114 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001115 +705bonus pay for amazing work on #OSS 00010001115 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001116 +705bonus pay for amazing work on #OSS 00010001116 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001117 +705bonus pay for amazing work on #OSS 00010001117 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001118 +705bonus pay for amazing work on #OSS 00010001118 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001119 +705bonus pay for amazing work on #OSS 00010001119 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001120 +705bonus pay for amazing work on #OSS 00010001120 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001121 +705bonus pay for amazing work on #OSS 00010001121 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001122 +705bonus pay for amazing work on #OSS 00010001122 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001123 +705bonus pay for amazing work on #OSS 00010001123 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001124 +705bonus pay for amazing work on #OSS 00010001124 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001125 +705bonus pay for amazing work on #OSS 00010001125 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001126 +705bonus pay for amazing work on #OSS 00010001126 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001127 +705bonus pay for amazing work on #OSS 00010001127 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001128 +705bonus pay for amazing work on #OSS 00010001128 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001129 +705bonus pay for amazing work on #OSS 00010001129 +62223138010481967038518 0000100000#KaoRNhiH9VGaZ#Joshua Thompson 1121042880001130 +705bonus pay for amazing work on #OSS 00010001130 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001131 +705bonus pay for amazing work on #OSS 00010001131 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001132 +705bonus pay for amazing work on #OSS 00010001132 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001133 +705bonus pay for amazing work on #OSS 00010001133 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001134 +705bonus pay for amazing work on #OSS 00010001134 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001135 +705bonus pay for amazing work on #OSS 00010001135 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001136 +705bonus pay for amazing work on #OSS 00010001136 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001137 +705bonus pay for amazing work on #OSS 00010001137 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001138 +705bonus pay for amazing work on #OSS 00010001138 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001139 +705bonus pay for amazing work on #OSS 00010001139 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001140 +705bonus pay for amazing work on #OSS 00010001140 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001141 +705bonus pay for amazing work on #OSS 00010001141 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001142 +705bonus pay for amazing work on #OSS 00010001142 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001143 +705bonus pay for amazing work on #OSS 00010001143 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001144 +705bonus pay for amazing work on #OSS 00010001144 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001145 +705bonus pay for amazing work on #OSS 00010001145 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001146 +705bonus pay for amazing work on #OSS 00010001146 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001147 +705bonus pay for amazing work on #OSS 00010001147 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001148 +705bonus pay for amazing work on #OSS 00010001148 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001149 +705bonus pay for amazing work on #OSS 00010001149 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001150 +705bonus pay for amazing work on #OSS 00010001150 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001151 +705bonus pay for amazing work on #OSS 00010001151 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001152 +705bonus pay for amazing work on #OSS 00010001152 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001153 +705bonus pay for amazing work on #OSS 00010001153 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001154 +705bonus pay for amazing work on #OSS 00010001154 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001155 +705bonus pay for amazing work on #OSS 00010001155 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001156 +705bonus pay for amazing work on #OSS 00010001156 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001157 +705bonus pay for amazing work on #OSS 00010001157 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001158 +705bonus pay for amazing work on #OSS 00010001158 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001159 +705bonus pay for amazing work on #OSS 00010001159 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001160 +705bonus pay for amazing work on #OSS 00010001160 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001161 +705bonus pay for amazing work on #OSS 00010001161 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001162 +705bonus pay for amazing work on #OSS 00010001162 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001163 +705bonus pay for amazing work on #OSS 00010001163 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001164 +705bonus pay for amazing work on #OSS 00010001164 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001165 +705bonus pay for amazing work on #OSS 00010001165 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001166 +705bonus pay for amazing work on #OSS 00010001166 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001167 +705bonus pay for amazing work on #OSS 00010001167 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001168 +705bonus pay for amazing work on #OSS 00010001168 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001169 +705bonus pay for amazing work on #OSS 00010001169 +62223138010481967038518 0000100000#xOOu5t33WLpZQ#Emily Davis 1121042880001170 +705bonus pay for amazing work on #OSS 00010001170 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001171 +705bonus pay for amazing work on #OSS 00010001171 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001172 +705bonus pay for amazing work on #OSS 00010001172 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001173 +705bonus pay for amazing work on #OSS 00010001173 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001174 +705bonus pay for amazing work on #OSS 00010001174 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001175 +705bonus pay for amazing work on #OSS 00010001175 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001176 +705bonus pay for amazing work on #OSS 00010001176 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001177 +705bonus pay for amazing work on #OSS 00010001177 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001178 +705bonus pay for amazing work on #OSS 00010001178 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001179 +705bonus pay for amazing work on #OSS 00010001179 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001180 +705bonus pay for amazing work on #OSS 00010001180 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001181 +705bonus pay for amazing work on #OSS 00010001181 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001182 +705bonus pay for amazing work on #OSS 00010001182 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001183 +705bonus pay for amazing work on #OSS 00010001183 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001184 +705bonus pay for amazing work on #OSS 00010001184 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001185 +705bonus pay for amazing work on #OSS 00010001185 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001186 +705bonus pay for amazing work on #OSS 00010001186 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001187 +705bonus pay for amazing work on #OSS 00010001187 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001188 +705bonus pay for amazing work on #OSS 00010001188 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001189 +705bonus pay for amazing work on #OSS 00010001189 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001190 +705bonus pay for amazing work on #OSS 00010001190 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001191 +705bonus pay for amazing work on #OSS 00010001191 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001192 +705bonus pay for amazing work on #OSS 00010001192 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001193 +705bonus pay for amazing work on #OSS 00010001193 +62223138010481967038518 0000100000#syPORJb6kSrU8#William Brown 1121042880001194 +705bonus pay for amazing work on #OSS 00010001194 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#William Anderson 1121042880001195 +705bonus pay for amazing work on #OSS 00010001195 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001196 +705bonus pay for amazing work on #OSS 00010001196 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001197 +705bonus pay for amazing work on #OSS 00010001197 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001198 +705bonus pay for amazing work on #OSS 00010001198 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001199 +705bonus pay for amazing work on #OSS 00010001199 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001200 +705bonus pay for amazing work on #OSS 00010001200 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001201 +705bonus pay for amazing work on #OSS 00010001201 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001202 +705bonus pay for amazing work on #OSS 00010001202 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001203 +705bonus pay for amazing work on #OSS 00010001203 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001204 +705bonus pay for amazing work on #OSS 00010001204 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001205 +705bonus pay for amazing work on #OSS 00010001205 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001206 +705bonus pay for amazing work on #OSS 00010001206 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001207 +705bonus pay for amazing work on #OSS 00010001207 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001208 +705bonus pay for amazing work on #OSS 00010001208 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001209 +705bonus pay for amazing work on #OSS 00010001209 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001210 +705bonus pay for amazing work on #OSS 00010001210 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001211 +705bonus pay for amazing work on #OSS 00010001211 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001212 +705bonus pay for amazing work on #OSS 00010001212 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001213 +705bonus pay for amazing work on #OSS 00010001213 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001214 +705bonus pay for amazing work on #OSS 00010001214 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001215 +705bonus pay for amazing work on #OSS 00010001215 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001216 +705bonus pay for amazing work on #OSS 00010001216 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001217 +705bonus pay for amazing work on #OSS 00010001217 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001218 +705bonus pay for amazing work on #OSS 00010001218 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001219 +705bonus pay for amazing work on #OSS 00010001219 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001220 +705bonus pay for amazing work on #OSS 00010001220 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001221 +705bonus pay for amazing work on #OSS 00010001221 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001222 +705bonus pay for amazing work on #OSS 00010001222 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001223 +705bonus pay for amazing work on #OSS 00010001223 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001224 +705bonus pay for amazing work on #OSS 00010001224 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001225 +705bonus pay for amazing work on #OSS 00010001225 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001226 +705bonus pay for amazing work on #OSS 00010001226 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001227 +705bonus pay for amazing work on #OSS 00010001227 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001228 +705bonus pay for amazing work on #OSS 00010001228 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001229 +705bonus pay for amazing work on #OSS 00010001229 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001230 +705bonus pay for amazing work on #OSS 00010001230 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001231 +705bonus pay for amazing work on #OSS 00010001231 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001232 +705bonus pay for amazing work on #OSS 00010001232 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001233 +705bonus pay for amazing work on #OSS 00010001233 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001234 +705bonus pay for amazing work on #OSS 00010001234 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001235 +705bonus pay for amazing work on #OSS 00010001235 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001236 +705bonus pay for amazing work on #OSS 00010001236 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001237 +705bonus pay for amazing work on #OSS 00010001237 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001238 +705bonus pay for amazing work on #OSS 00010001238 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001239 +705bonus pay for amazing work on #OSS 00010001239 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001240 +705bonus pay for amazing work on #OSS 00010001240 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001241 +705bonus pay for amazing work on #OSS 00010001241 +62223138010481967038518 0000100000#9SFX0fWVG8Tzn#Daniel Anderson 1121042880001242 +705bonus pay for amazing work on #OSS 00010001242 +62223138010481967038518 0000100000#k44W4Dz1huW2W#Anthony Harris 1121042880001243 +705bonus pay for amazing work on #OSS 00010001243 +62223138010481967038518 0000100000#k44W4Dz1huW2W#Anthony Harris 1121042880001244 +705bonus pay for amazing work on #OSS 00010001244 +62223138010481967038518 0000100000#k44W4Dz1huW2W#Anthony Harris 1121042880001245 +705bonus pay for amazing work on #OSS 00010001245 +62223138010481967038518 0000100000#k44W4Dz1huW2W#Anthony Harris 1121042880001246 +705bonus pay for amazing work on #OSS 00010001246 +62223138010481967038518 0000100000#k44W4Dz1huW2W#Anthony Harris 1121042880001247 +705bonus pay for amazing work on #OSS 00010001247 +62223138010481967038518 0000100000#k44W4Dz1huW2W#Anthony Harris 1121042880001248 +705bonus pay for amazing work on #OSS 00010001248 +62223138010481967038518 0000100000#k44W4Dz1huW2W#Anthony Harris 1121042880001249 +705bonus pay for amazing work on #OSS 00010001249 +62223138010481967038518 0000100000#k44W4Dz1huW2W#Anthony Harris 1121042880001250 +705bonus pay for amazing work on #OSS 00010001250 +82000025008922512500000000000000000125000000121042882 121042880000003 +5200Wells Fargo 121042882 PPDTrans. Des 180511 0121042880000004 +62223138010481967038518 0000100000#Fg8U8Rt9qiqxr#Sofia Garcia 1121042880000001 +705bonus pay for amazing work on #OSS 00010000001 +62223138010481967038518 0000100000#Fg8U8Rt9qiqxr#Sofia Garcia 1121042880000002 +705bonus pay for amazing work on #OSS 00010000002 +62223138010481967038518 0000100000#Fg8U8Rt9qiqxr#Sofia Garcia 1121042880000003 +705bonus pay for amazing work on #OSS 00010000003 +62223138010481967038518 0000100000#Fg8U8Rt9qiqxr#Sofia Garcia 1121042880000004 +705bonus pay for amazing work on #OSS 00010000004 +62223138010481967038518 0000100000#Fg8U8Rt9qiqxr#Sofia Garcia 1121042880000005 +705bonus pay for amazing work on #OSS 00010000005 +62223138010481967038518 0000100000#Fg8U8Rt9qiqxr#Sofia Garcia 1121042880000006 +705bonus pay for amazing work on #OSS 00010000006 +62223138010481967038518 0000100000#Fg8U8Rt9qiqxr#Sofia Garcia 1121042880000007 +705bonus pay for amazing work on #OSS 00010000007 +62223138010481967038518 0000100000#Fg8U8Rt9qiqxr#Sofia Garcia 1121042880000008 +705bonus pay for amazing work on #OSS 00010000008 +62223138010481967038518 0000100000#Fg8U8Rt9qiqxr#Sofia Garcia 1121042880000009 +705bonus pay for amazing work on #OSS 00010000009 +62223138010481967038518 0000100000#Fg8U8Rt9qiqxr#Sofia Garcia 1121042880000010 +705bonus pay for amazing work on #OSS 00010000010 +62223138010481967038518 0000100000#Fg8U8Rt9qiqxr#Sofia Garcia 1121042880000011 +705bonus pay for amazing work on #OSS 00010000011 +62223138010481967038518 0000100000#Fg8U8Rt9qiqxr#Sofia Garcia 1121042880000012 +705bonus pay for amazing work on #OSS 00010000012 +62223138010481967038518 0000100000#Fg8U8Rt9qiqxr#Sofia Garcia 1121042880000013 +705bonus pay for amazing work on #OSS 00010000013 +62223138010481967038518 0000100000#Fg8U8Rt9qiqxr#Sofia Garcia 1121042880000014 +705bonus pay for amazing work on #OSS 00010000014 +62223138010481967038518 0000100000#Fg8U8Rt9qiqxr#Sofia Garcia 1121042880000015 +705bonus pay for amazing work on #OSS 00010000015 +62223138010481967038518 0000100000#Fg8U8Rt9qiqxr#Sofia Garcia 1121042880000016 +705bonus pay for amazing work on #OSS 00010000016 +62223138010481967038518 0000100000#Fg8U8Rt9qiqxr#Sofia Garcia 1121042880000017 +705bonus pay for amazing work on #OSS 00010000017 +62223138010481967038518 0000100000#Fg8U8Rt9qiqxr#Sofia Garcia 1121042880000018 +705bonus pay for amazing work on #OSS 00010000018 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000019 +705bonus pay for amazing work on #OSS 00010000019 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000020 +705bonus pay for amazing work on #OSS 00010000020 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000021 +705bonus pay for amazing work on #OSS 00010000021 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000022 +705bonus pay for amazing work on #OSS 00010000022 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000023 +705bonus pay for amazing work on #OSS 00010000023 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000024 +705bonus pay for amazing work on #OSS 00010000024 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000025 +705bonus pay for amazing work on #OSS 00010000025 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000026 +705bonus pay for amazing work on #OSS 00010000026 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000027 +705bonus pay for amazing work on #OSS 00010000027 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000028 +705bonus pay for amazing work on #OSS 00010000028 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000029 +705bonus pay for amazing work on #OSS 00010000029 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000030 +705bonus pay for amazing work on #OSS 00010000030 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000031 +705bonus pay for amazing work on #OSS 00010000031 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000032 +705bonus pay for amazing work on #OSS 00010000032 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000033 +705bonus pay for amazing work on #OSS 00010000033 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000034 +705bonus pay for amazing work on #OSS 00010000034 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000035 +705bonus pay for amazing work on #OSS 00010000035 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000036 +705bonus pay for amazing work on #OSS 00010000036 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000037 +705bonus pay for amazing work on #OSS 00010000037 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000038 +705bonus pay for amazing work on #OSS 00010000038 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000039 +705bonus pay for amazing work on #OSS 00010000039 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000040 +705bonus pay for amazing work on #OSS 00010000040 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000041 +705bonus pay for amazing work on #OSS 00010000041 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000042 +705bonus pay for amazing work on #OSS 00010000042 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000043 +705bonus pay for amazing work on #OSS 00010000043 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000044 +705bonus pay for amazing work on #OSS 00010000044 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000045 +705bonus pay for amazing work on #OSS 00010000045 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000046 +705bonus pay for amazing work on #OSS 00010000046 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000047 +705bonus pay for amazing work on #OSS 00010000047 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000048 +705bonus pay for amazing work on #OSS 00010000048 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000049 +705bonus pay for amazing work on #OSS 00010000049 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000050 +705bonus pay for amazing work on #OSS 00010000050 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000051 +705bonus pay for amazing work on #OSS 00010000051 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000052 +705bonus pay for amazing work on #OSS 00010000052 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000053 +705bonus pay for amazing work on #OSS 00010000053 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000054 +705bonus pay for amazing work on #OSS 00010000054 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000055 +705bonus pay for amazing work on #OSS 00010000055 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000056 +705bonus pay for amazing work on #OSS 00010000056 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000057 +705bonus pay for amazing work on #OSS 00010000057 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000058 +705bonus pay for amazing work on #OSS 00010000058 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000059 +705bonus pay for amazing work on #OSS 00010000059 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000060 +705bonus pay for amazing work on #OSS 00010000060 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000061 +705bonus pay for amazing work on #OSS 00010000061 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000062 +705bonus pay for amazing work on #OSS 00010000062 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000063 +705bonus pay for amazing work on #OSS 00010000063 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000064 +705bonus pay for amazing work on #OSS 00010000064 +62223138010481967038518 0000100000#RDQEdcEJZpOcf#Daniel Anderson 1121042880000065 +705bonus pay for amazing work on #OSS 00010000065 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Daniel White 1121042880000066 +705bonus pay for amazing work on #OSS 00010000066 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000067 +705bonus pay for amazing work on #OSS 00010000067 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000068 +705bonus pay for amazing work on #OSS 00010000068 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000069 +705bonus pay for amazing work on #OSS 00010000069 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000070 +705bonus pay for amazing work on #OSS 00010000070 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000071 +705bonus pay for amazing work on #OSS 00010000071 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000072 +705bonus pay for amazing work on #OSS 00010000072 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000073 +705bonus pay for amazing work on #OSS 00010000073 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000074 +705bonus pay for amazing work on #OSS 00010000074 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000075 +705bonus pay for amazing work on #OSS 00010000075 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000076 +705bonus pay for amazing work on #OSS 00010000076 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000077 +705bonus pay for amazing work on #OSS 00010000077 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000078 +705bonus pay for amazing work on #OSS 00010000078 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000079 +705bonus pay for amazing work on #OSS 00010000079 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000080 +705bonus pay for amazing work on #OSS 00010000080 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000081 +705bonus pay for amazing work on #OSS 00010000081 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000082 +705bonus pay for amazing work on #OSS 00010000082 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000083 +705bonus pay for amazing work on #OSS 00010000083 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000084 +705bonus pay for amazing work on #OSS 00010000084 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000085 +705bonus pay for amazing work on #OSS 00010000085 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000086 +705bonus pay for amazing work on #OSS 00010000086 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000087 +705bonus pay for amazing work on #OSS 00010000087 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000088 +705bonus pay for amazing work on #OSS 00010000088 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000089 +705bonus pay for amazing work on #OSS 00010000089 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000090 +705bonus pay for amazing work on #OSS 00010000090 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000091 +705bonus pay for amazing work on #OSS 00010000091 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000092 +705bonus pay for amazing work on #OSS 00010000092 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000093 +705bonus pay for amazing work on #OSS 00010000093 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000094 +705bonus pay for amazing work on #OSS 00010000094 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000095 +705bonus pay for amazing work on #OSS 00010000095 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000096 +705bonus pay for amazing work on #OSS 00010000096 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000097 +705bonus pay for amazing work on #OSS 00010000097 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000098 +705bonus pay for amazing work on #OSS 00010000098 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000099 +705bonus pay for amazing work on #OSS 00010000099 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000100 +705bonus pay for amazing work on #OSS 00010000100 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000101 +705bonus pay for amazing work on #OSS 00010000101 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000102 +705bonus pay for amazing work on #OSS 00010000102 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000103 +705bonus pay for amazing work on #OSS 00010000103 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000104 +705bonus pay for amazing work on #OSS 00010000104 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000105 +705bonus pay for amazing work on #OSS 00010000105 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000106 +705bonus pay for amazing work on #OSS 00010000106 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000107 +705bonus pay for amazing work on #OSS 00010000107 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000108 +705bonus pay for amazing work on #OSS 00010000108 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000109 +705bonus pay for amazing work on #OSS 00010000109 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000110 +705bonus pay for amazing work on #OSS 00010000110 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000111 +705bonus pay for amazing work on #OSS 00010000111 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000112 +705bonus pay for amazing work on #OSS 00010000112 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000113 +705bonus pay for amazing work on #OSS 00010000113 +62223138010481967038518 0000100000#1oqPqxiM93gBj#Addison White 1121042880000114 +705bonus pay for amazing work on #OSS 00010000114 +62223138010481967038518 0000100000#yzTZKqaeAPlek#Addison Brown 1121042880000115 +705bonus pay for amazing work on #OSS 00010000115 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000116 +705bonus pay for amazing work on #OSS 00010000116 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000117 +705bonus pay for amazing work on #OSS 00010000117 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000118 +705bonus pay for amazing work on #OSS 00010000118 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000119 +705bonus pay for amazing work on #OSS 00010000119 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000120 +705bonus pay for amazing work on #OSS 00010000120 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000121 +705bonus pay for amazing work on #OSS 00010000121 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000122 +705bonus pay for amazing work on #OSS 00010000122 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000123 +705bonus pay for amazing work on #OSS 00010000123 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000124 +705bonus pay for amazing work on #OSS 00010000124 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000125 +705bonus pay for amazing work on #OSS 00010000125 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000126 +705bonus pay for amazing work on #OSS 00010000126 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000127 +705bonus pay for amazing work on #OSS 00010000127 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000128 +705bonus pay for amazing work on #OSS 00010000128 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000129 +705bonus pay for amazing work on #OSS 00010000129 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000130 +705bonus pay for amazing work on #OSS 00010000130 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000131 +705bonus pay for amazing work on #OSS 00010000131 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000132 +705bonus pay for amazing work on #OSS 00010000132 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000133 +705bonus pay for amazing work on #OSS 00010000133 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000134 +705bonus pay for amazing work on #OSS 00010000134 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000135 +705bonus pay for amazing work on #OSS 00010000135 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000136 +705bonus pay for amazing work on #OSS 00010000136 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000137 +705bonus pay for amazing work on #OSS 00010000137 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000138 +705bonus pay for amazing work on #OSS 00010000138 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000139 +705bonus pay for amazing work on #OSS 00010000139 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000140 +705bonus pay for amazing work on #OSS 00010000140 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000141 +705bonus pay for amazing work on #OSS 00010000141 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000142 +705bonus pay for amazing work on #OSS 00010000142 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000143 +705bonus pay for amazing work on #OSS 00010000143 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000144 +705bonus pay for amazing work on #OSS 00010000144 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000145 +705bonus pay for amazing work on #OSS 00010000145 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000146 +705bonus pay for amazing work on #OSS 00010000146 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000147 +705bonus pay for amazing work on #OSS 00010000147 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000148 +705bonus pay for amazing work on #OSS 00010000148 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000149 +705bonus pay for amazing work on #OSS 00010000149 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000150 +705bonus pay for amazing work on #OSS 00010000150 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000151 +705bonus pay for amazing work on #OSS 00010000151 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000152 +705bonus pay for amazing work on #OSS 00010000152 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000153 +705bonus pay for amazing work on #OSS 00010000153 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000154 +705bonus pay for amazing work on #OSS 00010000154 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000155 +705bonus pay for amazing work on #OSS 00010000155 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000156 +705bonus pay for amazing work on #OSS 00010000156 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000157 +705bonus pay for amazing work on #OSS 00010000157 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000158 +705bonus pay for amazing work on #OSS 00010000158 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000159 +705bonus pay for amazing work on #OSS 00010000159 +62223138010481967038518 0000100000#yzTZKqaeAPlek#William Brown 1121042880000160 +705bonus pay for amazing work on #OSS 00010000160 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000161 +705bonus pay for amazing work on #OSS 00010000161 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000162 +705bonus pay for amazing work on #OSS 00010000162 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000163 +705bonus pay for amazing work on #OSS 00010000163 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000164 +705bonus pay for amazing work on #OSS 00010000164 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000165 +705bonus pay for amazing work on #OSS 00010000165 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000166 +705bonus pay for amazing work on #OSS 00010000166 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000167 +705bonus pay for amazing work on #OSS 00010000167 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000168 +705bonus pay for amazing work on #OSS 00010000168 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000169 +705bonus pay for amazing work on #OSS 00010000169 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000170 +705bonus pay for amazing work on #OSS 00010000170 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000171 +705bonus pay for amazing work on #OSS 00010000171 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000172 +705bonus pay for amazing work on #OSS 00010000172 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000173 +705bonus pay for amazing work on #OSS 00010000173 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000174 +705bonus pay for amazing work on #OSS 00010000174 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000175 +705bonus pay for amazing work on #OSS 00010000175 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000176 +705bonus pay for amazing work on #OSS 00010000176 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000177 +705bonus pay for amazing work on #OSS 00010000177 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000178 +705bonus pay for amazing work on #OSS 00010000178 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000179 +705bonus pay for amazing work on #OSS 00010000179 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000180 +705bonus pay for amazing work on #OSS 00010000180 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000181 +705bonus pay for amazing work on #OSS 00010000181 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000182 +705bonus pay for amazing work on #OSS 00010000182 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000183 +705bonus pay for amazing work on #OSS 00010000183 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000184 +705bonus pay for amazing work on #OSS 00010000184 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000185 +705bonus pay for amazing work on #OSS 00010000185 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000186 +705bonus pay for amazing work on #OSS 00010000186 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000187 +705bonus pay for amazing work on #OSS 00010000187 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000188 +705bonus pay for amazing work on #OSS 00010000188 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000189 +705bonus pay for amazing work on #OSS 00010000189 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000190 +705bonus pay for amazing work on #OSS 00010000190 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000191 +705bonus pay for amazing work on #OSS 00010000191 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000192 +705bonus pay for amazing work on #OSS 00010000192 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000193 +705bonus pay for amazing work on #OSS 00010000193 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000194 +705bonus pay for amazing work on #OSS 00010000194 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000195 +705bonus pay for amazing work on #OSS 00010000195 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000196 +705bonus pay for amazing work on #OSS 00010000196 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000197 +705bonus pay for amazing work on #OSS 00010000197 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000198 +705bonus pay for amazing work on #OSS 00010000198 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000199 +705bonus pay for amazing work on #OSS 00010000199 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000200 +705bonus pay for amazing work on #OSS 00010000200 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000201 +705bonus pay for amazing work on #OSS 00010000201 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000202 +705bonus pay for amazing work on #OSS 00010000202 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000203 +705bonus pay for amazing work on #OSS 00010000203 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000204 +705bonus pay for amazing work on #OSS 00010000204 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000205 +705bonus pay for amazing work on #OSS 00010000205 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000206 +705bonus pay for amazing work on #OSS 00010000206 +62223138010481967038518 0000100000#6qM79BQJvDDVe#Sofia Garcia 1121042880000207 +705bonus pay for amazing work on #OSS 00010000207 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Sofia Wilson 1121042880000208 +705bonus pay for amazing work on #OSS 00010000208 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000209 +705bonus pay for amazing work on #OSS 00010000209 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000210 +705bonus pay for amazing work on #OSS 00010000210 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000211 +705bonus pay for amazing work on #OSS 00010000211 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000212 +705bonus pay for amazing work on #OSS 00010000212 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000213 +705bonus pay for amazing work on #OSS 00010000213 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000214 +705bonus pay for amazing work on #OSS 00010000214 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000215 +705bonus pay for amazing work on #OSS 00010000215 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000216 +705bonus pay for amazing work on #OSS 00010000216 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000217 +705bonus pay for amazing work on #OSS 00010000217 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000218 +705bonus pay for amazing work on #OSS 00010000218 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000219 +705bonus pay for amazing work on #OSS 00010000219 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000220 +705bonus pay for amazing work on #OSS 00010000220 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000221 +705bonus pay for amazing work on #OSS 00010000221 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000222 +705bonus pay for amazing work on #OSS 00010000222 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000223 +705bonus pay for amazing work on #OSS 00010000223 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000224 +705bonus pay for amazing work on #OSS 00010000224 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000225 +705bonus pay for amazing work on #OSS 00010000225 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000226 +705bonus pay for amazing work on #OSS 00010000226 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000227 +705bonus pay for amazing work on #OSS 00010000227 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000228 +705bonus pay for amazing work on #OSS 00010000228 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000229 +705bonus pay for amazing work on #OSS 00010000229 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000230 +705bonus pay for amazing work on #OSS 00010000230 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000231 +705bonus pay for amazing work on #OSS 00010000231 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000232 +705bonus pay for amazing work on #OSS 00010000232 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000233 +705bonus pay for amazing work on #OSS 00010000233 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000234 +705bonus pay for amazing work on #OSS 00010000234 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000235 +705bonus pay for amazing work on #OSS 00010000235 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000236 +705bonus pay for amazing work on #OSS 00010000236 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000237 +705bonus pay for amazing work on #OSS 00010000237 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000238 +705bonus pay for amazing work on #OSS 00010000238 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000239 +705bonus pay for amazing work on #OSS 00010000239 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000240 +705bonus pay for amazing work on #OSS 00010000240 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000241 +705bonus pay for amazing work on #OSS 00010000241 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000242 +705bonus pay for amazing work on #OSS 00010000242 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000243 +705bonus pay for amazing work on #OSS 00010000243 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000244 +705bonus pay for amazing work on #OSS 00010000244 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000245 +705bonus pay for amazing work on #OSS 00010000245 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000246 +705bonus pay for amazing work on #OSS 00010000246 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000247 +705bonus pay for amazing work on #OSS 00010000247 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000248 +705bonus pay for amazing work on #OSS 00010000248 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000249 +705bonus pay for amazing work on #OSS 00010000249 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000250 +705bonus pay for amazing work on #OSS 00010000250 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000251 +705bonus pay for amazing work on #OSS 00010000251 +62223138010481967038518 0000100000#fGGgRdtrx4YNB#Mia Wilson 1121042880000252 +705bonus pay for amazing work on #OSS 00010000252 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000253 +705bonus pay for amazing work on #OSS 00010000253 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000254 +705bonus pay for amazing work on #OSS 00010000254 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000255 +705bonus pay for amazing work on #OSS 00010000255 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000256 +705bonus pay for amazing work on #OSS 00010000256 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000257 +705bonus pay for amazing work on #OSS 00010000257 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000258 +705bonus pay for amazing work on #OSS 00010000258 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000259 +705bonus pay for amazing work on #OSS 00010000259 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000260 +705bonus pay for amazing work on #OSS 00010000260 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000261 +705bonus pay for amazing work on #OSS 00010000261 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000262 +705bonus pay for amazing work on #OSS 00010000262 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000263 +705bonus pay for amazing work on #OSS 00010000263 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000264 +705bonus pay for amazing work on #OSS 00010000264 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000265 +705bonus pay for amazing work on #OSS 00010000265 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000266 +705bonus pay for amazing work on #OSS 00010000266 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000267 +705bonus pay for amazing work on #OSS 00010000267 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000268 +705bonus pay for amazing work on #OSS 00010000268 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000269 +705bonus pay for amazing work on #OSS 00010000269 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000270 +705bonus pay for amazing work on #OSS 00010000270 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000271 +705bonus pay for amazing work on #OSS 00010000271 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000272 +705bonus pay for amazing work on #OSS 00010000272 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000273 +705bonus pay for amazing work on #OSS 00010000273 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000274 +705bonus pay for amazing work on #OSS 00010000274 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000275 +705bonus pay for amazing work on #OSS 00010000275 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000276 +705bonus pay for amazing work on #OSS 00010000276 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000277 +705bonus pay for amazing work on #OSS 00010000277 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000278 +705bonus pay for amazing work on #OSS 00010000278 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000279 +705bonus pay for amazing work on #OSS 00010000279 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000280 +705bonus pay for amazing work on #OSS 00010000280 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000281 +705bonus pay for amazing work on #OSS 00010000281 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000282 +705bonus pay for amazing work on #OSS 00010000282 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000283 +705bonus pay for amazing work on #OSS 00010000283 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000284 +705bonus pay for amazing work on #OSS 00010000284 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000285 +705bonus pay for amazing work on #OSS 00010000285 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000286 +705bonus pay for amazing work on #OSS 00010000286 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000287 +705bonus pay for amazing work on #OSS 00010000287 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000288 +705bonus pay for amazing work on #OSS 00010000288 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000289 +705bonus pay for amazing work on #OSS 00010000289 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000290 +705bonus pay for amazing work on #OSS 00010000290 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000291 +705bonus pay for amazing work on #OSS 00010000291 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000292 +705bonus pay for amazing work on #OSS 00010000292 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000293 +705bonus pay for amazing work on #OSS 00010000293 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000294 +705bonus pay for amazing work on #OSS 00010000294 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000295 +705bonus pay for amazing work on #OSS 00010000295 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000296 +705bonus pay for amazing work on #OSS 00010000296 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000297 +705bonus pay for amazing work on #OSS 00010000297 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000298 +705bonus pay for amazing work on #OSS 00010000298 +62223138010481967038518 0000100000#PNk2Q9YM1HSVX#Ella Thomas 1121042880000299 +705bonus pay for amazing work on #OSS 00010000299 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Sophia Smith 1121042880000300 +705bonus pay for amazing work on #OSS 00010000300 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000301 +705bonus pay for amazing work on #OSS 00010000301 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000302 +705bonus pay for amazing work on #OSS 00010000302 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000303 +705bonus pay for amazing work on #OSS 00010000303 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000304 +705bonus pay for amazing work on #OSS 00010000304 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000305 +705bonus pay for amazing work on #OSS 00010000305 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000306 +705bonus pay for amazing work on #OSS 00010000306 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000307 +705bonus pay for amazing work on #OSS 00010000307 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000308 +705bonus pay for amazing work on #OSS 00010000308 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000309 +705bonus pay for amazing work on #OSS 00010000309 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000310 +705bonus pay for amazing work on #OSS 00010000310 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000311 +705bonus pay for amazing work on #OSS 00010000311 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000312 +705bonus pay for amazing work on #OSS 00010000312 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000313 +705bonus pay for amazing work on #OSS 00010000313 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000314 +705bonus pay for amazing work on #OSS 00010000314 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000315 +705bonus pay for amazing work on #OSS 00010000315 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000316 +705bonus pay for amazing work on #OSS 00010000316 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000317 +705bonus pay for amazing work on #OSS 00010000317 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000318 +705bonus pay for amazing work on #OSS 00010000318 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000319 +705bonus pay for amazing work on #OSS 00010000319 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000320 +705bonus pay for amazing work on #OSS 00010000320 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000321 +705bonus pay for amazing work on #OSS 00010000321 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000322 +705bonus pay for amazing work on #OSS 00010000322 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000323 +705bonus pay for amazing work on #OSS 00010000323 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000324 +705bonus pay for amazing work on #OSS 00010000324 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000325 +705bonus pay for amazing work on #OSS 00010000325 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000326 +705bonus pay for amazing work on #OSS 00010000326 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000327 +705bonus pay for amazing work on #OSS 00010000327 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000328 +705bonus pay for amazing work on #OSS 00010000328 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000329 +705bonus pay for amazing work on #OSS 00010000329 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000330 +705bonus pay for amazing work on #OSS 00010000330 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000331 +705bonus pay for amazing work on #OSS 00010000331 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000332 +705bonus pay for amazing work on #OSS 00010000332 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000333 +705bonus pay for amazing work on #OSS 00010000333 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000334 +705bonus pay for amazing work on #OSS 00010000334 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000335 +705bonus pay for amazing work on #OSS 00010000335 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000336 +705bonus pay for amazing work on #OSS 00010000336 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000337 +705bonus pay for amazing work on #OSS 00010000337 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000338 +705bonus pay for amazing work on #OSS 00010000338 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000339 +705bonus pay for amazing work on #OSS 00010000339 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000340 +705bonus pay for amazing work on #OSS 00010000340 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000341 +705bonus pay for amazing work on #OSS 00010000341 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000342 +705bonus pay for amazing work on #OSS 00010000342 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000343 +705bonus pay for amazing work on #OSS 00010000343 +62223138010481967038518 0000100000#NhWhHfdJXFyZb#Jacob Smith 1121042880000344 +705bonus pay for amazing work on #OSS 00010000344 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000345 +705bonus pay for amazing work on #OSS 00010000345 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000346 +705bonus pay for amazing work on #OSS 00010000346 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000347 +705bonus pay for amazing work on #OSS 00010000347 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000348 +705bonus pay for amazing work on #OSS 00010000348 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000349 +705bonus pay for amazing work on #OSS 00010000349 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000350 +705bonus pay for amazing work on #OSS 00010000350 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000351 +705bonus pay for amazing work on #OSS 00010000351 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000352 +705bonus pay for amazing work on #OSS 00010000352 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000353 +705bonus pay for amazing work on #OSS 00010000353 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000354 +705bonus pay for amazing work on #OSS 00010000354 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000355 +705bonus pay for amazing work on #OSS 00010000355 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000356 +705bonus pay for amazing work on #OSS 00010000356 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000357 +705bonus pay for amazing work on #OSS 00010000357 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000358 +705bonus pay for amazing work on #OSS 00010000358 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000359 +705bonus pay for amazing work on #OSS 00010000359 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000360 +705bonus pay for amazing work on #OSS 00010000360 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000361 +705bonus pay for amazing work on #OSS 00010000361 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000362 +705bonus pay for amazing work on #OSS 00010000362 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000363 +705bonus pay for amazing work on #OSS 00010000363 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000364 +705bonus pay for amazing work on #OSS 00010000364 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000365 +705bonus pay for amazing work on #OSS 00010000365 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000366 +705bonus pay for amazing work on #OSS 00010000366 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000367 +705bonus pay for amazing work on #OSS 00010000367 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000368 +705bonus pay for amazing work on #OSS 00010000368 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000369 +705bonus pay for amazing work on #OSS 00010000369 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000370 +705bonus pay for amazing work on #OSS 00010000370 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000371 +705bonus pay for amazing work on #OSS 00010000371 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000372 +705bonus pay for amazing work on #OSS 00010000372 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000373 +705bonus pay for amazing work on #OSS 00010000373 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000374 +705bonus pay for amazing work on #OSS 00010000374 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000375 +705bonus pay for amazing work on #OSS 00010000375 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000376 +705bonus pay for amazing work on #OSS 00010000376 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000377 +705bonus pay for amazing work on #OSS 00010000377 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000378 +705bonus pay for amazing work on #OSS 00010000378 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000379 +705bonus pay for amazing work on #OSS 00010000379 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000380 +705bonus pay for amazing work on #OSS 00010000380 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000381 +705bonus pay for amazing work on #OSS 00010000381 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000382 +705bonus pay for amazing work on #OSS 00010000382 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000383 +705bonus pay for amazing work on #OSS 00010000383 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000384 +705bonus pay for amazing work on #OSS 00010000384 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000385 +705bonus pay for amazing work on #OSS 00010000385 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000386 +705bonus pay for amazing work on #OSS 00010000386 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000387 +705bonus pay for amazing work on #OSS 00010000387 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000388 +705bonus pay for amazing work on #OSS 00010000388 +62223138010481967038518 0000100000#MH6BMjPVo3ZoR#Alexander Moore 1121042880000389 +705bonus pay for amazing work on #OSS 00010000389 +62223138010481967038518 0000100000#6icCe6e1VijvW#Alexander White 1121042880000390 +705bonus pay for amazing work on #OSS 00010000390 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000391 +705bonus pay for amazing work on #OSS 00010000391 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000392 +705bonus pay for amazing work on #OSS 00010000392 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000393 +705bonus pay for amazing work on #OSS 00010000393 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000394 +705bonus pay for amazing work on #OSS 00010000394 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000395 +705bonus pay for amazing work on #OSS 00010000395 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000396 +705bonus pay for amazing work on #OSS 00010000396 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000397 +705bonus pay for amazing work on #OSS 00010000397 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000398 +705bonus pay for amazing work on #OSS 00010000398 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000399 +705bonus pay for amazing work on #OSS 00010000399 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000400 +705bonus pay for amazing work on #OSS 00010000400 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000401 +705bonus pay for amazing work on #OSS 00010000401 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000402 +705bonus pay for amazing work on #OSS 00010000402 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000403 +705bonus pay for amazing work on #OSS 00010000403 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000404 +705bonus pay for amazing work on #OSS 00010000404 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000405 +705bonus pay for amazing work on #OSS 00010000405 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000406 +705bonus pay for amazing work on #OSS 00010000406 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000407 +705bonus pay for amazing work on #OSS 00010000407 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000408 +705bonus pay for amazing work on #OSS 00010000408 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000409 +705bonus pay for amazing work on #OSS 00010000409 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000410 +705bonus pay for amazing work on #OSS 00010000410 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000411 +705bonus pay for amazing work on #OSS 00010000411 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000412 +705bonus pay for amazing work on #OSS 00010000412 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000413 +705bonus pay for amazing work on #OSS 00010000413 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000414 +705bonus pay for amazing work on #OSS 00010000414 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000415 +705bonus pay for amazing work on #OSS 00010000415 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000416 +705bonus pay for amazing work on #OSS 00010000416 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000417 +705bonus pay for amazing work on #OSS 00010000417 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000418 +705bonus pay for amazing work on #OSS 00010000418 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000419 +705bonus pay for amazing work on #OSS 00010000419 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000420 +705bonus pay for amazing work on #OSS 00010000420 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000421 +705bonus pay for amazing work on #OSS 00010000421 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000422 +705bonus pay for amazing work on #OSS 00010000422 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000423 +705bonus pay for amazing work on #OSS 00010000423 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000424 +705bonus pay for amazing work on #OSS 00010000424 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000425 +705bonus pay for amazing work on #OSS 00010000425 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000426 +705bonus pay for amazing work on #OSS 00010000426 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000427 +705bonus pay for amazing work on #OSS 00010000427 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000428 +705bonus pay for amazing work on #OSS 00010000428 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000429 +705bonus pay for amazing work on #OSS 00010000429 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000430 +705bonus pay for amazing work on #OSS 00010000430 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000431 +705bonus pay for amazing work on #OSS 00010000431 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000432 +705bonus pay for amazing work on #OSS 00010000432 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000433 +705bonus pay for amazing work on #OSS 00010000433 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000434 +705bonus pay for amazing work on #OSS 00010000434 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000435 +705bonus pay for amazing work on #OSS 00010000435 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000436 +705bonus pay for amazing work on #OSS 00010000436 +62223138010481967038518 0000100000#6icCe6e1VijvW#Addison White 1121042880000437 +705bonus pay for amazing work on #OSS 00010000437 +62223138010481967038518 0000100000#H80hpwgN6RvCv#Addison Martinez 1121042880000438 +705bonus pay for amazing work on #OSS 00010000438 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000439 +705bonus pay for amazing work on #OSS 00010000439 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000440 +705bonus pay for amazing work on #OSS 00010000440 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000441 +705bonus pay for amazing work on #OSS 00010000441 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000442 +705bonus pay for amazing work on #OSS 00010000442 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000443 +705bonus pay for amazing work on #OSS 00010000443 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000444 +705bonus pay for amazing work on #OSS 00010000444 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000445 +705bonus pay for amazing work on #OSS 00010000445 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000446 +705bonus pay for amazing work on #OSS 00010000446 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000447 +705bonus pay for amazing work on #OSS 00010000447 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000448 +705bonus pay for amazing work on #OSS 00010000448 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000449 +705bonus pay for amazing work on #OSS 00010000449 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000450 +705bonus pay for amazing work on #OSS 00010000450 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000451 +705bonus pay for amazing work on #OSS 00010000451 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000452 +705bonus pay for amazing work on #OSS 00010000452 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000453 +705bonus pay for amazing work on #OSS 00010000453 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000454 +705bonus pay for amazing work on #OSS 00010000454 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000455 +705bonus pay for amazing work on #OSS 00010000455 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000456 +705bonus pay for amazing work on #OSS 00010000456 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000457 +705bonus pay for amazing work on #OSS 00010000457 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000458 +705bonus pay for amazing work on #OSS 00010000458 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000459 +705bonus pay for amazing work on #OSS 00010000459 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000460 +705bonus pay for amazing work on #OSS 00010000460 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000461 +705bonus pay for amazing work on #OSS 00010000461 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000462 +705bonus pay for amazing work on #OSS 00010000462 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000463 +705bonus pay for amazing work on #OSS 00010000463 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000464 +705bonus pay for amazing work on #OSS 00010000464 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000465 +705bonus pay for amazing work on #OSS 00010000465 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000466 +705bonus pay for amazing work on #OSS 00010000466 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000467 +705bonus pay for amazing work on #OSS 00010000467 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000468 +705bonus pay for amazing work on #OSS 00010000468 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000469 +705bonus pay for amazing work on #OSS 00010000469 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000470 +705bonus pay for amazing work on #OSS 00010000470 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000471 +705bonus pay for amazing work on #OSS 00010000471 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000472 +705bonus pay for amazing work on #OSS 00010000472 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000473 +705bonus pay for amazing work on #OSS 00010000473 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000474 +705bonus pay for amazing work on #OSS 00010000474 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000475 +705bonus pay for amazing work on #OSS 00010000475 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000476 +705bonus pay for amazing work on #OSS 00010000476 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000477 +705bonus pay for amazing work on #OSS 00010000477 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000478 +705bonus pay for amazing work on #OSS 00010000478 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000479 +705bonus pay for amazing work on #OSS 00010000479 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000480 +705bonus pay for amazing work on #OSS 00010000480 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000481 +705bonus pay for amazing work on #OSS 00010000481 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000482 +705bonus pay for amazing work on #OSS 00010000482 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000483 +705bonus pay for amazing work on #OSS 00010000483 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000484 +705bonus pay for amazing work on #OSS 00010000484 +62223138010481967038518 0000100000#H80hpwgN6RvCv#David Martinez 1121042880000485 +705bonus pay for amazing work on #OSS 00010000485 +62223138010481967038518 0000100000#KWceVpQPacGbf#David Wilson 1121042880000486 +705bonus pay for amazing work on #OSS 00010000486 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000487 +705bonus pay for amazing work on #OSS 00010000487 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000488 +705bonus pay for amazing work on #OSS 00010000488 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000489 +705bonus pay for amazing work on #OSS 00010000489 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000490 +705bonus pay for amazing work on #OSS 00010000490 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000491 +705bonus pay for amazing work on #OSS 00010000491 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000492 +705bonus pay for amazing work on #OSS 00010000492 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000493 +705bonus pay for amazing work on #OSS 00010000493 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000494 +705bonus pay for amazing work on #OSS 00010000494 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000495 +705bonus pay for amazing work on #OSS 00010000495 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000496 +705bonus pay for amazing work on #OSS 00010000496 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000497 +705bonus pay for amazing work on #OSS 00010000497 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000498 +705bonus pay for amazing work on #OSS 00010000498 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000499 +705bonus pay for amazing work on #OSS 00010000499 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000500 +705bonus pay for amazing work on #OSS 00010000500 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000501 +705bonus pay for amazing work on #OSS 00010000501 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000502 +705bonus pay for amazing work on #OSS 00010000502 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000503 +705bonus pay for amazing work on #OSS 00010000503 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000504 +705bonus pay for amazing work on #OSS 00010000504 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000505 +705bonus pay for amazing work on #OSS 00010000505 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000506 +705bonus pay for amazing work on #OSS 00010000506 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000507 +705bonus pay for amazing work on #OSS 00010000507 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000508 +705bonus pay for amazing work on #OSS 00010000508 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000509 +705bonus pay for amazing work on #OSS 00010000509 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000510 +705bonus pay for amazing work on #OSS 00010000510 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000511 +705bonus pay for amazing work on #OSS 00010000511 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000512 +705bonus pay for amazing work on #OSS 00010000512 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000513 +705bonus pay for amazing work on #OSS 00010000513 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000514 +705bonus pay for amazing work on #OSS 00010000514 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000515 +705bonus pay for amazing work on #OSS 00010000515 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000516 +705bonus pay for amazing work on #OSS 00010000516 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000517 +705bonus pay for amazing work on #OSS 00010000517 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000518 +705bonus pay for amazing work on #OSS 00010000518 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000519 +705bonus pay for amazing work on #OSS 00010000519 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000520 +705bonus pay for amazing work on #OSS 00010000520 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000521 +705bonus pay for amazing work on #OSS 00010000521 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000522 +705bonus pay for amazing work on #OSS 00010000522 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000523 +705bonus pay for amazing work on #OSS 00010000523 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000524 +705bonus pay for amazing work on #OSS 00010000524 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000525 +705bonus pay for amazing work on #OSS 00010000525 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000526 +705bonus pay for amazing work on #OSS 00010000526 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000527 +705bonus pay for amazing work on #OSS 00010000527 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000528 +705bonus pay for amazing work on #OSS 00010000528 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000529 +705bonus pay for amazing work on #OSS 00010000529 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000530 +705bonus pay for amazing work on #OSS 00010000530 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000531 +705bonus pay for amazing work on #OSS 00010000531 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000532 +705bonus pay for amazing work on #OSS 00010000532 +62223138010481967038518 0000100000#KWceVpQPacGbf#Mia Wilson 1121042880000533 +705bonus pay for amazing work on #OSS 00010000533 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Avery Jackson 1121042880000534 +705bonus pay for amazing work on #OSS 00010000534 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000535 +705bonus pay for amazing work on #OSS 00010000535 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000536 +705bonus pay for amazing work on #OSS 00010000536 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000537 +705bonus pay for amazing work on #OSS 00010000537 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000538 +705bonus pay for amazing work on #OSS 00010000538 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000539 +705bonus pay for amazing work on #OSS 00010000539 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000540 +705bonus pay for amazing work on #OSS 00010000540 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000541 +705bonus pay for amazing work on #OSS 00010000541 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000542 +705bonus pay for amazing work on #OSS 00010000542 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000543 +705bonus pay for amazing work on #OSS 00010000543 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000544 +705bonus pay for amazing work on #OSS 00010000544 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000545 +705bonus pay for amazing work on #OSS 00010000545 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000546 +705bonus pay for amazing work on #OSS 00010000546 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000547 +705bonus pay for amazing work on #OSS 00010000547 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000548 +705bonus pay for amazing work on #OSS 00010000548 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000549 +705bonus pay for amazing work on #OSS 00010000549 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000550 +705bonus pay for amazing work on #OSS 00010000550 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000551 +705bonus pay for amazing work on #OSS 00010000551 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000552 +705bonus pay for amazing work on #OSS 00010000552 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000553 +705bonus pay for amazing work on #OSS 00010000553 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000554 +705bonus pay for amazing work on #OSS 00010000554 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000555 +705bonus pay for amazing work on #OSS 00010000555 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000556 +705bonus pay for amazing work on #OSS 00010000556 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000557 +705bonus pay for amazing work on #OSS 00010000557 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000558 +705bonus pay for amazing work on #OSS 00010000558 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000559 +705bonus pay for amazing work on #OSS 00010000559 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000560 +705bonus pay for amazing work on #OSS 00010000560 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000561 +705bonus pay for amazing work on #OSS 00010000561 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000562 +705bonus pay for amazing work on #OSS 00010000562 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000563 +705bonus pay for amazing work on #OSS 00010000563 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000564 +705bonus pay for amazing work on #OSS 00010000564 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000565 +705bonus pay for amazing work on #OSS 00010000565 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000566 +705bonus pay for amazing work on #OSS 00010000566 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000567 +705bonus pay for amazing work on #OSS 00010000567 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000568 +705bonus pay for amazing work on #OSS 00010000568 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000569 +705bonus pay for amazing work on #OSS 00010000569 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000570 +705bonus pay for amazing work on #OSS 00010000570 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000571 +705bonus pay for amazing work on #OSS 00010000571 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000572 +705bonus pay for amazing work on #OSS 00010000572 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000573 +705bonus pay for amazing work on #OSS 00010000573 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000574 +705bonus pay for amazing work on #OSS 00010000574 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000575 +705bonus pay for amazing work on #OSS 00010000575 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000576 +705bonus pay for amazing work on #OSS 00010000576 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000577 +705bonus pay for amazing work on #OSS 00010000577 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000578 +705bonus pay for amazing work on #OSS 00010000578 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000579 +705bonus pay for amazing work on #OSS 00010000579 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000580 +705bonus pay for amazing work on #OSS 00010000580 +62223138010481967038518 0000100000#67zVsGV2WeIvz#Elijah Jackson 1121042880000581 +705bonus pay for amazing work on #OSS 00010000581 +62223138010481967038518 0000100000#48D8RQSKunQt0#Matthew Thomas 1121042880000582 +705bonus pay for amazing work on #OSS 00010000582 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000583 +705bonus pay for amazing work on #OSS 00010000583 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000584 +705bonus pay for amazing work on #OSS 00010000584 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000585 +705bonus pay for amazing work on #OSS 00010000585 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000586 +705bonus pay for amazing work on #OSS 00010000586 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000587 +705bonus pay for amazing work on #OSS 00010000587 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000588 +705bonus pay for amazing work on #OSS 00010000588 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000589 +705bonus pay for amazing work on #OSS 00010000589 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000590 +705bonus pay for amazing work on #OSS 00010000590 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000591 +705bonus pay for amazing work on #OSS 00010000591 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000592 +705bonus pay for amazing work on #OSS 00010000592 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000593 +705bonus pay for amazing work on #OSS 00010000593 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000594 +705bonus pay for amazing work on #OSS 00010000594 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000595 +705bonus pay for amazing work on #OSS 00010000595 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000596 +705bonus pay for amazing work on #OSS 00010000596 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000597 +705bonus pay for amazing work on #OSS 00010000597 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000598 +705bonus pay for amazing work on #OSS 00010000598 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000599 +705bonus pay for amazing work on #OSS 00010000599 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000600 +705bonus pay for amazing work on #OSS 00010000600 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000601 +705bonus pay for amazing work on #OSS 00010000601 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000602 +705bonus pay for amazing work on #OSS 00010000602 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000603 +705bonus pay for amazing work on #OSS 00010000603 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000604 +705bonus pay for amazing work on #OSS 00010000604 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000605 +705bonus pay for amazing work on #OSS 00010000605 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000606 +705bonus pay for amazing work on #OSS 00010000606 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000607 +705bonus pay for amazing work on #OSS 00010000607 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000608 +705bonus pay for amazing work on #OSS 00010000608 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000609 +705bonus pay for amazing work on #OSS 00010000609 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000610 +705bonus pay for amazing work on #OSS 00010000610 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000611 +705bonus pay for amazing work on #OSS 00010000611 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000612 +705bonus pay for amazing work on #OSS 00010000612 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000613 +705bonus pay for amazing work on #OSS 00010000613 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000614 +705bonus pay for amazing work on #OSS 00010000614 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000615 +705bonus pay for amazing work on #OSS 00010000615 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000616 +705bonus pay for amazing work on #OSS 00010000616 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000617 +705bonus pay for amazing work on #OSS 00010000617 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000618 +705bonus pay for amazing work on #OSS 00010000618 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000619 +705bonus pay for amazing work on #OSS 00010000619 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000620 +705bonus pay for amazing work on #OSS 00010000620 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000621 +705bonus pay for amazing work on #OSS 00010000621 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000622 +705bonus pay for amazing work on #OSS 00010000622 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000623 +705bonus pay for amazing work on #OSS 00010000623 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000624 +705bonus pay for amazing work on #OSS 00010000624 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000625 +705bonus pay for amazing work on #OSS 00010000625 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000626 +705bonus pay for amazing work on #OSS 00010000626 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000627 +705bonus pay for amazing work on #OSS 00010000627 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000628 +705bonus pay for amazing work on #OSS 00010000628 +62223138010481967038518 0000100000#48D8RQSKunQt0#Ella Thomas 1121042880000629 +705bonus pay for amazing work on #OSS 00010000629 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000630 +705bonus pay for amazing work on #OSS 00010000630 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000631 +705bonus pay for amazing work on #OSS 00010000631 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000632 +705bonus pay for amazing work on #OSS 00010000632 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000633 +705bonus pay for amazing work on #OSS 00010000633 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000634 +705bonus pay for amazing work on #OSS 00010000634 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000635 +705bonus pay for amazing work on #OSS 00010000635 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000636 +705bonus pay for amazing work on #OSS 00010000636 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000637 +705bonus pay for amazing work on #OSS 00010000637 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000638 +705bonus pay for amazing work on #OSS 00010000638 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000639 +705bonus pay for amazing work on #OSS 00010000639 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000640 +705bonus pay for amazing work on #OSS 00010000640 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000641 +705bonus pay for amazing work on #OSS 00010000641 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000642 +705bonus pay for amazing work on #OSS 00010000642 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000643 +705bonus pay for amazing work on #OSS 00010000643 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000644 +705bonus pay for amazing work on #OSS 00010000644 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000645 +705bonus pay for amazing work on #OSS 00010000645 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000646 +705bonus pay for amazing work on #OSS 00010000646 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000647 +705bonus pay for amazing work on #OSS 00010000647 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000648 +705bonus pay for amazing work on #OSS 00010000648 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000649 +705bonus pay for amazing work on #OSS 00010000649 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000650 +705bonus pay for amazing work on #OSS 00010000650 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000651 +705bonus pay for amazing work on #OSS 00010000651 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000652 +705bonus pay for amazing work on #OSS 00010000652 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000653 +705bonus pay for amazing work on #OSS 00010000653 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000654 +705bonus pay for amazing work on #OSS 00010000654 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000655 +705bonus pay for amazing work on #OSS 00010000655 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000656 +705bonus pay for amazing work on #OSS 00010000656 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000657 +705bonus pay for amazing work on #OSS 00010000657 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000658 +705bonus pay for amazing work on #OSS 00010000658 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000659 +705bonus pay for amazing work on #OSS 00010000659 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000660 +705bonus pay for amazing work on #OSS 00010000660 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000661 +705bonus pay for amazing work on #OSS 00010000661 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000662 +705bonus pay for amazing work on #OSS 00010000662 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000663 +705bonus pay for amazing work on #OSS 00010000663 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000664 +705bonus pay for amazing work on #OSS 00010000664 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000665 +705bonus pay for amazing work on #OSS 00010000665 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000666 +705bonus pay for amazing work on #OSS 00010000666 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000667 +705bonus pay for amazing work on #OSS 00010000667 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000668 +705bonus pay for amazing work on #OSS 00010000668 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000669 +705bonus pay for amazing work on #OSS 00010000669 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000670 +705bonus pay for amazing work on #OSS 00010000670 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000671 +705bonus pay for amazing work on #OSS 00010000671 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000672 +705bonus pay for amazing work on #OSS 00010000672 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000673 +705bonus pay for amazing work on #OSS 00010000673 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000674 +705bonus pay for amazing work on #OSS 00010000674 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000675 +705bonus pay for amazing work on #OSS 00010000675 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000676 +705bonus pay for amazing work on #OSS 00010000676 +62223138010481967038518 0000100000#ktxiT9xpH9dWh#Emma Johnson 1121042880000677 +705bonus pay for amazing work on #OSS 00010000677 +62223138010481967038518 0000100000#8D7NKKP5yG9pt#Daniel Anderson 1121042880000678 +705bonus pay for amazing work on #OSS 00010000678 +62223138010481967038518 0000100000#8D7NKKP5yG9pt#Daniel Anderson 1121042880000679 +705bonus pay for amazing work on #OSS 00010000679 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000680 +705bonus pay for amazing work on #OSS 00010000680 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000681 +705bonus pay for amazing work on #OSS 00010000681 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000682 +705bonus pay for amazing work on #OSS 00010000682 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000683 +705bonus pay for amazing work on #OSS 00010000683 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000684 +705bonus pay for amazing work on #OSS 00010000684 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000685 +705bonus pay for amazing work on #OSS 00010000685 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000686 +705bonus pay for amazing work on #OSS 00010000686 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000687 +705bonus pay for amazing work on #OSS 00010000687 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000688 +705bonus pay for amazing work on #OSS 00010000688 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000689 +705bonus pay for amazing work on #OSS 00010000689 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000690 +705bonus pay for amazing work on #OSS 00010000690 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000691 +705bonus pay for amazing work on #OSS 00010000691 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000692 +705bonus pay for amazing work on #OSS 00010000692 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000693 +705bonus pay for amazing work on #OSS 00010000693 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000694 +705bonus pay for amazing work on #OSS 00010000694 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000695 +705bonus pay for amazing work on #OSS 00010000695 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000696 +705bonus pay for amazing work on #OSS 00010000696 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000697 +705bonus pay for amazing work on #OSS 00010000697 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000698 +705bonus pay for amazing work on #OSS 00010000698 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000699 +705bonus pay for amazing work on #OSS 00010000699 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000700 +705bonus pay for amazing work on #OSS 00010000700 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000701 +705bonus pay for amazing work on #OSS 00010000701 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000702 +705bonus pay for amazing work on #OSS 00010000702 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000703 +705bonus pay for amazing work on #OSS 00010000703 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000704 +705bonus pay for amazing work on #OSS 00010000704 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000705 +705bonus pay for amazing work on #OSS 00010000705 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000706 +705bonus pay for amazing work on #OSS 00010000706 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000707 +705bonus pay for amazing work on #OSS 00010000707 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000708 +705bonus pay for amazing work on #OSS 00010000708 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000709 +705bonus pay for amazing work on #OSS 00010000709 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000710 +705bonus pay for amazing work on #OSS 00010000710 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000711 +705bonus pay for amazing work on #OSS 00010000711 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000712 +705bonus pay for amazing work on #OSS 00010000712 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000713 +705bonus pay for amazing work on #OSS 00010000713 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000714 +705bonus pay for amazing work on #OSS 00010000714 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000715 +705bonus pay for amazing work on #OSS 00010000715 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000716 +705bonus pay for amazing work on #OSS 00010000716 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000717 +705bonus pay for amazing work on #OSS 00010000717 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000718 +705bonus pay for amazing work on #OSS 00010000718 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000719 +705bonus pay for amazing work on #OSS 00010000719 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000720 +705bonus pay for amazing work on #OSS 00010000720 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000721 +705bonus pay for amazing work on #OSS 00010000721 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000722 +705bonus pay for amazing work on #OSS 00010000722 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000723 +705bonus pay for amazing work on #OSS 00010000723 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000724 +705bonus pay for amazing work on #OSS 00010000724 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000725 +705bonus pay for amazing work on #OSS 00010000725 +62223138010481967038518 0000100000#guCNUGWyo0sqJ#Alexander Moore 1121042880000726 +705bonus pay for amazing work on #OSS 00010000726 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000727 +705bonus pay for amazing work on #OSS 00010000727 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000728 +705bonus pay for amazing work on #OSS 00010000728 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000729 +705bonus pay for amazing work on #OSS 00010000729 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000730 +705bonus pay for amazing work on #OSS 00010000730 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000731 +705bonus pay for amazing work on #OSS 00010000731 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000732 +705bonus pay for amazing work on #OSS 00010000732 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000733 +705bonus pay for amazing work on #OSS 00010000733 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000734 +705bonus pay for amazing work on #OSS 00010000734 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000735 +705bonus pay for amazing work on #OSS 00010000735 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000736 +705bonus pay for amazing work on #OSS 00010000736 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000737 +705bonus pay for amazing work on #OSS 00010000737 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000738 +705bonus pay for amazing work on #OSS 00010000738 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000739 +705bonus pay for amazing work on #OSS 00010000739 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000740 +705bonus pay for amazing work on #OSS 00010000740 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000741 +705bonus pay for amazing work on #OSS 00010000741 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000742 +705bonus pay for amazing work on #OSS 00010000742 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000743 +705bonus pay for amazing work on #OSS 00010000743 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000744 +705bonus pay for amazing work on #OSS 00010000744 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000745 +705bonus pay for amazing work on #OSS 00010000745 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000746 +705bonus pay for amazing work on #OSS 00010000746 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000747 +705bonus pay for amazing work on #OSS 00010000747 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000748 +705bonus pay for amazing work on #OSS 00010000748 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000749 +705bonus pay for amazing work on #OSS 00010000749 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000750 +705bonus pay for amazing work on #OSS 00010000750 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000751 +705bonus pay for amazing work on #OSS 00010000751 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000752 +705bonus pay for amazing work on #OSS 00010000752 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000753 +705bonus pay for amazing work on #OSS 00010000753 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000754 +705bonus pay for amazing work on #OSS 00010000754 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000755 +705bonus pay for amazing work on #OSS 00010000755 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000756 +705bonus pay for amazing work on #OSS 00010000756 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000757 +705bonus pay for amazing work on #OSS 00010000757 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000758 +705bonus pay for amazing work on #OSS 00010000758 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000759 +705bonus pay for amazing work on #OSS 00010000759 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000760 +705bonus pay for amazing work on #OSS 00010000760 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000761 +705bonus pay for amazing work on #OSS 00010000761 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000762 +705bonus pay for amazing work on #OSS 00010000762 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000763 +705bonus pay for amazing work on #OSS 00010000763 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000764 +705bonus pay for amazing work on #OSS 00010000764 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000765 +705bonus pay for amazing work on #OSS 00010000765 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000766 +705bonus pay for amazing work on #OSS 00010000766 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000767 +705bonus pay for amazing work on #OSS 00010000767 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000768 +705bonus pay for amazing work on #OSS 00010000768 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000769 +705bonus pay for amazing work on #OSS 00010000769 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000770 +705bonus pay for amazing work on #OSS 00010000770 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000771 +705bonus pay for amazing work on #OSS 00010000771 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000772 +705bonus pay for amazing work on #OSS 00010000772 +62223138010481967038518 0000100000#GR6Ob4BHIONPm#Ella Thomas 1121042880000773 +705bonus pay for amazing work on #OSS 00010000773 +62223138010481967038518 0000100000#lGlkAIAapidNq#Ella Garcia 1121042880000774 +705bonus pay for amazing work on #OSS 00010000774 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000775 +705bonus pay for amazing work on #OSS 00010000775 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000776 +705bonus pay for amazing work on #OSS 00010000776 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000777 +705bonus pay for amazing work on #OSS 00010000777 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000778 +705bonus pay for amazing work on #OSS 00010000778 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000779 +705bonus pay for amazing work on #OSS 00010000779 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000780 +705bonus pay for amazing work on #OSS 00010000780 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000781 +705bonus pay for amazing work on #OSS 00010000781 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000782 +705bonus pay for amazing work on #OSS 00010000782 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000783 +705bonus pay for amazing work on #OSS 00010000783 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000784 +705bonus pay for amazing work on #OSS 00010000784 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000785 +705bonus pay for amazing work on #OSS 00010000785 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000786 +705bonus pay for amazing work on #OSS 00010000786 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000787 +705bonus pay for amazing work on #OSS 00010000787 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000788 +705bonus pay for amazing work on #OSS 00010000788 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000789 +705bonus pay for amazing work on #OSS 00010000789 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000790 +705bonus pay for amazing work on #OSS 00010000790 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000791 +705bonus pay for amazing work on #OSS 00010000791 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000792 +705bonus pay for amazing work on #OSS 00010000792 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000793 +705bonus pay for amazing work on #OSS 00010000793 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000794 +705bonus pay for amazing work on #OSS 00010000794 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000795 +705bonus pay for amazing work on #OSS 00010000795 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000796 +705bonus pay for amazing work on #OSS 00010000796 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000797 +705bonus pay for amazing work on #OSS 00010000797 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000798 +705bonus pay for amazing work on #OSS 00010000798 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000799 +705bonus pay for amazing work on #OSS 00010000799 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000800 +705bonus pay for amazing work on #OSS 00010000800 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000801 +705bonus pay for amazing work on #OSS 00010000801 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000802 +705bonus pay for amazing work on #OSS 00010000802 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000803 +705bonus pay for amazing work on #OSS 00010000803 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000804 +705bonus pay for amazing work on #OSS 00010000804 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000805 +705bonus pay for amazing work on #OSS 00010000805 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000806 +705bonus pay for amazing work on #OSS 00010000806 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000807 +705bonus pay for amazing work on #OSS 00010000807 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000808 +705bonus pay for amazing work on #OSS 00010000808 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000809 +705bonus pay for amazing work on #OSS 00010000809 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000810 +705bonus pay for amazing work on #OSS 00010000810 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000811 +705bonus pay for amazing work on #OSS 00010000811 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000812 +705bonus pay for amazing work on #OSS 00010000812 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000813 +705bonus pay for amazing work on #OSS 00010000813 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000814 +705bonus pay for amazing work on #OSS 00010000814 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000815 +705bonus pay for amazing work on #OSS 00010000815 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000816 +705bonus pay for amazing work on #OSS 00010000816 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000817 +705bonus pay for amazing work on #OSS 00010000817 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000818 +705bonus pay for amazing work on #OSS 00010000818 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000819 +705bonus pay for amazing work on #OSS 00010000819 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000820 +705bonus pay for amazing work on #OSS 00010000820 +62223138010481967038518 0000100000#lGlkAIAapidNq#Sofia Garcia 1121042880000821 +705bonus pay for amazing work on #OSS 00010000821 +62223138010481967038518 0000100000#cL2zLXGthWenB#Sophia Smith 1121042880000822 +705bonus pay for amazing work on #OSS 00010000822 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000823 +705bonus pay for amazing work on #OSS 00010000823 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000824 +705bonus pay for amazing work on #OSS 00010000824 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000825 +705bonus pay for amazing work on #OSS 00010000825 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000826 +705bonus pay for amazing work on #OSS 00010000826 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000827 +705bonus pay for amazing work on #OSS 00010000827 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000828 +705bonus pay for amazing work on #OSS 00010000828 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000829 +705bonus pay for amazing work on #OSS 00010000829 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000830 +705bonus pay for amazing work on #OSS 00010000830 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000831 +705bonus pay for amazing work on #OSS 00010000831 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000832 +705bonus pay for amazing work on #OSS 00010000832 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000833 +705bonus pay for amazing work on #OSS 00010000833 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000834 +705bonus pay for amazing work on #OSS 00010000834 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000835 +705bonus pay for amazing work on #OSS 00010000835 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000836 +705bonus pay for amazing work on #OSS 00010000836 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000837 +705bonus pay for amazing work on #OSS 00010000837 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000838 +705bonus pay for amazing work on #OSS 00010000838 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000839 +705bonus pay for amazing work on #OSS 00010000839 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000840 +705bonus pay for amazing work on #OSS 00010000840 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000841 +705bonus pay for amazing work on #OSS 00010000841 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000842 +705bonus pay for amazing work on #OSS 00010000842 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000843 +705bonus pay for amazing work on #OSS 00010000843 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000844 +705bonus pay for amazing work on #OSS 00010000844 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000845 +705bonus pay for amazing work on #OSS 00010000845 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000846 +705bonus pay for amazing work on #OSS 00010000846 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000847 +705bonus pay for amazing work on #OSS 00010000847 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000848 +705bonus pay for amazing work on #OSS 00010000848 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000849 +705bonus pay for amazing work on #OSS 00010000849 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000850 +705bonus pay for amazing work on #OSS 00010000850 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000851 +705bonus pay for amazing work on #OSS 00010000851 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000852 +705bonus pay for amazing work on #OSS 00010000852 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000853 +705bonus pay for amazing work on #OSS 00010000853 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000854 +705bonus pay for amazing work on #OSS 00010000854 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000855 +705bonus pay for amazing work on #OSS 00010000855 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000856 +705bonus pay for amazing work on #OSS 00010000856 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000857 +705bonus pay for amazing work on #OSS 00010000857 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000858 +705bonus pay for amazing work on #OSS 00010000858 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000859 +705bonus pay for amazing work on #OSS 00010000859 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000860 +705bonus pay for amazing work on #OSS 00010000860 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000861 +705bonus pay for amazing work on #OSS 00010000861 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000862 +705bonus pay for amazing work on #OSS 00010000862 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000863 +705bonus pay for amazing work on #OSS 00010000863 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000864 +705bonus pay for amazing work on #OSS 00010000864 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000865 +705bonus pay for amazing work on #OSS 00010000865 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000866 +705bonus pay for amazing work on #OSS 00010000866 +62223138010481967038518 0000100000#cL2zLXGthWenB#Jacob Smith 1121042880000867 +705bonus pay for amazing work on #OSS 00010000867 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000868 +705bonus pay for amazing work on #OSS 00010000868 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000869 +705bonus pay for amazing work on #OSS 00010000869 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000870 +705bonus pay for amazing work on #OSS 00010000870 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000871 +705bonus pay for amazing work on #OSS 00010000871 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000872 +705bonus pay for amazing work on #OSS 00010000872 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000873 +705bonus pay for amazing work on #OSS 00010000873 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000874 +705bonus pay for amazing work on #OSS 00010000874 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000875 +705bonus pay for amazing work on #OSS 00010000875 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000876 +705bonus pay for amazing work on #OSS 00010000876 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000877 +705bonus pay for amazing work on #OSS 00010000877 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000878 +705bonus pay for amazing work on #OSS 00010000878 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000879 +705bonus pay for amazing work on #OSS 00010000879 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000880 +705bonus pay for amazing work on #OSS 00010000880 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000881 +705bonus pay for amazing work on #OSS 00010000881 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000882 +705bonus pay for amazing work on #OSS 00010000882 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000883 +705bonus pay for amazing work on #OSS 00010000883 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000884 +705bonus pay for amazing work on #OSS 00010000884 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000885 +705bonus pay for amazing work on #OSS 00010000885 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000886 +705bonus pay for amazing work on #OSS 00010000886 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000887 +705bonus pay for amazing work on #OSS 00010000887 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000888 +705bonus pay for amazing work on #OSS 00010000888 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000889 +705bonus pay for amazing work on #OSS 00010000889 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000890 +705bonus pay for amazing work on #OSS 00010000890 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000891 +705bonus pay for amazing work on #OSS 00010000891 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000892 +705bonus pay for amazing work on #OSS 00010000892 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000893 +705bonus pay for amazing work on #OSS 00010000893 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000894 +705bonus pay for amazing work on #OSS 00010000894 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000895 +705bonus pay for amazing work on #OSS 00010000895 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000896 +705bonus pay for amazing work on #OSS 00010000896 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000897 +705bonus pay for amazing work on #OSS 00010000897 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000898 +705bonus pay for amazing work on #OSS 00010000898 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000899 +705bonus pay for amazing work on #OSS 00010000899 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000900 +705bonus pay for amazing work on #OSS 00010000900 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000901 +705bonus pay for amazing work on #OSS 00010000901 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000902 +705bonus pay for amazing work on #OSS 00010000902 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000903 +705bonus pay for amazing work on #OSS 00010000903 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000904 +705bonus pay for amazing work on #OSS 00010000904 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000905 +705bonus pay for amazing work on #OSS 00010000905 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000906 +705bonus pay for amazing work on #OSS 00010000906 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000907 +705bonus pay for amazing work on #OSS 00010000907 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000908 +705bonus pay for amazing work on #OSS 00010000908 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000909 +705bonus pay for amazing work on #OSS 00010000909 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000910 +705bonus pay for amazing work on #OSS 00010000910 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000911 +705bonus pay for amazing work on #OSS 00010000911 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000912 +705bonus pay for amazing work on #OSS 00010000912 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000913 +705bonus pay for amazing work on #OSS 00010000913 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000914 +705bonus pay for amazing work on #OSS 00010000914 +62223138010481967038518 0000100000#V2XlW6LOSAOI4#Ethan Williams 1121042880000915 +705bonus pay for amazing work on #OSS 00010000915 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000916 +705bonus pay for amazing work on #OSS 00010000916 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000917 +705bonus pay for amazing work on #OSS 00010000917 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000918 +705bonus pay for amazing work on #OSS 00010000918 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000919 +705bonus pay for amazing work on #OSS 00010000919 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000920 +705bonus pay for amazing work on #OSS 00010000920 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000921 +705bonus pay for amazing work on #OSS 00010000921 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000922 +705bonus pay for amazing work on #OSS 00010000922 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000923 +705bonus pay for amazing work on #OSS 00010000923 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000924 +705bonus pay for amazing work on #OSS 00010000924 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000925 +705bonus pay for amazing work on #OSS 00010000925 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000926 +705bonus pay for amazing work on #OSS 00010000926 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000927 +705bonus pay for amazing work on #OSS 00010000927 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000928 +705bonus pay for amazing work on #OSS 00010000928 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000929 +705bonus pay for amazing work on #OSS 00010000929 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000930 +705bonus pay for amazing work on #OSS 00010000930 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000931 +705bonus pay for amazing work on #OSS 00010000931 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000932 +705bonus pay for amazing work on #OSS 00010000932 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000933 +705bonus pay for amazing work on #OSS 00010000933 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000934 +705bonus pay for amazing work on #OSS 00010000934 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000935 +705bonus pay for amazing work on #OSS 00010000935 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000936 +705bonus pay for amazing work on #OSS 00010000936 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000937 +705bonus pay for amazing work on #OSS 00010000937 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000938 +705bonus pay for amazing work on #OSS 00010000938 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000939 +705bonus pay for amazing work on #OSS 00010000939 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000940 +705bonus pay for amazing work on #OSS 00010000940 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000941 +705bonus pay for amazing work on #OSS 00010000941 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000942 +705bonus pay for amazing work on #OSS 00010000942 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000943 +705bonus pay for amazing work on #OSS 00010000943 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000944 +705bonus pay for amazing work on #OSS 00010000944 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000945 +705bonus pay for amazing work on #OSS 00010000945 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000946 +705bonus pay for amazing work on #OSS 00010000946 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000947 +705bonus pay for amazing work on #OSS 00010000947 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000948 +705bonus pay for amazing work on #OSS 00010000948 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000949 +705bonus pay for amazing work on #OSS 00010000949 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000950 +705bonus pay for amazing work on #OSS 00010000950 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000951 +705bonus pay for amazing work on #OSS 00010000951 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000952 +705bonus pay for amazing work on #OSS 00010000952 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000953 +705bonus pay for amazing work on #OSS 00010000953 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000954 +705bonus pay for amazing work on #OSS 00010000954 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000955 +705bonus pay for amazing work on #OSS 00010000955 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000956 +705bonus pay for amazing work on #OSS 00010000956 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000957 +705bonus pay for amazing work on #OSS 00010000957 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000958 +705bonus pay for amazing work on #OSS 00010000958 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000959 +705bonus pay for amazing work on #OSS 00010000959 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000960 +705bonus pay for amazing work on #OSS 00010000960 +62223138010481967038518 0000100000#GqLneLsSQniLY#Olivia Jones 1121042880000961 +705bonus pay for amazing work on #OSS 00010000961 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Abigail Miller 1121042880000962 +705bonus pay for amazing work on #OSS 00010000962 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000963 +705bonus pay for amazing work on #OSS 00010000963 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000964 +705bonus pay for amazing work on #OSS 00010000964 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000965 +705bonus pay for amazing work on #OSS 00010000965 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000966 +705bonus pay for amazing work on #OSS 00010000966 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000967 +705bonus pay for amazing work on #OSS 00010000967 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000968 +705bonus pay for amazing work on #OSS 00010000968 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000969 +705bonus pay for amazing work on #OSS 00010000969 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000970 +705bonus pay for amazing work on #OSS 00010000970 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000971 +705bonus pay for amazing work on #OSS 00010000971 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000972 +705bonus pay for amazing work on #OSS 00010000972 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000973 +705bonus pay for amazing work on #OSS 00010000973 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000974 +705bonus pay for amazing work on #OSS 00010000974 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000975 +705bonus pay for amazing work on #OSS 00010000975 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000976 +705bonus pay for amazing work on #OSS 00010000976 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000977 +705bonus pay for amazing work on #OSS 00010000977 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000978 +705bonus pay for amazing work on #OSS 00010000978 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000979 +705bonus pay for amazing work on #OSS 00010000979 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000980 +705bonus pay for amazing work on #OSS 00010000980 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000981 +705bonus pay for amazing work on #OSS 00010000981 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000982 +705bonus pay for amazing work on #OSS 00010000982 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000983 +705bonus pay for amazing work on #OSS 00010000983 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000984 +705bonus pay for amazing work on #OSS 00010000984 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000985 +705bonus pay for amazing work on #OSS 00010000985 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000986 +705bonus pay for amazing work on #OSS 00010000986 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000987 +705bonus pay for amazing work on #OSS 00010000987 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000988 +705bonus pay for amazing work on #OSS 00010000988 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000989 +705bonus pay for amazing work on #OSS 00010000989 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000990 +705bonus pay for amazing work on #OSS 00010000990 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000991 +705bonus pay for amazing work on #OSS 00010000991 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000992 +705bonus pay for amazing work on #OSS 00010000992 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000993 +705bonus pay for amazing work on #OSS 00010000993 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000994 +705bonus pay for amazing work on #OSS 00010000994 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000995 +705bonus pay for amazing work on #OSS 00010000995 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000996 +705bonus pay for amazing work on #OSS 00010000996 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000997 +705bonus pay for amazing work on #OSS 00010000997 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000998 +705bonus pay for amazing work on #OSS 00010000998 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880000999 +705bonus pay for amazing work on #OSS 00010000999 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880001000 +705bonus pay for amazing work on #OSS 00010001000 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880001001 +705bonus pay for amazing work on #OSS 00010001001 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880001002 +705bonus pay for amazing work on #OSS 00010001002 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880001003 +705bonus pay for amazing work on #OSS 00010001003 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880001004 +705bonus pay for amazing work on #OSS 00010001004 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880001005 +705bonus pay for amazing work on #OSS 00010001005 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880001006 +705bonus pay for amazing work on #OSS 00010001006 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880001007 +705bonus pay for amazing work on #OSS 00010001007 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880001008 +705bonus pay for amazing work on #OSS 00010001008 +62223138010481967038518 0000100000#LUnvxWvasQEVp#Jayden Miller 1121042880001009 +705bonus pay for amazing work on #OSS 00010001009 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001010 +705bonus pay for amazing work on #OSS 00010001010 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001011 +705bonus pay for amazing work on #OSS 00010001011 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001012 +705bonus pay for amazing work on #OSS 00010001012 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001013 +705bonus pay for amazing work on #OSS 00010001013 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001014 +705bonus pay for amazing work on #OSS 00010001014 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001015 +705bonus pay for amazing work on #OSS 00010001015 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001016 +705bonus pay for amazing work on #OSS 00010001016 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001017 +705bonus pay for amazing work on #OSS 00010001017 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001018 +705bonus pay for amazing work on #OSS 00010001018 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001019 +705bonus pay for amazing work on #OSS 00010001019 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001020 +705bonus pay for amazing work on #OSS 00010001020 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001021 +705bonus pay for amazing work on #OSS 00010001021 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001022 +705bonus pay for amazing work on #OSS 00010001022 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001023 +705bonus pay for amazing work on #OSS 00010001023 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001024 +705bonus pay for amazing work on #OSS 00010001024 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001025 +705bonus pay for amazing work on #OSS 00010001025 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001026 +705bonus pay for amazing work on #OSS 00010001026 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001027 +705bonus pay for amazing work on #OSS 00010001027 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001028 +705bonus pay for amazing work on #OSS 00010001028 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001029 +705bonus pay for amazing work on #OSS 00010001029 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001030 +705bonus pay for amazing work on #OSS 00010001030 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001031 +705bonus pay for amazing work on #OSS 00010001031 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001032 +705bonus pay for amazing work on #OSS 00010001032 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001033 +705bonus pay for amazing work on #OSS 00010001033 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001034 +705bonus pay for amazing work on #OSS 00010001034 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001035 +705bonus pay for amazing work on #OSS 00010001035 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001036 +705bonus pay for amazing work on #OSS 00010001036 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001037 +705bonus pay for amazing work on #OSS 00010001037 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001038 +705bonus pay for amazing work on #OSS 00010001038 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001039 +705bonus pay for amazing work on #OSS 00010001039 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001040 +705bonus pay for amazing work on #OSS 00010001040 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001041 +705bonus pay for amazing work on #OSS 00010001041 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001042 +705bonus pay for amazing work on #OSS 00010001042 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001043 +705bonus pay for amazing work on #OSS 00010001043 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001044 +705bonus pay for amazing work on #OSS 00010001044 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001045 +705bonus pay for amazing work on #OSS 00010001045 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001046 +705bonus pay for amazing work on #OSS 00010001046 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001047 +705bonus pay for amazing work on #OSS 00010001047 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001048 +705bonus pay for amazing work on #OSS 00010001048 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001049 +705bonus pay for amazing work on #OSS 00010001049 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001050 +705bonus pay for amazing work on #OSS 00010001050 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001051 +705bonus pay for amazing work on #OSS 00010001051 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001052 +705bonus pay for amazing work on #OSS 00010001052 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001053 +705bonus pay for amazing work on #OSS 00010001053 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001054 +705bonus pay for amazing work on #OSS 00010001054 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001055 +705bonus pay for amazing work on #OSS 00010001055 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001056 +705bonus pay for amazing work on #OSS 00010001056 +62223138010481967038518 0000100000#j9YWW2NZE35Wn#Jayden Miller 1121042880001057 +705bonus pay for amazing work on #OSS 00010001057 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001058 +705bonus pay for amazing work on #OSS 00010001058 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001059 +705bonus pay for amazing work on #OSS 00010001059 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001060 +705bonus pay for amazing work on #OSS 00010001060 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001061 +705bonus pay for amazing work on #OSS 00010001061 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001062 +705bonus pay for amazing work on #OSS 00010001062 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001063 +705bonus pay for amazing work on #OSS 00010001063 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001064 +705bonus pay for amazing work on #OSS 00010001064 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001065 +705bonus pay for amazing work on #OSS 00010001065 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001066 +705bonus pay for amazing work on #OSS 00010001066 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001067 +705bonus pay for amazing work on #OSS 00010001067 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001068 +705bonus pay for amazing work on #OSS 00010001068 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001069 +705bonus pay for amazing work on #OSS 00010001069 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001070 +705bonus pay for amazing work on #OSS 00010001070 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001071 +705bonus pay for amazing work on #OSS 00010001071 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001072 +705bonus pay for amazing work on #OSS 00010001072 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001073 +705bonus pay for amazing work on #OSS 00010001073 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001074 +705bonus pay for amazing work on #OSS 00010001074 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001075 +705bonus pay for amazing work on #OSS 00010001075 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001076 +705bonus pay for amazing work on #OSS 00010001076 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001077 +705bonus pay for amazing work on #OSS 00010001077 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001078 +705bonus pay for amazing work on #OSS 00010001078 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001079 +705bonus pay for amazing work on #OSS 00010001079 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001080 +705bonus pay for amazing work on #OSS 00010001080 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001081 +705bonus pay for amazing work on #OSS 00010001081 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001082 +705bonus pay for amazing work on #OSS 00010001082 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001083 +705bonus pay for amazing work on #OSS 00010001083 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001084 +705bonus pay for amazing work on #OSS 00010001084 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001085 +705bonus pay for amazing work on #OSS 00010001085 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001086 +705bonus pay for amazing work on #OSS 00010001086 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001087 +705bonus pay for amazing work on #OSS 00010001087 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001088 +705bonus pay for amazing work on #OSS 00010001088 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001089 +705bonus pay for amazing work on #OSS 00010001089 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001090 +705bonus pay for amazing work on #OSS 00010001090 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001091 +705bonus pay for amazing work on #OSS 00010001091 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001092 +705bonus pay for amazing work on #OSS 00010001092 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001093 +705bonus pay for amazing work on #OSS 00010001093 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001094 +705bonus pay for amazing work on #OSS 00010001094 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001095 +705bonus pay for amazing work on #OSS 00010001095 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001096 +705bonus pay for amazing work on #OSS 00010001096 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001097 +705bonus pay for amazing work on #OSS 00010001097 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001098 +705bonus pay for amazing work on #OSS 00010001098 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001099 +705bonus pay for amazing work on #OSS 00010001099 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001100 +705bonus pay for amazing work on #OSS 00010001100 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001101 +705bonus pay for amazing work on #OSS 00010001101 +62223138010481967038518 0000100000#odOqAKb0mIDOA#Emily Davis 1121042880001102 +705bonus pay for amazing work on #OSS 00010001102 +62223138010481967038518 0000100000#sb41synxZqze1#Emily Wilson 1121042880001103 +705bonus pay for amazing work on #OSS 00010001103 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001104 +705bonus pay for amazing work on #OSS 00010001104 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001105 +705bonus pay for amazing work on #OSS 00010001105 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001106 +705bonus pay for amazing work on #OSS 00010001106 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001107 +705bonus pay for amazing work on #OSS 00010001107 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001108 +705bonus pay for amazing work on #OSS 00010001108 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001109 +705bonus pay for amazing work on #OSS 00010001109 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001110 +705bonus pay for amazing work on #OSS 00010001110 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001111 +705bonus pay for amazing work on #OSS 00010001111 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001112 +705bonus pay for amazing work on #OSS 00010001112 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001113 +705bonus pay for amazing work on #OSS 00010001113 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001114 +705bonus pay for amazing work on #OSS 00010001114 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001115 +705bonus pay for amazing work on #OSS 00010001115 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001116 +705bonus pay for amazing work on #OSS 00010001116 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001117 +705bonus pay for amazing work on #OSS 00010001117 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001118 +705bonus pay for amazing work on #OSS 00010001118 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001119 +705bonus pay for amazing work on #OSS 00010001119 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001120 +705bonus pay for amazing work on #OSS 00010001120 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001121 +705bonus pay for amazing work on #OSS 00010001121 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001122 +705bonus pay for amazing work on #OSS 00010001122 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001123 +705bonus pay for amazing work on #OSS 00010001123 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001124 +705bonus pay for amazing work on #OSS 00010001124 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001125 +705bonus pay for amazing work on #OSS 00010001125 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001126 +705bonus pay for amazing work on #OSS 00010001126 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001127 +705bonus pay for amazing work on #OSS 00010001127 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001128 +705bonus pay for amazing work on #OSS 00010001128 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001129 +705bonus pay for amazing work on #OSS 00010001129 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001130 +705bonus pay for amazing work on #OSS 00010001130 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001131 +705bonus pay for amazing work on #OSS 00010001131 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001132 +705bonus pay for amazing work on #OSS 00010001132 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001133 +705bonus pay for amazing work on #OSS 00010001133 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001134 +705bonus pay for amazing work on #OSS 00010001134 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001135 +705bonus pay for amazing work on #OSS 00010001135 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001136 +705bonus pay for amazing work on #OSS 00010001136 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001137 +705bonus pay for amazing work on #OSS 00010001137 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001138 +705bonus pay for amazing work on #OSS 00010001138 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001139 +705bonus pay for amazing work on #OSS 00010001139 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001140 +705bonus pay for amazing work on #OSS 00010001140 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001141 +705bonus pay for amazing work on #OSS 00010001141 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001142 +705bonus pay for amazing work on #OSS 00010001142 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001143 +705bonus pay for amazing work on #OSS 00010001143 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001144 +705bonus pay for amazing work on #OSS 00010001144 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001145 +705bonus pay for amazing work on #OSS 00010001145 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001146 +705bonus pay for amazing work on #OSS 00010001146 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001147 +705bonus pay for amazing work on #OSS 00010001147 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001148 +705bonus pay for amazing work on #OSS 00010001148 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001149 +705bonus pay for amazing work on #OSS 00010001149 +62223138010481967038518 0000100000#sb41synxZqze1#Mia Wilson 1121042880001150 +705bonus pay for amazing work on #OSS 00010001150 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001151 +705bonus pay for amazing work on #OSS 00010001151 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001152 +705bonus pay for amazing work on #OSS 00010001152 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001153 +705bonus pay for amazing work on #OSS 00010001153 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001154 +705bonus pay for amazing work on #OSS 00010001154 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001155 +705bonus pay for amazing work on #OSS 00010001155 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001156 +705bonus pay for amazing work on #OSS 00010001156 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001157 +705bonus pay for amazing work on #OSS 00010001157 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001158 +705bonus pay for amazing work on #OSS 00010001158 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001159 +705bonus pay for amazing work on #OSS 00010001159 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001160 +705bonus pay for amazing work on #OSS 00010001160 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001161 +705bonus pay for amazing work on #OSS 00010001161 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001162 +705bonus pay for amazing work on #OSS 00010001162 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001163 +705bonus pay for amazing work on #OSS 00010001163 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001164 +705bonus pay for amazing work on #OSS 00010001164 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001165 +705bonus pay for amazing work on #OSS 00010001165 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001166 +705bonus pay for amazing work on #OSS 00010001166 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001167 +705bonus pay for amazing work on #OSS 00010001167 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001168 +705bonus pay for amazing work on #OSS 00010001168 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001169 +705bonus pay for amazing work on #OSS 00010001169 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001170 +705bonus pay for amazing work on #OSS 00010001170 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001171 +705bonus pay for amazing work on #OSS 00010001171 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001172 +705bonus pay for amazing work on #OSS 00010001172 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001173 +705bonus pay for amazing work on #OSS 00010001173 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001174 +705bonus pay for amazing work on #OSS 00010001174 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001175 +705bonus pay for amazing work on #OSS 00010001175 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001176 +705bonus pay for amazing work on #OSS 00010001176 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001177 +705bonus pay for amazing work on #OSS 00010001177 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001178 +705bonus pay for amazing work on #OSS 00010001178 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001179 +705bonus pay for amazing work on #OSS 00010001179 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001180 +705bonus pay for amazing work on #OSS 00010001180 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001181 +705bonus pay for amazing work on #OSS 00010001181 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001182 +705bonus pay for amazing work on #OSS 00010001182 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001183 +705bonus pay for amazing work on #OSS 00010001183 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001184 +705bonus pay for amazing work on #OSS 00010001184 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001185 +705bonus pay for amazing work on #OSS 00010001185 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001186 +705bonus pay for amazing work on #OSS 00010001186 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001187 +705bonus pay for amazing work on #OSS 00010001187 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001188 +705bonus pay for amazing work on #OSS 00010001188 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001189 +705bonus pay for amazing work on #OSS 00010001189 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001190 +705bonus pay for amazing work on #OSS 00010001190 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001191 +705bonus pay for amazing work on #OSS 00010001191 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001192 +705bonus pay for amazing work on #OSS 00010001192 +62223138010481967038518 0000100000#mDuzsBSFnomN9#Jacob Smith 1121042880001193 +705bonus pay for amazing work on #OSS 00010001193 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001194 +705bonus pay for amazing work on #OSS 00010001194 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001195 +705bonus pay for amazing work on #OSS 00010001195 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001196 +705bonus pay for amazing work on #OSS 00010001196 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001197 +705bonus pay for amazing work on #OSS 00010001197 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001198 +705bonus pay for amazing work on #OSS 00010001198 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001199 +705bonus pay for amazing work on #OSS 00010001199 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001200 +705bonus pay for amazing work on #OSS 00010001200 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001201 +705bonus pay for amazing work on #OSS 00010001201 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001202 +705bonus pay for amazing work on #OSS 00010001202 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001203 +705bonus pay for amazing work on #OSS 00010001203 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001204 +705bonus pay for amazing work on #OSS 00010001204 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001205 +705bonus pay for amazing work on #OSS 00010001205 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001206 +705bonus pay for amazing work on #OSS 00010001206 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001207 +705bonus pay for amazing work on #OSS 00010001207 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001208 +705bonus pay for amazing work on #OSS 00010001208 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001209 +705bonus pay for amazing work on #OSS 00010001209 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001210 +705bonus pay for amazing work on #OSS 00010001210 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001211 +705bonus pay for amazing work on #OSS 00010001211 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001212 +705bonus pay for amazing work on #OSS 00010001212 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001213 +705bonus pay for amazing work on #OSS 00010001213 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001214 +705bonus pay for amazing work on #OSS 00010001214 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001215 +705bonus pay for amazing work on #OSS 00010001215 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001216 +705bonus pay for amazing work on #OSS 00010001216 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001217 +705bonus pay for amazing work on #OSS 00010001217 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001218 +705bonus pay for amazing work on #OSS 00010001218 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001219 +705bonus pay for amazing work on #OSS 00010001219 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001220 +705bonus pay for amazing work on #OSS 00010001220 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001221 +705bonus pay for amazing work on #OSS 00010001221 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001222 +705bonus pay for amazing work on #OSS 00010001222 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001223 +705bonus pay for amazing work on #OSS 00010001223 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001224 +705bonus pay for amazing work on #OSS 00010001224 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001225 +705bonus pay for amazing work on #OSS 00010001225 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001226 +705bonus pay for amazing work on #OSS 00010001226 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001227 +705bonus pay for amazing work on #OSS 00010001227 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001228 +705bonus pay for amazing work on #OSS 00010001228 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001229 +705bonus pay for amazing work on #OSS 00010001229 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001230 +705bonus pay for amazing work on #OSS 00010001230 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001231 +705bonus pay for amazing work on #OSS 00010001231 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001232 +705bonus pay for amazing work on #OSS 00010001232 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001233 +705bonus pay for amazing work on #OSS 00010001233 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001234 +705bonus pay for amazing work on #OSS 00010001234 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001235 +705bonus pay for amazing work on #OSS 00010001235 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001236 +705bonus pay for amazing work on #OSS 00010001236 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001237 +705bonus pay for amazing work on #OSS 00010001237 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001238 +705bonus pay for amazing work on #OSS 00010001238 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001239 +705bonus pay for amazing work on #OSS 00010001239 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001240 +705bonus pay for amazing work on #OSS 00010001240 +62223138010481967038518 0000100000#1QywIDvT0IuMR#Anthony Harris 1121042880001241 +705bonus pay for amazing work on #OSS 00010001241 +62223138010481967038518 0000100000#Ozgz87ZccMqeg#Olivia Jones 1121042880001242 +705bonus pay for amazing work on #OSS 00010001242 +62223138010481967038518 0000100000#Ozgz87ZccMqeg#Olivia Jones 1121042880001243 +705bonus pay for amazing work on #OSS 00010001243 +62223138010481967038518 0000100000#Ozgz87ZccMqeg#Olivia Jones 1121042880001244 +705bonus pay for amazing work on #OSS 00010001244 +62223138010481967038518 0000100000#Ozgz87ZccMqeg#Olivia Jones 1121042880001245 +705bonus pay for amazing work on #OSS 00010001245 +62223138010481967038518 0000100000#Ozgz87ZccMqeg#Olivia Jones 1121042880001246 +705bonus pay for amazing work on #OSS 00010001246 +62223138010481967038518 0000100000#Ozgz87ZccMqeg#Olivia Jones 1121042880001247 +705bonus pay for amazing work on #OSS 00010001247 +62223138010481967038518 0000100000#Ozgz87ZccMqeg#Olivia Jones 1121042880001248 +705bonus pay for amazing work on #OSS 00010001248 +62223138010481967038518 0000100000#Ozgz87ZccMqeg#Olivia Jones 1121042880001249 +705bonus pay for amazing work on #OSS 00010001249 +62223138010481967038518 0000100000#Ozgz87ZccMqeg#Olivia Jones 1121042880001250 +705bonus pay for amazing work on #OSS 00010001250 +82000025008922512500000000000000000125000000121042882 121042880000004 +9000004001001000100005690050000000000000000000500000000 diff --git a/cmd/writeACH/201805101355.ach b/cmd/writeACH/201805101355.ach new file mode 100644 index 000000000..3ebc86628 --- /dev/null +++ b/cmd/writeACH/201805101355.ach @@ -0,0 +1,10010 @@ +101 231380104 1210428821805100000A094101Citadel Wells Fargo +5200Wells Fargo 121042882 PPDTrans. Des 180511 0121042880000001 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000001 +705bonus pay for amazing work on #OSS 00010000001 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000002 +705bonus pay for amazing work on #OSS 00010000002 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000003 +705bonus pay for amazing work on #OSS 00010000003 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000004 +705bonus pay for amazing work on #OSS 00010000004 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000005 +705bonus pay for amazing work on #OSS 00010000005 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000006 +705bonus pay for amazing work on #OSS 00010000006 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000007 +705bonus pay for amazing work on #OSS 00010000007 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000008 +705bonus pay for amazing work on #OSS 00010000008 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000009 +705bonus pay for amazing work on #OSS 00010000009 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000010 +705bonus pay for amazing work on #OSS 00010000010 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000011 +705bonus pay for amazing work on #OSS 00010000011 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000012 +705bonus pay for amazing work on #OSS 00010000012 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000013 +705bonus pay for amazing work on #OSS 00010000013 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000014 +705bonus pay for amazing work on #OSS 00010000014 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000015 +705bonus pay for amazing work on #OSS 00010000015 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000016 +705bonus pay for amazing work on #OSS 00010000016 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000017 +705bonus pay for amazing work on #OSS 00010000017 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000018 +705bonus pay for amazing work on #OSS 00010000018 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000019 +705bonus pay for amazing work on #OSS 00010000019 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000020 +705bonus pay for amazing work on #OSS 00010000020 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000021 +705bonus pay for amazing work on #OSS 00010000021 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000022 +705bonus pay for amazing work on #OSS 00010000022 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000023 +705bonus pay for amazing work on #OSS 00010000023 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000024 +705bonus pay for amazing work on #OSS 00010000024 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000025 +705bonus pay for amazing work on #OSS 00010000025 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000026 +705bonus pay for amazing work on #OSS 00010000026 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000027 +705bonus pay for amazing work on #OSS 00010000027 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000028 +705bonus pay for amazing work on #OSS 00010000028 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000029 +705bonus pay for amazing work on #OSS 00010000029 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000030 +705bonus pay for amazing work on #OSS 00010000030 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000031 +705bonus pay for amazing work on #OSS 00010000031 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000032 +705bonus pay for amazing work on #OSS 00010000032 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000033 +705bonus pay for amazing work on #OSS 00010000033 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000034 +705bonus pay for amazing work on #OSS 00010000034 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000035 +705bonus pay for amazing work on #OSS 00010000035 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000036 +705bonus pay for amazing work on #OSS 00010000036 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000037 +705bonus pay for amazing work on #OSS 00010000037 +62223138010481967038518 0000100000#RCP2bSsZWeCCE#Alexander Moore 1121042880000038 +705bonus pay for amazing work on #OSS 00010000038 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Alexander White 1121042880000039 +705bonus pay for amazing work on #OSS 00010000039 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000040 +705bonus pay for amazing work on #OSS 00010000040 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000041 +705bonus pay for amazing work on #OSS 00010000041 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000042 +705bonus pay for amazing work on #OSS 00010000042 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000043 +705bonus pay for amazing work on #OSS 00010000043 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000044 +705bonus pay for amazing work on #OSS 00010000044 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000045 +705bonus pay for amazing work on #OSS 00010000045 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000046 +705bonus pay for amazing work on #OSS 00010000046 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000047 +705bonus pay for amazing work on #OSS 00010000047 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000048 +705bonus pay for amazing work on #OSS 00010000048 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000049 +705bonus pay for amazing work on #OSS 00010000049 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000050 +705bonus pay for amazing work on #OSS 00010000050 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000051 +705bonus pay for amazing work on #OSS 00010000051 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000052 +705bonus pay for amazing work on #OSS 00010000052 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000053 +705bonus pay for amazing work on #OSS 00010000053 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000054 +705bonus pay for amazing work on #OSS 00010000054 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000055 +705bonus pay for amazing work on #OSS 00010000055 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000056 +705bonus pay for amazing work on #OSS 00010000056 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000057 +705bonus pay for amazing work on #OSS 00010000057 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000058 +705bonus pay for amazing work on #OSS 00010000058 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000059 +705bonus pay for amazing work on #OSS 00010000059 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000060 +705bonus pay for amazing work on #OSS 00010000060 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000061 +705bonus pay for amazing work on #OSS 00010000061 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000062 +705bonus pay for amazing work on #OSS 00010000062 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000063 +705bonus pay for amazing work on #OSS 00010000063 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000064 +705bonus pay for amazing work on #OSS 00010000064 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000065 +705bonus pay for amazing work on #OSS 00010000065 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000066 +705bonus pay for amazing work on #OSS 00010000066 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000067 +705bonus pay for amazing work on #OSS 00010000067 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000068 +705bonus pay for amazing work on #OSS 00010000068 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000069 +705bonus pay for amazing work on #OSS 00010000069 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000070 +705bonus pay for amazing work on #OSS 00010000070 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000071 +705bonus pay for amazing work on #OSS 00010000071 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000072 +705bonus pay for amazing work on #OSS 00010000072 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000073 +705bonus pay for amazing work on #OSS 00010000073 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000074 +705bonus pay for amazing work on #OSS 00010000074 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000075 +705bonus pay for amazing work on #OSS 00010000075 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000076 +705bonus pay for amazing work on #OSS 00010000076 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000077 +705bonus pay for amazing work on #OSS 00010000077 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000078 +705bonus pay for amazing work on #OSS 00010000078 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000079 +705bonus pay for amazing work on #OSS 00010000079 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000080 +705bonus pay for amazing work on #OSS 00010000080 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000081 +705bonus pay for amazing work on #OSS 00010000081 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000082 +705bonus pay for amazing work on #OSS 00010000082 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000083 +705bonus pay for amazing work on #OSS 00010000083 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000084 +705bonus pay for amazing work on #OSS 00010000084 +62223138010481967038518 0000100000#0wvMj6gEc2YVZ#Addison White 1121042880000085 +705bonus pay for amazing work on #OSS 00010000085 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000086 +705bonus pay for amazing work on #OSS 00010000086 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000087 +705bonus pay for amazing work on #OSS 00010000087 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000088 +705bonus pay for amazing work on #OSS 00010000088 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000089 +705bonus pay for amazing work on #OSS 00010000089 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000090 +705bonus pay for amazing work on #OSS 00010000090 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000091 +705bonus pay for amazing work on #OSS 00010000091 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000092 +705bonus pay for amazing work on #OSS 00010000092 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000093 +705bonus pay for amazing work on #OSS 00010000093 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000094 +705bonus pay for amazing work on #OSS 00010000094 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000095 +705bonus pay for amazing work on #OSS 00010000095 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000096 +705bonus pay for amazing work on #OSS 00010000096 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000097 +705bonus pay for amazing work on #OSS 00010000097 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000098 +705bonus pay for amazing work on #OSS 00010000098 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000099 +705bonus pay for amazing work on #OSS 00010000099 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000100 +705bonus pay for amazing work on #OSS 00010000100 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000101 +705bonus pay for amazing work on #OSS 00010000101 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000102 +705bonus pay for amazing work on #OSS 00010000102 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000103 +705bonus pay for amazing work on #OSS 00010000103 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000104 +705bonus pay for amazing work on #OSS 00010000104 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000105 +705bonus pay for amazing work on #OSS 00010000105 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000106 +705bonus pay for amazing work on #OSS 00010000106 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000107 +705bonus pay for amazing work on #OSS 00010000107 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000108 +705bonus pay for amazing work on #OSS 00010000108 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000109 +705bonus pay for amazing work on #OSS 00010000109 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000110 +705bonus pay for amazing work on #OSS 00010000110 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000111 +705bonus pay for amazing work on #OSS 00010000111 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000112 +705bonus pay for amazing work on #OSS 00010000112 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000113 +705bonus pay for amazing work on #OSS 00010000113 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000114 +705bonus pay for amazing work on #OSS 00010000114 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000115 +705bonus pay for amazing work on #OSS 00010000115 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000116 +705bonus pay for amazing work on #OSS 00010000116 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000117 +705bonus pay for amazing work on #OSS 00010000117 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000118 +705bonus pay for amazing work on #OSS 00010000118 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000119 +705bonus pay for amazing work on #OSS 00010000119 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000120 +705bonus pay for amazing work on #OSS 00010000120 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000121 +705bonus pay for amazing work on #OSS 00010000121 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000122 +705bonus pay for amazing work on #OSS 00010000122 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000123 +705bonus pay for amazing work on #OSS 00010000123 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000124 +705bonus pay for amazing work on #OSS 00010000124 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000125 +705bonus pay for amazing work on #OSS 00010000125 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000126 +705bonus pay for amazing work on #OSS 00010000126 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000127 +705bonus pay for amazing work on #OSS 00010000127 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000128 +705bonus pay for amazing work on #OSS 00010000128 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000129 +705bonus pay for amazing work on #OSS 00010000129 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000130 +705bonus pay for amazing work on #OSS 00010000130 +62223138010481967038518 0000100000#PdeE1oGEvIt9B#Elijah Jackson 1121042880000131 +705bonus pay for amazing work on #OSS 00010000131 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000132 +705bonus pay for amazing work on #OSS 00010000132 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000133 +705bonus pay for amazing work on #OSS 00010000133 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000134 +705bonus pay for amazing work on #OSS 00010000134 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000135 +705bonus pay for amazing work on #OSS 00010000135 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000136 +705bonus pay for amazing work on #OSS 00010000136 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000137 +705bonus pay for amazing work on #OSS 00010000137 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000138 +705bonus pay for amazing work on #OSS 00010000138 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000139 +705bonus pay for amazing work on #OSS 00010000139 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000140 +705bonus pay for amazing work on #OSS 00010000140 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000141 +705bonus pay for amazing work on #OSS 00010000141 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000142 +705bonus pay for amazing work on #OSS 00010000142 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000143 +705bonus pay for amazing work on #OSS 00010000143 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000144 +705bonus pay for amazing work on #OSS 00010000144 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000145 +705bonus pay for amazing work on #OSS 00010000145 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000146 +705bonus pay for amazing work on #OSS 00010000146 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000147 +705bonus pay for amazing work on #OSS 00010000147 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000148 +705bonus pay for amazing work on #OSS 00010000148 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000149 +705bonus pay for amazing work on #OSS 00010000149 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000150 +705bonus pay for amazing work on #OSS 00010000150 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000151 +705bonus pay for amazing work on #OSS 00010000151 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000152 +705bonus pay for amazing work on #OSS 00010000152 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000153 +705bonus pay for amazing work on #OSS 00010000153 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000154 +705bonus pay for amazing work on #OSS 00010000154 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000155 +705bonus pay for amazing work on #OSS 00010000155 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000156 +705bonus pay for amazing work on #OSS 00010000156 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000157 +705bonus pay for amazing work on #OSS 00010000157 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000158 +705bonus pay for amazing work on #OSS 00010000158 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000159 +705bonus pay for amazing work on #OSS 00010000159 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000160 +705bonus pay for amazing work on #OSS 00010000160 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000161 +705bonus pay for amazing work on #OSS 00010000161 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000162 +705bonus pay for amazing work on #OSS 00010000162 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000163 +705bonus pay for amazing work on #OSS 00010000163 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000164 +705bonus pay for amazing work on #OSS 00010000164 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000165 +705bonus pay for amazing work on #OSS 00010000165 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000166 +705bonus pay for amazing work on #OSS 00010000166 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000167 +705bonus pay for amazing work on #OSS 00010000167 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000168 +705bonus pay for amazing work on #OSS 00010000168 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000169 +705bonus pay for amazing work on #OSS 00010000169 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000170 +705bonus pay for amazing work on #OSS 00010000170 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000171 +705bonus pay for amazing work on #OSS 00010000171 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000172 +705bonus pay for amazing work on #OSS 00010000172 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000173 +705bonus pay for amazing work on #OSS 00010000173 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000174 +705bonus pay for amazing work on #OSS 00010000174 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000175 +705bonus pay for amazing work on #OSS 00010000175 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000176 +705bonus pay for amazing work on #OSS 00010000176 +62223138010481967038518 0000100000#DeKJU7RcZL5Sc#Elizabeth Taylor 1121042880000177 +705bonus pay for amazing work on #OSS 00010000177 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Natalie Thompson 1121042880000178 +705bonus pay for amazing work on #OSS 00010000178 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000179 +705bonus pay for amazing work on #OSS 00010000179 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000180 +705bonus pay for amazing work on #OSS 00010000180 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000181 +705bonus pay for amazing work on #OSS 00010000181 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000182 +705bonus pay for amazing work on #OSS 00010000182 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000183 +705bonus pay for amazing work on #OSS 00010000183 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000184 +705bonus pay for amazing work on #OSS 00010000184 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000185 +705bonus pay for amazing work on #OSS 00010000185 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000186 +705bonus pay for amazing work on #OSS 00010000186 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000187 +705bonus pay for amazing work on #OSS 00010000187 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000188 +705bonus pay for amazing work on #OSS 00010000188 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000189 +705bonus pay for amazing work on #OSS 00010000189 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000190 +705bonus pay for amazing work on #OSS 00010000190 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000191 +705bonus pay for amazing work on #OSS 00010000191 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000192 +705bonus pay for amazing work on #OSS 00010000192 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000193 +705bonus pay for amazing work on #OSS 00010000193 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000194 +705bonus pay for amazing work on #OSS 00010000194 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000195 +705bonus pay for amazing work on #OSS 00010000195 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000196 +705bonus pay for amazing work on #OSS 00010000196 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000197 +705bonus pay for amazing work on #OSS 00010000197 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000198 +705bonus pay for amazing work on #OSS 00010000198 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000199 +705bonus pay for amazing work on #OSS 00010000199 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000200 +705bonus pay for amazing work on #OSS 00010000200 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000201 +705bonus pay for amazing work on #OSS 00010000201 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000202 +705bonus pay for amazing work on #OSS 00010000202 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000203 +705bonus pay for amazing work on #OSS 00010000203 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000204 +705bonus pay for amazing work on #OSS 00010000204 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000205 +705bonus pay for amazing work on #OSS 00010000205 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000206 +705bonus pay for amazing work on #OSS 00010000206 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000207 +705bonus pay for amazing work on #OSS 00010000207 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000208 +705bonus pay for amazing work on #OSS 00010000208 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000209 +705bonus pay for amazing work on #OSS 00010000209 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000210 +705bonus pay for amazing work on #OSS 00010000210 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000211 +705bonus pay for amazing work on #OSS 00010000211 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000212 +705bonus pay for amazing work on #OSS 00010000212 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000213 +705bonus pay for amazing work on #OSS 00010000213 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000214 +705bonus pay for amazing work on #OSS 00010000214 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000215 +705bonus pay for amazing work on #OSS 00010000215 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000216 +705bonus pay for amazing work on #OSS 00010000216 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000217 +705bonus pay for amazing work on #OSS 00010000217 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000218 +705bonus pay for amazing work on #OSS 00010000218 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000219 +705bonus pay for amazing work on #OSS 00010000219 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000220 +705bonus pay for amazing work on #OSS 00010000220 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000221 +705bonus pay for amazing work on #OSS 00010000221 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000222 +705bonus pay for amazing work on #OSS 00010000222 +62223138010481967038518 0000100000#RqC9kaErtyTXc#Joshua Thompson 1121042880000223 +705bonus pay for amazing work on #OSS 00010000223 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Joshua Davis 1121042880000224 +705bonus pay for amazing work on #OSS 00010000224 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000225 +705bonus pay for amazing work on #OSS 00010000225 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000226 +705bonus pay for amazing work on #OSS 00010000226 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000227 +705bonus pay for amazing work on #OSS 00010000227 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000228 +705bonus pay for amazing work on #OSS 00010000228 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000229 +705bonus pay for amazing work on #OSS 00010000229 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000230 +705bonus pay for amazing work on #OSS 00010000230 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000231 +705bonus pay for amazing work on #OSS 00010000231 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000232 +705bonus pay for amazing work on #OSS 00010000232 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000233 +705bonus pay for amazing work on #OSS 00010000233 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000234 +705bonus pay for amazing work on #OSS 00010000234 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000235 +705bonus pay for amazing work on #OSS 00010000235 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000236 +705bonus pay for amazing work on #OSS 00010000236 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000237 +705bonus pay for amazing work on #OSS 00010000237 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000238 +705bonus pay for amazing work on #OSS 00010000238 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000239 +705bonus pay for amazing work on #OSS 00010000239 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000240 +705bonus pay for amazing work on #OSS 00010000240 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000241 +705bonus pay for amazing work on #OSS 00010000241 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000242 +705bonus pay for amazing work on #OSS 00010000242 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000243 +705bonus pay for amazing work on #OSS 00010000243 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000244 +705bonus pay for amazing work on #OSS 00010000244 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000245 +705bonus pay for amazing work on #OSS 00010000245 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000246 +705bonus pay for amazing work on #OSS 00010000246 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000247 +705bonus pay for amazing work on #OSS 00010000247 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000248 +705bonus pay for amazing work on #OSS 00010000248 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000249 +705bonus pay for amazing work on #OSS 00010000249 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000250 +705bonus pay for amazing work on #OSS 00010000250 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000251 +705bonus pay for amazing work on #OSS 00010000251 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000252 +705bonus pay for amazing work on #OSS 00010000252 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000253 +705bonus pay for amazing work on #OSS 00010000253 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000254 +705bonus pay for amazing work on #OSS 00010000254 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000255 +705bonus pay for amazing work on #OSS 00010000255 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000256 +705bonus pay for amazing work on #OSS 00010000256 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000257 +705bonus pay for amazing work on #OSS 00010000257 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000258 +705bonus pay for amazing work on #OSS 00010000258 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000259 +705bonus pay for amazing work on #OSS 00010000259 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000260 +705bonus pay for amazing work on #OSS 00010000260 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000261 +705bonus pay for amazing work on #OSS 00010000261 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000262 +705bonus pay for amazing work on #OSS 00010000262 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000263 +705bonus pay for amazing work on #OSS 00010000263 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000264 +705bonus pay for amazing work on #OSS 00010000264 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000265 +705bonus pay for amazing work on #OSS 00010000265 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000266 +705bonus pay for amazing work on #OSS 00010000266 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000267 +705bonus pay for amazing work on #OSS 00010000267 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000268 +705bonus pay for amazing work on #OSS 00010000268 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000269 +705bonus pay for amazing work on #OSS 00010000269 +62223138010481967038518 0000100000#zncFCSsFIQjLZ#Emily Davis 1121042880000270 +705bonus pay for amazing work on #OSS 00010000270 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000271 +705bonus pay for amazing work on #OSS 00010000271 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000272 +705bonus pay for amazing work on #OSS 00010000272 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000273 +705bonus pay for amazing work on #OSS 00010000273 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000274 +705bonus pay for amazing work on #OSS 00010000274 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000275 +705bonus pay for amazing work on #OSS 00010000275 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000276 +705bonus pay for amazing work on #OSS 00010000276 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000277 +705bonus pay for amazing work on #OSS 00010000277 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000278 +705bonus pay for amazing work on #OSS 00010000278 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000279 +705bonus pay for amazing work on #OSS 00010000279 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000280 +705bonus pay for amazing work on #OSS 00010000280 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000281 +705bonus pay for amazing work on #OSS 00010000281 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000282 +705bonus pay for amazing work on #OSS 00010000282 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000283 +705bonus pay for amazing work on #OSS 00010000283 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000284 +705bonus pay for amazing work on #OSS 00010000284 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000285 +705bonus pay for amazing work on #OSS 00010000285 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000286 +705bonus pay for amazing work on #OSS 00010000286 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000287 +705bonus pay for amazing work on #OSS 00010000287 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000288 +705bonus pay for amazing work on #OSS 00010000288 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000289 +705bonus pay for amazing work on #OSS 00010000289 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000290 +705bonus pay for amazing work on #OSS 00010000290 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000291 +705bonus pay for amazing work on #OSS 00010000291 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000292 +705bonus pay for amazing work on #OSS 00010000292 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000293 +705bonus pay for amazing work on #OSS 00010000293 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000294 +705bonus pay for amazing work on #OSS 00010000294 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000295 +705bonus pay for amazing work on #OSS 00010000295 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000296 +705bonus pay for amazing work on #OSS 00010000296 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000297 +705bonus pay for amazing work on #OSS 00010000297 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000298 +705bonus pay for amazing work on #OSS 00010000298 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000299 +705bonus pay for amazing work on #OSS 00010000299 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000300 +705bonus pay for amazing work on #OSS 00010000300 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000301 +705bonus pay for amazing work on #OSS 00010000301 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000302 +705bonus pay for amazing work on #OSS 00010000302 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000303 +705bonus pay for amazing work on #OSS 00010000303 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000304 +705bonus pay for amazing work on #OSS 00010000304 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000305 +705bonus pay for amazing work on #OSS 00010000305 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000306 +705bonus pay for amazing work on #OSS 00010000306 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000307 +705bonus pay for amazing work on #OSS 00010000307 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000308 +705bonus pay for amazing work on #OSS 00010000308 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000309 +705bonus pay for amazing work on #OSS 00010000309 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000310 +705bonus pay for amazing work on #OSS 00010000310 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000311 +705bonus pay for amazing work on #OSS 00010000311 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000312 +705bonus pay for amazing work on #OSS 00010000312 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000313 +705bonus pay for amazing work on #OSS 00010000313 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000314 +705bonus pay for amazing work on #OSS 00010000314 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000315 +705bonus pay for amazing work on #OSS 00010000315 +62223138010481967038518 0000100000#GeqMftiuLza0G#Lily Martin 1121042880000316 +705bonus pay for amazing work on #OSS 00010000316 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000317 +705bonus pay for amazing work on #OSS 00010000317 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000318 +705bonus pay for amazing work on #OSS 00010000318 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000319 +705bonus pay for amazing work on #OSS 00010000319 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000320 +705bonus pay for amazing work on #OSS 00010000320 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000321 +705bonus pay for amazing work on #OSS 00010000321 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000322 +705bonus pay for amazing work on #OSS 00010000322 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000323 +705bonus pay for amazing work on #OSS 00010000323 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000324 +705bonus pay for amazing work on #OSS 00010000324 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000325 +705bonus pay for amazing work on #OSS 00010000325 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000326 +705bonus pay for amazing work on #OSS 00010000326 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000327 +705bonus pay for amazing work on #OSS 00010000327 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000328 +705bonus pay for amazing work on #OSS 00010000328 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000329 +705bonus pay for amazing work on #OSS 00010000329 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000330 +705bonus pay for amazing work on #OSS 00010000330 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000331 +705bonus pay for amazing work on #OSS 00010000331 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000332 +705bonus pay for amazing work on #OSS 00010000332 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000333 +705bonus pay for amazing work on #OSS 00010000333 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000334 +705bonus pay for amazing work on #OSS 00010000334 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000335 +705bonus pay for amazing work on #OSS 00010000335 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000336 +705bonus pay for amazing work on #OSS 00010000336 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000337 +705bonus pay for amazing work on #OSS 00010000337 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000338 +705bonus pay for amazing work on #OSS 00010000338 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000339 +705bonus pay for amazing work on #OSS 00010000339 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000340 +705bonus pay for amazing work on #OSS 00010000340 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000341 +705bonus pay for amazing work on #OSS 00010000341 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000342 +705bonus pay for amazing work on #OSS 00010000342 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000343 +705bonus pay for amazing work on #OSS 00010000343 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000344 +705bonus pay for amazing work on #OSS 00010000344 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000345 +705bonus pay for amazing work on #OSS 00010000345 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000346 +705bonus pay for amazing work on #OSS 00010000346 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000347 +705bonus pay for amazing work on #OSS 00010000347 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000348 +705bonus pay for amazing work on #OSS 00010000348 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000349 +705bonus pay for amazing work on #OSS 00010000349 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000350 +705bonus pay for amazing work on #OSS 00010000350 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000351 +705bonus pay for amazing work on #OSS 00010000351 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000352 +705bonus pay for amazing work on #OSS 00010000352 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000353 +705bonus pay for amazing work on #OSS 00010000353 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000354 +705bonus pay for amazing work on #OSS 00010000354 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000355 +705bonus pay for amazing work on #OSS 00010000355 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000356 +705bonus pay for amazing work on #OSS 00010000356 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000357 +705bonus pay for amazing work on #OSS 00010000357 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000358 +705bonus pay for amazing work on #OSS 00010000358 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000359 +705bonus pay for amazing work on #OSS 00010000359 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000360 +705bonus pay for amazing work on #OSS 00010000360 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000361 +705bonus pay for amazing work on #OSS 00010000361 +62223138010481967038518 0000100000#PBLQ7P1ORpj8z#Joshua Thompson 1121042880000362 +705bonus pay for amazing work on #OSS 00010000362 +62223138010481967038518 0000100000#6MioPHg74TgsP#Joshua Wilson 1121042880000363 +705bonus pay for amazing work on #OSS 00010000363 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000364 +705bonus pay for amazing work on #OSS 00010000364 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000365 +705bonus pay for amazing work on #OSS 00010000365 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000366 +705bonus pay for amazing work on #OSS 00010000366 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000367 +705bonus pay for amazing work on #OSS 00010000367 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000368 +705bonus pay for amazing work on #OSS 00010000368 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000369 +705bonus pay for amazing work on #OSS 00010000369 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000370 +705bonus pay for amazing work on #OSS 00010000370 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000371 +705bonus pay for amazing work on #OSS 00010000371 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000372 +705bonus pay for amazing work on #OSS 00010000372 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000373 +705bonus pay for amazing work on #OSS 00010000373 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000374 +705bonus pay for amazing work on #OSS 00010000374 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000375 +705bonus pay for amazing work on #OSS 00010000375 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000376 +705bonus pay for amazing work on #OSS 00010000376 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000377 +705bonus pay for amazing work on #OSS 00010000377 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000378 +705bonus pay for amazing work on #OSS 00010000378 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000379 +705bonus pay for amazing work on #OSS 00010000379 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000380 +705bonus pay for amazing work on #OSS 00010000380 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000381 +705bonus pay for amazing work on #OSS 00010000381 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000382 +705bonus pay for amazing work on #OSS 00010000382 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000383 +705bonus pay for amazing work on #OSS 00010000383 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000384 +705bonus pay for amazing work on #OSS 00010000384 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000385 +705bonus pay for amazing work on #OSS 00010000385 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000386 +705bonus pay for amazing work on #OSS 00010000386 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000387 +705bonus pay for amazing work on #OSS 00010000387 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000388 +705bonus pay for amazing work on #OSS 00010000388 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000389 +705bonus pay for amazing work on #OSS 00010000389 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000390 +705bonus pay for amazing work on #OSS 00010000390 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000391 +705bonus pay for amazing work on #OSS 00010000391 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000392 +705bonus pay for amazing work on #OSS 00010000392 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000393 +705bonus pay for amazing work on #OSS 00010000393 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000394 +705bonus pay for amazing work on #OSS 00010000394 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000395 +705bonus pay for amazing work on #OSS 00010000395 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000396 +705bonus pay for amazing work on #OSS 00010000396 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000397 +705bonus pay for amazing work on #OSS 00010000397 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000398 +705bonus pay for amazing work on #OSS 00010000398 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000399 +705bonus pay for amazing work on #OSS 00010000399 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000400 +705bonus pay for amazing work on #OSS 00010000400 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000401 +705bonus pay for amazing work on #OSS 00010000401 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000402 +705bonus pay for amazing work on #OSS 00010000402 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000403 +705bonus pay for amazing work on #OSS 00010000403 +62223138010481967038518 0000100000#6MioPHg74TgsP#Mia Wilson 1121042880000404 +705bonus pay for amazing work on #OSS 00010000404 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000405 +705bonus pay for amazing work on #OSS 00010000405 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000406 +705bonus pay for amazing work on #OSS 00010000406 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000407 +705bonus pay for amazing work on #OSS 00010000407 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000408 +705bonus pay for amazing work on #OSS 00010000408 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000409 +705bonus pay for amazing work on #OSS 00010000409 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000410 +705bonus pay for amazing work on #OSS 00010000410 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000411 +705bonus pay for amazing work on #OSS 00010000411 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000412 +705bonus pay for amazing work on #OSS 00010000412 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000413 +705bonus pay for amazing work on #OSS 00010000413 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000414 +705bonus pay for amazing work on #OSS 00010000414 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000415 +705bonus pay for amazing work on #OSS 00010000415 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000416 +705bonus pay for amazing work on #OSS 00010000416 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000417 +705bonus pay for amazing work on #OSS 00010000417 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000418 +705bonus pay for amazing work on #OSS 00010000418 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000419 +705bonus pay for amazing work on #OSS 00010000419 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000420 +705bonus pay for amazing work on #OSS 00010000420 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000421 +705bonus pay for amazing work on #OSS 00010000421 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000422 +705bonus pay for amazing work on #OSS 00010000422 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000423 +705bonus pay for amazing work on #OSS 00010000423 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000424 +705bonus pay for amazing work on #OSS 00010000424 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000425 +705bonus pay for amazing work on #OSS 00010000425 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000426 +705bonus pay for amazing work on #OSS 00010000426 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000427 +705bonus pay for amazing work on #OSS 00010000427 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000428 +705bonus pay for amazing work on #OSS 00010000428 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000429 +705bonus pay for amazing work on #OSS 00010000429 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000430 +705bonus pay for amazing work on #OSS 00010000430 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000431 +705bonus pay for amazing work on #OSS 00010000431 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000432 +705bonus pay for amazing work on #OSS 00010000432 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000433 +705bonus pay for amazing work on #OSS 00010000433 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000434 +705bonus pay for amazing work on #OSS 00010000434 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000435 +705bonus pay for amazing work on #OSS 00010000435 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000436 +705bonus pay for amazing work on #OSS 00010000436 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000437 +705bonus pay for amazing work on #OSS 00010000437 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000438 +705bonus pay for amazing work on #OSS 00010000438 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000439 +705bonus pay for amazing work on #OSS 00010000439 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000440 +705bonus pay for amazing work on #OSS 00010000440 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000441 +705bonus pay for amazing work on #OSS 00010000441 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000442 +705bonus pay for amazing work on #OSS 00010000442 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000443 +705bonus pay for amazing work on #OSS 00010000443 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000444 +705bonus pay for amazing work on #OSS 00010000444 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000445 +705bonus pay for amazing work on #OSS 00010000445 +62223138010481967038518 0000100000#drJPHD6ZooZXd#Jacob Smith 1121042880000446 +705bonus pay for amazing work on #OSS 00010000446 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000447 +705bonus pay for amazing work on #OSS 00010000447 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000448 +705bonus pay for amazing work on #OSS 00010000448 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000449 +705bonus pay for amazing work on #OSS 00010000449 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000450 +705bonus pay for amazing work on #OSS 00010000450 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000451 +705bonus pay for amazing work on #OSS 00010000451 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000452 +705bonus pay for amazing work on #OSS 00010000452 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000453 +705bonus pay for amazing work on #OSS 00010000453 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000454 +705bonus pay for amazing work on #OSS 00010000454 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000455 +705bonus pay for amazing work on #OSS 00010000455 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000456 +705bonus pay for amazing work on #OSS 00010000456 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000457 +705bonus pay for amazing work on #OSS 00010000457 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000458 +705bonus pay for amazing work on #OSS 00010000458 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000459 +705bonus pay for amazing work on #OSS 00010000459 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000460 +705bonus pay for amazing work on #OSS 00010000460 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000461 +705bonus pay for amazing work on #OSS 00010000461 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000462 +705bonus pay for amazing work on #OSS 00010000462 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000463 +705bonus pay for amazing work on #OSS 00010000463 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000464 +705bonus pay for amazing work on #OSS 00010000464 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000465 +705bonus pay for amazing work on #OSS 00010000465 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000466 +705bonus pay for amazing work on #OSS 00010000466 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000467 +705bonus pay for amazing work on #OSS 00010000467 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000468 +705bonus pay for amazing work on #OSS 00010000468 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000469 +705bonus pay for amazing work on #OSS 00010000469 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000470 +705bonus pay for amazing work on #OSS 00010000470 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000471 +705bonus pay for amazing work on #OSS 00010000471 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000472 +705bonus pay for amazing work on #OSS 00010000472 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000473 +705bonus pay for amazing work on #OSS 00010000473 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000474 +705bonus pay for amazing work on #OSS 00010000474 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000475 +705bonus pay for amazing work on #OSS 00010000475 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000476 +705bonus pay for amazing work on #OSS 00010000476 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000477 +705bonus pay for amazing work on #OSS 00010000477 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000478 +705bonus pay for amazing work on #OSS 00010000478 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000479 +705bonus pay for amazing work on #OSS 00010000479 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000480 +705bonus pay for amazing work on #OSS 00010000480 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000481 +705bonus pay for amazing work on #OSS 00010000481 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000482 +705bonus pay for amazing work on #OSS 00010000482 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000483 +705bonus pay for amazing work on #OSS 00010000483 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000484 +705bonus pay for amazing work on #OSS 00010000484 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000485 +705bonus pay for amazing work on #OSS 00010000485 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000486 +705bonus pay for amazing work on #OSS 00010000486 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000487 +705bonus pay for amazing work on #OSS 00010000487 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000488 +705bonus pay for amazing work on #OSS 00010000488 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000489 +705bonus pay for amazing work on #OSS 00010000489 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000490 +705bonus pay for amazing work on #OSS 00010000490 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000491 +705bonus pay for amazing work on #OSS 00010000491 +62223138010481967038518 0000100000#8zLmN5tRw4DAs#Mia Wilson 1121042880000492 +705bonus pay for amazing work on #OSS 00010000492 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000493 +705bonus pay for amazing work on #OSS 00010000493 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000494 +705bonus pay for amazing work on #OSS 00010000494 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000495 +705bonus pay for amazing work on #OSS 00010000495 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000496 +705bonus pay for amazing work on #OSS 00010000496 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000497 +705bonus pay for amazing work on #OSS 00010000497 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000498 +705bonus pay for amazing work on #OSS 00010000498 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000499 +705bonus pay for amazing work on #OSS 00010000499 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000500 +705bonus pay for amazing work on #OSS 00010000500 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000501 +705bonus pay for amazing work on #OSS 00010000501 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000502 +705bonus pay for amazing work on #OSS 00010000502 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000503 +705bonus pay for amazing work on #OSS 00010000503 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000504 +705bonus pay for amazing work on #OSS 00010000504 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000505 +705bonus pay for amazing work on #OSS 00010000505 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000506 +705bonus pay for amazing work on #OSS 00010000506 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000507 +705bonus pay for amazing work on #OSS 00010000507 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000508 +705bonus pay for amazing work on #OSS 00010000508 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000509 +705bonus pay for amazing work on #OSS 00010000509 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000510 +705bonus pay for amazing work on #OSS 00010000510 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000511 +705bonus pay for amazing work on #OSS 00010000511 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000512 +705bonus pay for amazing work on #OSS 00010000512 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000513 +705bonus pay for amazing work on #OSS 00010000513 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000514 +705bonus pay for amazing work on #OSS 00010000514 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000515 +705bonus pay for amazing work on #OSS 00010000515 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000516 +705bonus pay for amazing work on #OSS 00010000516 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000517 +705bonus pay for amazing work on #OSS 00010000517 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000518 +705bonus pay for amazing work on #OSS 00010000518 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000519 +705bonus pay for amazing work on #OSS 00010000519 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000520 +705bonus pay for amazing work on #OSS 00010000520 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000521 +705bonus pay for amazing work on #OSS 00010000521 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000522 +705bonus pay for amazing work on #OSS 00010000522 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000523 +705bonus pay for amazing work on #OSS 00010000523 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000524 +705bonus pay for amazing work on #OSS 00010000524 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000525 +705bonus pay for amazing work on #OSS 00010000525 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000526 +705bonus pay for amazing work on #OSS 00010000526 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000527 +705bonus pay for amazing work on #OSS 00010000527 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000528 +705bonus pay for amazing work on #OSS 00010000528 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000529 +705bonus pay for amazing work on #OSS 00010000529 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000530 +705bonus pay for amazing work on #OSS 00010000530 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000531 +705bonus pay for amazing work on #OSS 00010000531 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000532 +705bonus pay for amazing work on #OSS 00010000532 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000533 +705bonus pay for amazing work on #OSS 00010000533 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000534 +705bonus pay for amazing work on #OSS 00010000534 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000535 +705bonus pay for amazing work on #OSS 00010000535 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000536 +705bonus pay for amazing work on #OSS 00010000536 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000537 +705bonus pay for amazing work on #OSS 00010000537 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000538 +705bonus pay for amazing work on #OSS 00010000538 +62223138010481967038518 0000100000#3W68qcMvpkHe3#Jacob Smith 1121042880000539 +705bonus pay for amazing work on #OSS 00010000539 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Jacob Jones 1121042880000540 +705bonus pay for amazing work on #OSS 00010000540 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000541 +705bonus pay for amazing work on #OSS 00010000541 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000542 +705bonus pay for amazing work on #OSS 00010000542 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000543 +705bonus pay for amazing work on #OSS 00010000543 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000544 +705bonus pay for amazing work on #OSS 00010000544 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000545 +705bonus pay for amazing work on #OSS 00010000545 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000546 +705bonus pay for amazing work on #OSS 00010000546 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000547 +705bonus pay for amazing work on #OSS 00010000547 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000548 +705bonus pay for amazing work on #OSS 00010000548 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000549 +705bonus pay for amazing work on #OSS 00010000549 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000550 +705bonus pay for amazing work on #OSS 00010000550 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000551 +705bonus pay for amazing work on #OSS 00010000551 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000552 +705bonus pay for amazing work on #OSS 00010000552 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000553 +705bonus pay for amazing work on #OSS 00010000553 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000554 +705bonus pay for amazing work on #OSS 00010000554 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000555 +705bonus pay for amazing work on #OSS 00010000555 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000556 +705bonus pay for amazing work on #OSS 00010000556 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000557 +705bonus pay for amazing work on #OSS 00010000557 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000558 +705bonus pay for amazing work on #OSS 00010000558 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000559 +705bonus pay for amazing work on #OSS 00010000559 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000560 +705bonus pay for amazing work on #OSS 00010000560 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000561 +705bonus pay for amazing work on #OSS 00010000561 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000562 +705bonus pay for amazing work on #OSS 00010000562 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000563 +705bonus pay for amazing work on #OSS 00010000563 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000564 +705bonus pay for amazing work on #OSS 00010000564 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000565 +705bonus pay for amazing work on #OSS 00010000565 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000566 +705bonus pay for amazing work on #OSS 00010000566 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000567 +705bonus pay for amazing work on #OSS 00010000567 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000568 +705bonus pay for amazing work on #OSS 00010000568 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000569 +705bonus pay for amazing work on #OSS 00010000569 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000570 +705bonus pay for amazing work on #OSS 00010000570 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000571 +705bonus pay for amazing work on #OSS 00010000571 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000572 +705bonus pay for amazing work on #OSS 00010000572 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000573 +705bonus pay for amazing work on #OSS 00010000573 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000574 +705bonus pay for amazing work on #OSS 00010000574 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000575 +705bonus pay for amazing work on #OSS 00010000575 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000576 +705bonus pay for amazing work on #OSS 00010000576 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000577 +705bonus pay for amazing work on #OSS 00010000577 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000578 +705bonus pay for amazing work on #OSS 00010000578 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000579 +705bonus pay for amazing work on #OSS 00010000579 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000580 +705bonus pay for amazing work on #OSS 00010000580 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000581 +705bonus pay for amazing work on #OSS 00010000581 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000582 +705bonus pay for amazing work on #OSS 00010000582 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000583 +705bonus pay for amazing work on #OSS 00010000583 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000584 +705bonus pay for amazing work on #OSS 00010000584 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000585 +705bonus pay for amazing work on #OSS 00010000585 +62223138010481967038518 0000100000#njGsJ6Stjfewf#Olivia Jones 1121042880000586 +705bonus pay for amazing work on #OSS 00010000586 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000587 +705bonus pay for amazing work on #OSS 00010000587 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000588 +705bonus pay for amazing work on #OSS 00010000588 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000589 +705bonus pay for amazing work on #OSS 00010000589 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000590 +705bonus pay for amazing work on #OSS 00010000590 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000591 +705bonus pay for amazing work on #OSS 00010000591 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000592 +705bonus pay for amazing work on #OSS 00010000592 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000593 +705bonus pay for amazing work on #OSS 00010000593 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000594 +705bonus pay for amazing work on #OSS 00010000594 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000595 +705bonus pay for amazing work on #OSS 00010000595 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000596 +705bonus pay for amazing work on #OSS 00010000596 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000597 +705bonus pay for amazing work on #OSS 00010000597 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000598 +705bonus pay for amazing work on #OSS 00010000598 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000599 +705bonus pay for amazing work on #OSS 00010000599 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000600 +705bonus pay for amazing work on #OSS 00010000600 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000601 +705bonus pay for amazing work on #OSS 00010000601 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000602 +705bonus pay for amazing work on #OSS 00010000602 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000603 +705bonus pay for amazing work on #OSS 00010000603 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000604 +705bonus pay for amazing work on #OSS 00010000604 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000605 +705bonus pay for amazing work on #OSS 00010000605 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000606 +705bonus pay for amazing work on #OSS 00010000606 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000607 +705bonus pay for amazing work on #OSS 00010000607 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000608 +705bonus pay for amazing work on #OSS 00010000608 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000609 +705bonus pay for amazing work on #OSS 00010000609 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000610 +705bonus pay for amazing work on #OSS 00010000610 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000611 +705bonus pay for amazing work on #OSS 00010000611 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000612 +705bonus pay for amazing work on #OSS 00010000612 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000613 +705bonus pay for amazing work on #OSS 00010000613 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000614 +705bonus pay for amazing work on #OSS 00010000614 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000615 +705bonus pay for amazing work on #OSS 00010000615 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000616 +705bonus pay for amazing work on #OSS 00010000616 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000617 +705bonus pay for amazing work on #OSS 00010000617 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000618 +705bonus pay for amazing work on #OSS 00010000618 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000619 +705bonus pay for amazing work on #OSS 00010000619 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000620 +705bonus pay for amazing work on #OSS 00010000620 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000621 +705bonus pay for amazing work on #OSS 00010000621 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000622 +705bonus pay for amazing work on #OSS 00010000622 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000623 +705bonus pay for amazing work on #OSS 00010000623 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000624 +705bonus pay for amazing work on #OSS 00010000624 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000625 +705bonus pay for amazing work on #OSS 00010000625 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000626 +705bonus pay for amazing work on #OSS 00010000626 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000627 +705bonus pay for amazing work on #OSS 00010000627 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000628 +705bonus pay for amazing work on #OSS 00010000628 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000629 +705bonus pay for amazing work on #OSS 00010000629 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000630 +705bonus pay for amazing work on #OSS 00010000630 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000631 +705bonus pay for amazing work on #OSS 00010000631 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000632 +705bonus pay for amazing work on #OSS 00010000632 +62223138010481967038518 0000100000#cWXuri972NX5k#Olivia Jones 1121042880000633 +705bonus pay for amazing work on #OSS 00010000633 +62223138010481967038518 0000100000#8i08En4CENEic#Olivia Robinson 1121042880000634 +705bonus pay for amazing work on #OSS 00010000634 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000635 +705bonus pay for amazing work on #OSS 00010000635 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000636 +705bonus pay for amazing work on #OSS 00010000636 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000637 +705bonus pay for amazing work on #OSS 00010000637 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000638 +705bonus pay for amazing work on #OSS 00010000638 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000639 +705bonus pay for amazing work on #OSS 00010000639 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000640 +705bonus pay for amazing work on #OSS 00010000640 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000641 +705bonus pay for amazing work on #OSS 00010000641 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000642 +705bonus pay for amazing work on #OSS 00010000642 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000643 +705bonus pay for amazing work on #OSS 00010000643 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000644 +705bonus pay for amazing work on #OSS 00010000644 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000645 +705bonus pay for amazing work on #OSS 00010000645 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000646 +705bonus pay for amazing work on #OSS 00010000646 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000647 +705bonus pay for amazing work on #OSS 00010000647 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000648 +705bonus pay for amazing work on #OSS 00010000648 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000649 +705bonus pay for amazing work on #OSS 00010000649 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000650 +705bonus pay for amazing work on #OSS 00010000650 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000651 +705bonus pay for amazing work on #OSS 00010000651 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000652 +705bonus pay for amazing work on #OSS 00010000652 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000653 +705bonus pay for amazing work on #OSS 00010000653 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000654 +705bonus pay for amazing work on #OSS 00010000654 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000655 +705bonus pay for amazing work on #OSS 00010000655 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000656 +705bonus pay for amazing work on #OSS 00010000656 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000657 +705bonus pay for amazing work on #OSS 00010000657 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000658 +705bonus pay for amazing work on #OSS 00010000658 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000659 +705bonus pay for amazing work on #OSS 00010000659 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000660 +705bonus pay for amazing work on #OSS 00010000660 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000661 +705bonus pay for amazing work on #OSS 00010000661 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000662 +705bonus pay for amazing work on #OSS 00010000662 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000663 +705bonus pay for amazing work on #OSS 00010000663 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000664 +705bonus pay for amazing work on #OSS 00010000664 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000665 +705bonus pay for amazing work on #OSS 00010000665 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000666 +705bonus pay for amazing work on #OSS 00010000666 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000667 +705bonus pay for amazing work on #OSS 00010000667 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000668 +705bonus pay for amazing work on #OSS 00010000668 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000669 +705bonus pay for amazing work on #OSS 00010000669 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000670 +705bonus pay for amazing work on #OSS 00010000670 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000671 +705bonus pay for amazing work on #OSS 00010000671 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000672 +705bonus pay for amazing work on #OSS 00010000672 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000673 +705bonus pay for amazing work on #OSS 00010000673 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000674 +705bonus pay for amazing work on #OSS 00010000674 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000675 +705bonus pay for amazing work on #OSS 00010000675 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000676 +705bonus pay for amazing work on #OSS 00010000676 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000677 +705bonus pay for amazing work on #OSS 00010000677 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000678 +705bonus pay for amazing work on #OSS 00010000678 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000679 +705bonus pay for amazing work on #OSS 00010000679 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000680 +705bonus pay for amazing work on #OSS 00010000680 +62223138010481967038518 0000100000#8i08En4CENEic#Zoey Robinson 1121042880000681 +705bonus pay for amazing work on #OSS 00010000681 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000682 +705bonus pay for amazing work on #OSS 00010000682 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000683 +705bonus pay for amazing work on #OSS 00010000683 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000684 +705bonus pay for amazing work on #OSS 00010000684 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000685 +705bonus pay for amazing work on #OSS 00010000685 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000686 +705bonus pay for amazing work on #OSS 00010000686 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000687 +705bonus pay for amazing work on #OSS 00010000687 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000688 +705bonus pay for amazing work on #OSS 00010000688 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000689 +705bonus pay for amazing work on #OSS 00010000689 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000690 +705bonus pay for amazing work on #OSS 00010000690 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000691 +705bonus pay for amazing work on #OSS 00010000691 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000692 +705bonus pay for amazing work on #OSS 00010000692 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000693 +705bonus pay for amazing work on #OSS 00010000693 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000694 +705bonus pay for amazing work on #OSS 00010000694 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000695 +705bonus pay for amazing work on #OSS 00010000695 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000696 +705bonus pay for amazing work on #OSS 00010000696 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000697 +705bonus pay for amazing work on #OSS 00010000697 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000698 +705bonus pay for amazing work on #OSS 00010000698 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000699 +705bonus pay for amazing work on #OSS 00010000699 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000700 +705bonus pay for amazing work on #OSS 00010000700 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000701 +705bonus pay for amazing work on #OSS 00010000701 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000702 +705bonus pay for amazing work on #OSS 00010000702 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000703 +705bonus pay for amazing work on #OSS 00010000703 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000704 +705bonus pay for amazing work on #OSS 00010000704 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000705 +705bonus pay for amazing work on #OSS 00010000705 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000706 +705bonus pay for amazing work on #OSS 00010000706 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000707 +705bonus pay for amazing work on #OSS 00010000707 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000708 +705bonus pay for amazing work on #OSS 00010000708 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000709 +705bonus pay for amazing work on #OSS 00010000709 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000710 +705bonus pay for amazing work on #OSS 00010000710 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000711 +705bonus pay for amazing work on #OSS 00010000711 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000712 +705bonus pay for amazing work on #OSS 00010000712 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000713 +705bonus pay for amazing work on #OSS 00010000713 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000714 +705bonus pay for amazing work on #OSS 00010000714 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000715 +705bonus pay for amazing work on #OSS 00010000715 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000716 +705bonus pay for amazing work on #OSS 00010000716 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000717 +705bonus pay for amazing work on #OSS 00010000717 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000718 +705bonus pay for amazing work on #OSS 00010000718 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000719 +705bonus pay for amazing work on #OSS 00010000719 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000720 +705bonus pay for amazing work on #OSS 00010000720 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000721 +705bonus pay for amazing work on #OSS 00010000721 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000722 +705bonus pay for amazing work on #OSS 00010000722 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000723 +705bonus pay for amazing work on #OSS 00010000723 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000724 +705bonus pay for amazing work on #OSS 00010000724 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000725 +705bonus pay for amazing work on #OSS 00010000725 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000726 +705bonus pay for amazing work on #OSS 00010000726 +62223138010481967038518 0000100000#mf29AAdc6JACS#Mia Wilson 1121042880000727 +705bonus pay for amazing work on #OSS 00010000727 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Isabella Williams 1121042880000728 +705bonus pay for amazing work on #OSS 00010000728 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000729 +705bonus pay for amazing work on #OSS 00010000729 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000730 +705bonus pay for amazing work on #OSS 00010000730 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000731 +705bonus pay for amazing work on #OSS 00010000731 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000732 +705bonus pay for amazing work on #OSS 00010000732 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000733 +705bonus pay for amazing work on #OSS 00010000733 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000734 +705bonus pay for amazing work on #OSS 00010000734 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000735 +705bonus pay for amazing work on #OSS 00010000735 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000736 +705bonus pay for amazing work on #OSS 00010000736 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000737 +705bonus pay for amazing work on #OSS 00010000737 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000738 +705bonus pay for amazing work on #OSS 00010000738 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000739 +705bonus pay for amazing work on #OSS 00010000739 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000740 +705bonus pay for amazing work on #OSS 00010000740 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000741 +705bonus pay for amazing work on #OSS 00010000741 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000742 +705bonus pay for amazing work on #OSS 00010000742 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000743 +705bonus pay for amazing work on #OSS 00010000743 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000744 +705bonus pay for amazing work on #OSS 00010000744 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000745 +705bonus pay for amazing work on #OSS 00010000745 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000746 +705bonus pay for amazing work on #OSS 00010000746 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000747 +705bonus pay for amazing work on #OSS 00010000747 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000748 +705bonus pay for amazing work on #OSS 00010000748 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000749 +705bonus pay for amazing work on #OSS 00010000749 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000750 +705bonus pay for amazing work on #OSS 00010000750 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000751 +705bonus pay for amazing work on #OSS 00010000751 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000752 +705bonus pay for amazing work on #OSS 00010000752 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000753 +705bonus pay for amazing work on #OSS 00010000753 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000754 +705bonus pay for amazing work on #OSS 00010000754 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000755 +705bonus pay for amazing work on #OSS 00010000755 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000756 +705bonus pay for amazing work on #OSS 00010000756 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000757 +705bonus pay for amazing work on #OSS 00010000757 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000758 +705bonus pay for amazing work on #OSS 00010000758 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000759 +705bonus pay for amazing work on #OSS 00010000759 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000760 +705bonus pay for amazing work on #OSS 00010000760 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000761 +705bonus pay for amazing work on #OSS 00010000761 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000762 +705bonus pay for amazing work on #OSS 00010000762 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000763 +705bonus pay for amazing work on #OSS 00010000763 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000764 +705bonus pay for amazing work on #OSS 00010000764 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000765 +705bonus pay for amazing work on #OSS 00010000765 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000766 +705bonus pay for amazing work on #OSS 00010000766 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000767 +705bonus pay for amazing work on #OSS 00010000767 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000768 +705bonus pay for amazing work on #OSS 00010000768 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000769 +705bonus pay for amazing work on #OSS 00010000769 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000770 +705bonus pay for amazing work on #OSS 00010000770 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000771 +705bonus pay for amazing work on #OSS 00010000771 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000772 +705bonus pay for amazing work on #OSS 00010000772 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000773 +705bonus pay for amazing work on #OSS 00010000773 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000774 +705bonus pay for amazing work on #OSS 00010000774 +62223138010481967038518 0000100000#bZ8RL94WWphmf#Ethan Williams 1121042880000775 +705bonus pay for amazing work on #OSS 00010000775 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000776 +705bonus pay for amazing work on #OSS 00010000776 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000777 +705bonus pay for amazing work on #OSS 00010000777 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000778 +705bonus pay for amazing work on #OSS 00010000778 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000779 +705bonus pay for amazing work on #OSS 00010000779 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000780 +705bonus pay for amazing work on #OSS 00010000780 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000781 +705bonus pay for amazing work on #OSS 00010000781 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000782 +705bonus pay for amazing work on #OSS 00010000782 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000783 +705bonus pay for amazing work on #OSS 00010000783 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000784 +705bonus pay for amazing work on #OSS 00010000784 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000785 +705bonus pay for amazing work on #OSS 00010000785 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000786 +705bonus pay for amazing work on #OSS 00010000786 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000787 +705bonus pay for amazing work on #OSS 00010000787 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000788 +705bonus pay for amazing work on #OSS 00010000788 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000789 +705bonus pay for amazing work on #OSS 00010000789 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000790 +705bonus pay for amazing work on #OSS 00010000790 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000791 +705bonus pay for amazing work on #OSS 00010000791 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000792 +705bonus pay for amazing work on #OSS 00010000792 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000793 +705bonus pay for amazing work on #OSS 00010000793 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000794 +705bonus pay for amazing work on #OSS 00010000794 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000795 +705bonus pay for amazing work on #OSS 00010000795 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000796 +705bonus pay for amazing work on #OSS 00010000796 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000797 +705bonus pay for amazing work on #OSS 00010000797 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000798 +705bonus pay for amazing work on #OSS 00010000798 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000799 +705bonus pay for amazing work on #OSS 00010000799 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000800 +705bonus pay for amazing work on #OSS 00010000800 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000801 +705bonus pay for amazing work on #OSS 00010000801 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000802 +705bonus pay for amazing work on #OSS 00010000802 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000803 +705bonus pay for amazing work on #OSS 00010000803 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000804 +705bonus pay for amazing work on #OSS 00010000804 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000805 +705bonus pay for amazing work on #OSS 00010000805 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000806 +705bonus pay for amazing work on #OSS 00010000806 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000807 +705bonus pay for amazing work on #OSS 00010000807 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000808 +705bonus pay for amazing work on #OSS 00010000808 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000809 +705bonus pay for amazing work on #OSS 00010000809 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000810 +705bonus pay for amazing work on #OSS 00010000810 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000811 +705bonus pay for amazing work on #OSS 00010000811 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000812 +705bonus pay for amazing work on #OSS 00010000812 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000813 +705bonus pay for amazing work on #OSS 00010000813 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000814 +705bonus pay for amazing work on #OSS 00010000814 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000815 +705bonus pay for amazing work on #OSS 00010000815 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000816 +705bonus pay for amazing work on #OSS 00010000816 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000817 +705bonus pay for amazing work on #OSS 00010000817 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000818 +705bonus pay for amazing work on #OSS 00010000818 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000819 +705bonus pay for amazing work on #OSS 00010000819 +62223138010481967038518 0000100000#RZxyJ30J4wKf5#Lily Martin 1121042880000820 +705bonus pay for amazing work on #OSS 00010000820 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Lily Williams 1121042880000821 +705bonus pay for amazing work on #OSS 00010000821 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000822 +705bonus pay for amazing work on #OSS 00010000822 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000823 +705bonus pay for amazing work on #OSS 00010000823 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000824 +705bonus pay for amazing work on #OSS 00010000824 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000825 +705bonus pay for amazing work on #OSS 00010000825 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000826 +705bonus pay for amazing work on #OSS 00010000826 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000827 +705bonus pay for amazing work on #OSS 00010000827 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000828 +705bonus pay for amazing work on #OSS 00010000828 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000829 +705bonus pay for amazing work on #OSS 00010000829 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000830 +705bonus pay for amazing work on #OSS 00010000830 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000831 +705bonus pay for amazing work on #OSS 00010000831 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000832 +705bonus pay for amazing work on #OSS 00010000832 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000833 +705bonus pay for amazing work on #OSS 00010000833 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000834 +705bonus pay for amazing work on #OSS 00010000834 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000835 +705bonus pay for amazing work on #OSS 00010000835 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000836 +705bonus pay for amazing work on #OSS 00010000836 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000837 +705bonus pay for amazing work on #OSS 00010000837 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000838 +705bonus pay for amazing work on #OSS 00010000838 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000839 +705bonus pay for amazing work on #OSS 00010000839 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000840 +705bonus pay for amazing work on #OSS 00010000840 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000841 +705bonus pay for amazing work on #OSS 00010000841 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000842 +705bonus pay for amazing work on #OSS 00010000842 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000843 +705bonus pay for amazing work on #OSS 00010000843 +62223138010481967038518 0000100000#7jGxeM21hK35Y#Ethan Williams 1121042880000844 +705bonus pay for amazing work on #OSS 00010000844 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000845 +705bonus pay for amazing work on #OSS 00010000845 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000846 +705bonus pay for amazing work on #OSS 00010000846 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000847 +705bonus pay for amazing work on #OSS 00010000847 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000848 +705bonus pay for amazing work on #OSS 00010000848 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000849 +705bonus pay for amazing work on #OSS 00010000849 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000850 +705bonus pay for amazing work on #OSS 00010000850 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000851 +705bonus pay for amazing work on #OSS 00010000851 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000852 +705bonus pay for amazing work on #OSS 00010000852 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000853 +705bonus pay for amazing work on #OSS 00010000853 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000854 +705bonus pay for amazing work on #OSS 00010000854 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000855 +705bonus pay for amazing work on #OSS 00010000855 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000856 +705bonus pay for amazing work on #OSS 00010000856 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000857 +705bonus pay for amazing work on #OSS 00010000857 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000858 +705bonus pay for amazing work on #OSS 00010000858 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000859 +705bonus pay for amazing work on #OSS 00010000859 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000860 +705bonus pay for amazing work on #OSS 00010000860 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000861 +705bonus pay for amazing work on #OSS 00010000861 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000862 +705bonus pay for amazing work on #OSS 00010000862 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000863 +705bonus pay for amazing work on #OSS 00010000863 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000864 +705bonus pay for amazing work on #OSS 00010000864 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000865 +705bonus pay for amazing work on #OSS 00010000865 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000866 +705bonus pay for amazing work on #OSS 00010000866 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000867 +705bonus pay for amazing work on #OSS 00010000867 +62223138010481967038518 0000100000#u07sfEG88QdUQ#William Brown 1121042880000868 +705bonus pay for amazing work on #OSS 00010000868 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000869 +705bonus pay for amazing work on #OSS 00010000869 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000870 +705bonus pay for amazing work on #OSS 00010000870 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000871 +705bonus pay for amazing work on #OSS 00010000871 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000872 +705bonus pay for amazing work on #OSS 00010000872 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000873 +705bonus pay for amazing work on #OSS 00010000873 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000874 +705bonus pay for amazing work on #OSS 00010000874 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000875 +705bonus pay for amazing work on #OSS 00010000875 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000876 +705bonus pay for amazing work on #OSS 00010000876 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000877 +705bonus pay for amazing work on #OSS 00010000877 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000878 +705bonus pay for amazing work on #OSS 00010000878 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000879 +705bonus pay for amazing work on #OSS 00010000879 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000880 +705bonus pay for amazing work on #OSS 00010000880 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000881 +705bonus pay for amazing work on #OSS 00010000881 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000882 +705bonus pay for amazing work on #OSS 00010000882 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000883 +705bonus pay for amazing work on #OSS 00010000883 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000884 +705bonus pay for amazing work on #OSS 00010000884 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000885 +705bonus pay for amazing work on #OSS 00010000885 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000886 +705bonus pay for amazing work on #OSS 00010000886 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000887 +705bonus pay for amazing work on #OSS 00010000887 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000888 +705bonus pay for amazing work on #OSS 00010000888 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000889 +705bonus pay for amazing work on #OSS 00010000889 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000890 +705bonus pay for amazing work on #OSS 00010000890 +62223138010481967038518 0000100000#rgTlx1F9gmHIR#Ella Thomas 1121042880000891 +705bonus pay for amazing work on #OSS 00010000891 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000892 +705bonus pay for amazing work on #OSS 00010000892 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000893 +705bonus pay for amazing work on #OSS 00010000893 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000894 +705bonus pay for amazing work on #OSS 00010000894 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000895 +705bonus pay for amazing work on #OSS 00010000895 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000896 +705bonus pay for amazing work on #OSS 00010000896 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000897 +705bonus pay for amazing work on #OSS 00010000897 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000898 +705bonus pay for amazing work on #OSS 00010000898 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000899 +705bonus pay for amazing work on #OSS 00010000899 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000900 +705bonus pay for amazing work on #OSS 00010000900 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000901 +705bonus pay for amazing work on #OSS 00010000901 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000902 +705bonus pay for amazing work on #OSS 00010000902 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000903 +705bonus pay for amazing work on #OSS 00010000903 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000904 +705bonus pay for amazing work on #OSS 00010000904 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000905 +705bonus pay for amazing work on #OSS 00010000905 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000906 +705bonus pay for amazing work on #OSS 00010000906 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000907 +705bonus pay for amazing work on #OSS 00010000907 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000908 +705bonus pay for amazing work on #OSS 00010000908 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000909 +705bonus pay for amazing work on #OSS 00010000909 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000910 +705bonus pay for amazing work on #OSS 00010000910 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000911 +705bonus pay for amazing work on #OSS 00010000911 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000912 +705bonus pay for amazing work on #OSS 00010000912 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000913 +705bonus pay for amazing work on #OSS 00010000913 +62223138010481967038518 0000100000#QU1KwpOlDf6Ej#Jayden Miller 1121042880000914 +705bonus pay for amazing work on #OSS 00010000914 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Jayden Anderson 1121042880000915 +705bonus pay for amazing work on #OSS 00010000915 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000916 +705bonus pay for amazing work on #OSS 00010000916 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000917 +705bonus pay for amazing work on #OSS 00010000917 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000918 +705bonus pay for amazing work on #OSS 00010000918 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000919 +705bonus pay for amazing work on #OSS 00010000919 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000920 +705bonus pay for amazing work on #OSS 00010000920 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000921 +705bonus pay for amazing work on #OSS 00010000921 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000922 +705bonus pay for amazing work on #OSS 00010000922 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000923 +705bonus pay for amazing work on #OSS 00010000923 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000924 +705bonus pay for amazing work on #OSS 00010000924 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000925 +705bonus pay for amazing work on #OSS 00010000925 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000926 +705bonus pay for amazing work on #OSS 00010000926 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000927 +705bonus pay for amazing work on #OSS 00010000927 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000928 +705bonus pay for amazing work on #OSS 00010000928 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000929 +705bonus pay for amazing work on #OSS 00010000929 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000930 +705bonus pay for amazing work on #OSS 00010000930 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000931 +705bonus pay for amazing work on #OSS 00010000931 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000932 +705bonus pay for amazing work on #OSS 00010000932 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000933 +705bonus pay for amazing work on #OSS 00010000933 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000934 +705bonus pay for amazing work on #OSS 00010000934 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000935 +705bonus pay for amazing work on #OSS 00010000935 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000936 +705bonus pay for amazing work on #OSS 00010000936 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000937 +705bonus pay for amazing work on #OSS 00010000937 +62223138010481967038518 0000100000#pOkxTlUtVJMqk#Daniel Anderson 1121042880000938 +705bonus pay for amazing work on #OSS 00010000938 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Liam Davis 1121042880000939 +705bonus pay for amazing work on #OSS 00010000939 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000940 +705bonus pay for amazing work on #OSS 00010000940 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000941 +705bonus pay for amazing work on #OSS 00010000941 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000942 +705bonus pay for amazing work on #OSS 00010000942 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000943 +705bonus pay for amazing work on #OSS 00010000943 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000944 +705bonus pay for amazing work on #OSS 00010000944 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000945 +705bonus pay for amazing work on #OSS 00010000945 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000946 +705bonus pay for amazing work on #OSS 00010000946 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000947 +705bonus pay for amazing work on #OSS 00010000947 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000948 +705bonus pay for amazing work on #OSS 00010000948 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000949 +705bonus pay for amazing work on #OSS 00010000949 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000950 +705bonus pay for amazing work on #OSS 00010000950 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000951 +705bonus pay for amazing work on #OSS 00010000951 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000952 +705bonus pay for amazing work on #OSS 00010000952 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000953 +705bonus pay for amazing work on #OSS 00010000953 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000954 +705bonus pay for amazing work on #OSS 00010000954 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000955 +705bonus pay for amazing work on #OSS 00010000955 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000956 +705bonus pay for amazing work on #OSS 00010000956 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000957 +705bonus pay for amazing work on #OSS 00010000957 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000958 +705bonus pay for amazing work on #OSS 00010000958 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000959 +705bonus pay for amazing work on #OSS 00010000959 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000960 +705bonus pay for amazing work on #OSS 00010000960 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000961 +705bonus pay for amazing work on #OSS 00010000961 +62223138010481967038518 0000100000#bgj1OvxdA7QyC#Emily Davis 1121042880000962 +705bonus pay for amazing work on #OSS 00010000962 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000963 +705bonus pay for amazing work on #OSS 00010000963 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000964 +705bonus pay for amazing work on #OSS 00010000964 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000965 +705bonus pay for amazing work on #OSS 00010000965 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000966 +705bonus pay for amazing work on #OSS 00010000966 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000967 +705bonus pay for amazing work on #OSS 00010000967 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000968 +705bonus pay for amazing work on #OSS 00010000968 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000969 +705bonus pay for amazing work on #OSS 00010000969 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000970 +705bonus pay for amazing work on #OSS 00010000970 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000971 +705bonus pay for amazing work on #OSS 00010000971 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000972 +705bonus pay for amazing work on #OSS 00010000972 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000973 +705bonus pay for amazing work on #OSS 00010000973 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000974 +705bonus pay for amazing work on #OSS 00010000974 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000975 +705bonus pay for amazing work on #OSS 00010000975 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000976 +705bonus pay for amazing work on #OSS 00010000976 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000977 +705bonus pay for amazing work on #OSS 00010000977 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000978 +705bonus pay for amazing work on #OSS 00010000978 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000979 +705bonus pay for amazing work on #OSS 00010000979 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000980 +705bonus pay for amazing work on #OSS 00010000980 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000981 +705bonus pay for amazing work on #OSS 00010000981 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000982 +705bonus pay for amazing work on #OSS 00010000982 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000983 +705bonus pay for amazing work on #OSS 00010000983 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000984 +705bonus pay for amazing work on #OSS 00010000984 +62223138010481967038518 0000100000#NkNbJ7t0FZ9Ku#Lily Martin 1121042880000985 +705bonus pay for amazing work on #OSS 00010000985 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Lily Anderson 1121042880000986 +705bonus pay for amazing work on #OSS 00010000986 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880000987 +705bonus pay for amazing work on #OSS 00010000987 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880000988 +705bonus pay for amazing work on #OSS 00010000988 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880000989 +705bonus pay for amazing work on #OSS 00010000989 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880000990 +705bonus pay for amazing work on #OSS 00010000990 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880000991 +705bonus pay for amazing work on #OSS 00010000991 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880000992 +705bonus pay for amazing work on #OSS 00010000992 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880000993 +705bonus pay for amazing work on #OSS 00010000993 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880000994 +705bonus pay for amazing work on #OSS 00010000994 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880000995 +705bonus pay for amazing work on #OSS 00010000995 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880000996 +705bonus pay for amazing work on #OSS 00010000996 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880000997 +705bonus pay for amazing work on #OSS 00010000997 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880000998 +705bonus pay for amazing work on #OSS 00010000998 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880000999 +705bonus pay for amazing work on #OSS 00010000999 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880001000 +705bonus pay for amazing work on #OSS 00010001000 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880001001 +705bonus pay for amazing work on #OSS 00010001001 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880001002 +705bonus pay for amazing work on #OSS 00010001002 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880001003 +705bonus pay for amazing work on #OSS 00010001003 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880001004 +705bonus pay for amazing work on #OSS 00010001004 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880001005 +705bonus pay for amazing work on #OSS 00010001005 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880001006 +705bonus pay for amazing work on #OSS 00010001006 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880001007 +705bonus pay for amazing work on #OSS 00010001007 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880001008 +705bonus pay for amazing work on #OSS 00010001008 +62223138010481967038518 0000100000#akgfB6zmfisPZ#Daniel Anderson 1121042880001009 +705bonus pay for amazing work on #OSS 00010001009 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Andrew Garcia 1121042880001010 +705bonus pay for amazing work on #OSS 00010001010 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001011 +705bonus pay for amazing work on #OSS 00010001011 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001012 +705bonus pay for amazing work on #OSS 00010001012 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001013 +705bonus pay for amazing work on #OSS 00010001013 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001014 +705bonus pay for amazing work on #OSS 00010001014 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001015 +705bonus pay for amazing work on #OSS 00010001015 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001016 +705bonus pay for amazing work on #OSS 00010001016 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001017 +705bonus pay for amazing work on #OSS 00010001017 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001018 +705bonus pay for amazing work on #OSS 00010001018 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001019 +705bonus pay for amazing work on #OSS 00010001019 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001020 +705bonus pay for amazing work on #OSS 00010001020 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001021 +705bonus pay for amazing work on #OSS 00010001021 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001022 +705bonus pay for amazing work on #OSS 00010001022 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001023 +705bonus pay for amazing work on #OSS 00010001023 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001024 +705bonus pay for amazing work on #OSS 00010001024 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001025 +705bonus pay for amazing work on #OSS 00010001025 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001026 +705bonus pay for amazing work on #OSS 00010001026 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001027 +705bonus pay for amazing work on #OSS 00010001027 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001028 +705bonus pay for amazing work on #OSS 00010001028 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001029 +705bonus pay for amazing work on #OSS 00010001029 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001030 +705bonus pay for amazing work on #OSS 00010001030 +62223138010481967038518 0000100000#kU6b9Wd8LaJnn#Sofia Garcia 1121042880001031 +705bonus pay for amazing work on #OSS 00010001031 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Sophia Smith 1121042880001032 +705bonus pay for amazing work on #OSS 00010001032 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001033 +705bonus pay for amazing work on #OSS 00010001033 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001034 +705bonus pay for amazing work on #OSS 00010001034 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001035 +705bonus pay for amazing work on #OSS 00010001035 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001036 +705bonus pay for amazing work on #OSS 00010001036 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001037 +705bonus pay for amazing work on #OSS 00010001037 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001038 +705bonus pay for amazing work on #OSS 00010001038 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001039 +705bonus pay for amazing work on #OSS 00010001039 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001040 +705bonus pay for amazing work on #OSS 00010001040 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001041 +705bonus pay for amazing work on #OSS 00010001041 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001042 +705bonus pay for amazing work on #OSS 00010001042 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001043 +705bonus pay for amazing work on #OSS 00010001043 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001044 +705bonus pay for amazing work on #OSS 00010001044 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001045 +705bonus pay for amazing work on #OSS 00010001045 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001046 +705bonus pay for amazing work on #OSS 00010001046 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001047 +705bonus pay for amazing work on #OSS 00010001047 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001048 +705bonus pay for amazing work on #OSS 00010001048 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001049 +705bonus pay for amazing work on #OSS 00010001049 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001050 +705bonus pay for amazing work on #OSS 00010001050 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001051 +705bonus pay for amazing work on #OSS 00010001051 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001052 +705bonus pay for amazing work on #OSS 00010001052 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001053 +705bonus pay for amazing work on #OSS 00010001053 +62223138010481967038518 0000100000#wjP6OzoCWmfd1#Jacob Smith 1121042880001054 +705bonus pay for amazing work on #OSS 00010001054 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Jacob Harris 1121042880001055 +705bonus pay for amazing work on #OSS 00010001055 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001056 +705bonus pay for amazing work on #OSS 00010001056 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001057 +705bonus pay for amazing work on #OSS 00010001057 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001058 +705bonus pay for amazing work on #OSS 00010001058 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001059 +705bonus pay for amazing work on #OSS 00010001059 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001060 +705bonus pay for amazing work on #OSS 00010001060 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001061 +705bonus pay for amazing work on #OSS 00010001061 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001062 +705bonus pay for amazing work on #OSS 00010001062 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001063 +705bonus pay for amazing work on #OSS 00010001063 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001064 +705bonus pay for amazing work on #OSS 00010001064 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001065 +705bonus pay for amazing work on #OSS 00010001065 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001066 +705bonus pay for amazing work on #OSS 00010001066 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001067 +705bonus pay for amazing work on #OSS 00010001067 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001068 +705bonus pay for amazing work on #OSS 00010001068 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001069 +705bonus pay for amazing work on #OSS 00010001069 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001070 +705bonus pay for amazing work on #OSS 00010001070 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001071 +705bonus pay for amazing work on #OSS 00010001071 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001072 +705bonus pay for amazing work on #OSS 00010001072 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001073 +705bonus pay for amazing work on #OSS 00010001073 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001074 +705bonus pay for amazing work on #OSS 00010001074 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001075 +705bonus pay for amazing work on #OSS 00010001075 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001076 +705bonus pay for amazing work on #OSS 00010001076 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001077 +705bonus pay for amazing work on #OSS 00010001077 +62223138010481967038518 0000100000#AMkEl3GhL0wtL#Anthony Harris 1121042880001078 +705bonus pay for amazing work on #OSS 00010001078 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Noah Jones 1121042880001079 +705bonus pay for amazing work on #OSS 00010001079 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001080 +705bonus pay for amazing work on #OSS 00010001080 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001081 +705bonus pay for amazing work on #OSS 00010001081 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001082 +705bonus pay for amazing work on #OSS 00010001082 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001083 +705bonus pay for amazing work on #OSS 00010001083 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001084 +705bonus pay for amazing work on #OSS 00010001084 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001085 +705bonus pay for amazing work on #OSS 00010001085 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001086 +705bonus pay for amazing work on #OSS 00010001086 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001087 +705bonus pay for amazing work on #OSS 00010001087 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001088 +705bonus pay for amazing work on #OSS 00010001088 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001089 +705bonus pay for amazing work on #OSS 00010001089 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001090 +705bonus pay for amazing work on #OSS 00010001090 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001091 +705bonus pay for amazing work on #OSS 00010001091 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001092 +705bonus pay for amazing work on #OSS 00010001092 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001093 +705bonus pay for amazing work on #OSS 00010001093 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001094 +705bonus pay for amazing work on #OSS 00010001094 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001095 +705bonus pay for amazing work on #OSS 00010001095 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001096 +705bonus pay for amazing work on #OSS 00010001096 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001097 +705bonus pay for amazing work on #OSS 00010001097 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001098 +705bonus pay for amazing work on #OSS 00010001098 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001099 +705bonus pay for amazing work on #OSS 00010001099 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001100 +705bonus pay for amazing work on #OSS 00010001100 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001101 +705bonus pay for amazing work on #OSS 00010001101 +62223138010481967038518 0000100000#9Ttu6gV9hoCGB#Olivia Jones 1121042880001102 +705bonus pay for amazing work on #OSS 00010001102 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001103 +705bonus pay for amazing work on #OSS 00010001103 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001104 +705bonus pay for amazing work on #OSS 00010001104 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001105 +705bonus pay for amazing work on #OSS 00010001105 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001106 +705bonus pay for amazing work on #OSS 00010001106 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001107 +705bonus pay for amazing work on #OSS 00010001107 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001108 +705bonus pay for amazing work on #OSS 00010001108 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001109 +705bonus pay for amazing work on #OSS 00010001109 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001110 +705bonus pay for amazing work on #OSS 00010001110 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001111 +705bonus pay for amazing work on #OSS 00010001111 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001112 +705bonus pay for amazing work on #OSS 00010001112 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001113 +705bonus pay for amazing work on #OSS 00010001113 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001114 +705bonus pay for amazing work on #OSS 00010001114 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001115 +705bonus pay for amazing work on #OSS 00010001115 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001116 +705bonus pay for amazing work on #OSS 00010001116 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001117 +705bonus pay for amazing work on #OSS 00010001117 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001118 +705bonus pay for amazing work on #OSS 00010001118 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001119 +705bonus pay for amazing work on #OSS 00010001119 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001120 +705bonus pay for amazing work on #OSS 00010001120 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001121 +705bonus pay for amazing work on #OSS 00010001121 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001122 +705bonus pay for amazing work on #OSS 00010001122 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001123 +705bonus pay for amazing work on #OSS 00010001123 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001124 +705bonus pay for amazing work on #OSS 00010001124 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001125 +705bonus pay for amazing work on #OSS 00010001125 +62223138010481967038518 0000100000#LhLH3f2WfAzQS#Addison White 1121042880001126 +705bonus pay for amazing work on #OSS 00010001126 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001127 +705bonus pay for amazing work on #OSS 00010001127 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001128 +705bonus pay for amazing work on #OSS 00010001128 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001129 +705bonus pay for amazing work on #OSS 00010001129 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001130 +705bonus pay for amazing work on #OSS 00010001130 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001131 +705bonus pay for amazing work on #OSS 00010001131 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001132 +705bonus pay for amazing work on #OSS 00010001132 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001133 +705bonus pay for amazing work on #OSS 00010001133 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001134 +705bonus pay for amazing work on #OSS 00010001134 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001135 +705bonus pay for amazing work on #OSS 00010001135 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001136 +705bonus pay for amazing work on #OSS 00010001136 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001137 +705bonus pay for amazing work on #OSS 00010001137 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001138 +705bonus pay for amazing work on #OSS 00010001138 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001139 +705bonus pay for amazing work on #OSS 00010001139 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001140 +705bonus pay for amazing work on #OSS 00010001140 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001141 +705bonus pay for amazing work on #OSS 00010001141 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001142 +705bonus pay for amazing work on #OSS 00010001142 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001143 +705bonus pay for amazing work on #OSS 00010001143 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001144 +705bonus pay for amazing work on #OSS 00010001144 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001145 +705bonus pay for amazing work on #OSS 00010001145 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001146 +705bonus pay for amazing work on #OSS 00010001146 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001147 +705bonus pay for amazing work on #OSS 00010001147 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001148 +705bonus pay for amazing work on #OSS 00010001148 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001149 +705bonus pay for amazing work on #OSS 00010001149 +62223138010481967038518 0000100000#XHzAppn5DzTT4#Jacob Smith 1121042880001150 +705bonus pay for amazing work on #OSS 00010001150 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001151 +705bonus pay for amazing work on #OSS 00010001151 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001152 +705bonus pay for amazing work on #OSS 00010001152 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001153 +705bonus pay for amazing work on #OSS 00010001153 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001154 +705bonus pay for amazing work on #OSS 00010001154 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001155 +705bonus pay for amazing work on #OSS 00010001155 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001156 +705bonus pay for amazing work on #OSS 00010001156 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001157 +705bonus pay for amazing work on #OSS 00010001157 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001158 +705bonus pay for amazing work on #OSS 00010001158 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001159 +705bonus pay for amazing work on #OSS 00010001159 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001160 +705bonus pay for amazing work on #OSS 00010001160 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001161 +705bonus pay for amazing work on #OSS 00010001161 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001162 +705bonus pay for amazing work on #OSS 00010001162 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001163 +705bonus pay for amazing work on #OSS 00010001163 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001164 +705bonus pay for amazing work on #OSS 00010001164 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001165 +705bonus pay for amazing work on #OSS 00010001165 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001166 +705bonus pay for amazing work on #OSS 00010001166 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001167 +705bonus pay for amazing work on #OSS 00010001167 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001168 +705bonus pay for amazing work on #OSS 00010001168 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001169 +705bonus pay for amazing work on #OSS 00010001169 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001170 +705bonus pay for amazing work on #OSS 00010001170 +62223138010481967038518 0000100000#Scd8hPCmAXopW#Addison White 1121042880001171 +705bonus pay for amazing work on #OSS 00010001171 +62223138010481967038518 0000100000#MVFHEGA606itb#Addison Martin 1121042880001172 +705bonus pay for amazing work on #OSS 00010001172 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001173 +705bonus pay for amazing work on #OSS 00010001173 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001174 +705bonus pay for amazing work on #OSS 00010001174 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001175 +705bonus pay for amazing work on #OSS 00010001175 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001176 +705bonus pay for amazing work on #OSS 00010001176 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001177 +705bonus pay for amazing work on #OSS 00010001177 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001178 +705bonus pay for amazing work on #OSS 00010001178 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001179 +705bonus pay for amazing work on #OSS 00010001179 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001180 +705bonus pay for amazing work on #OSS 00010001180 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001181 +705bonus pay for amazing work on #OSS 00010001181 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001182 +705bonus pay for amazing work on #OSS 00010001182 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001183 +705bonus pay for amazing work on #OSS 00010001183 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001184 +705bonus pay for amazing work on #OSS 00010001184 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001185 +705bonus pay for amazing work on #OSS 00010001185 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001186 +705bonus pay for amazing work on #OSS 00010001186 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001187 +705bonus pay for amazing work on #OSS 00010001187 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001188 +705bonus pay for amazing work on #OSS 00010001188 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001189 +705bonus pay for amazing work on #OSS 00010001189 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001190 +705bonus pay for amazing work on #OSS 00010001190 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001191 +705bonus pay for amazing work on #OSS 00010001191 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001192 +705bonus pay for amazing work on #OSS 00010001192 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001193 +705bonus pay for amazing work on #OSS 00010001193 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001194 +705bonus pay for amazing work on #OSS 00010001194 +62223138010481967038518 0000100000#MVFHEGA606itb#Lily Martin 1121042880001195 +705bonus pay for amazing work on #OSS 00010001195 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Madison Moore 1121042880001196 +705bonus pay for amazing work on #OSS 00010001196 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001197 +705bonus pay for amazing work on #OSS 00010001197 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001198 +705bonus pay for amazing work on #OSS 00010001198 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001199 +705bonus pay for amazing work on #OSS 00010001199 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001200 +705bonus pay for amazing work on #OSS 00010001200 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001201 +705bonus pay for amazing work on #OSS 00010001201 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001202 +705bonus pay for amazing work on #OSS 00010001202 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001203 +705bonus pay for amazing work on #OSS 00010001203 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001204 +705bonus pay for amazing work on #OSS 00010001204 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001205 +705bonus pay for amazing work on #OSS 00010001205 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001206 +705bonus pay for amazing work on #OSS 00010001206 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001207 +705bonus pay for amazing work on #OSS 00010001207 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001208 +705bonus pay for amazing work on #OSS 00010001208 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001209 +705bonus pay for amazing work on #OSS 00010001209 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001210 +705bonus pay for amazing work on #OSS 00010001210 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001211 +705bonus pay for amazing work on #OSS 00010001211 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001212 +705bonus pay for amazing work on #OSS 00010001212 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001213 +705bonus pay for amazing work on #OSS 00010001213 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001214 +705bonus pay for amazing work on #OSS 00010001214 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001215 +705bonus pay for amazing work on #OSS 00010001215 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001216 +705bonus pay for amazing work on #OSS 00010001216 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001217 +705bonus pay for amazing work on #OSS 00010001217 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001218 +705bonus pay for amazing work on #OSS 00010001218 +62223138010481967038518 0000100000#MUWKV1mQlttXp#Alexander Moore 1121042880001219 +705bonus pay for amazing work on #OSS 00010001219 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001220 +705bonus pay for amazing work on #OSS 00010001220 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001221 +705bonus pay for amazing work on #OSS 00010001221 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001222 +705bonus pay for amazing work on #OSS 00010001222 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001223 +705bonus pay for amazing work on #OSS 00010001223 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001224 +705bonus pay for amazing work on #OSS 00010001224 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001225 +705bonus pay for amazing work on #OSS 00010001225 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001226 +705bonus pay for amazing work on #OSS 00010001226 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001227 +705bonus pay for amazing work on #OSS 00010001227 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001228 +705bonus pay for amazing work on #OSS 00010001228 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001229 +705bonus pay for amazing work on #OSS 00010001229 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001230 +705bonus pay for amazing work on #OSS 00010001230 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001231 +705bonus pay for amazing work on #OSS 00010001231 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001232 +705bonus pay for amazing work on #OSS 00010001232 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001233 +705bonus pay for amazing work on #OSS 00010001233 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001234 +705bonus pay for amazing work on #OSS 00010001234 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001235 +705bonus pay for amazing work on #OSS 00010001235 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001236 +705bonus pay for amazing work on #OSS 00010001236 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001237 +705bonus pay for amazing work on #OSS 00010001237 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001238 +705bonus pay for amazing work on #OSS 00010001238 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001239 +705bonus pay for amazing work on #OSS 00010001239 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001240 +705bonus pay for amazing work on #OSS 00010001240 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001241 +705bonus pay for amazing work on #OSS 00010001241 +62223138010481967038518 0000100000#kFDPQpFxBX2fy#Olivia Jones 1121042880001242 +705bonus pay for amazing work on #OSS 00010001242 +62223138010481967038518 0000100000#IzkrRSgEObtqr#Olivia Harris 1121042880001243 +705bonus pay for amazing work on #OSS 00010001243 +62223138010481967038518 0000100000#IzkrRSgEObtqr#Anthony Harris 1121042880001244 +705bonus pay for amazing work on #OSS 00010001244 +62223138010481967038518 0000100000#IzkrRSgEObtqr#Anthony Harris 1121042880001245 +705bonus pay for amazing work on #OSS 00010001245 +62223138010481967038518 0000100000#IzkrRSgEObtqr#Anthony Harris 1121042880001246 +705bonus pay for amazing work on #OSS 00010001246 +62223138010481967038518 0000100000#IzkrRSgEObtqr#Anthony Harris 1121042880001247 +705bonus pay for amazing work on #OSS 00010001247 +62223138010481967038518 0000100000#IzkrRSgEObtqr#Anthony Harris 1121042880001248 +705bonus pay for amazing work on #OSS 00010001248 +62223138010481967038518 0000100000#IzkrRSgEObtqr#Anthony Harris 1121042880001249 +705bonus pay for amazing work on #OSS 00010001249 +62223138010481967038518 0000100000#IzkrRSgEObtqr#Anthony Harris 1121042880001250 +705bonus pay for amazing work on #OSS 00010001250 +82000025008922512500000000000000000125000000121042882 121042880000001 +5200Wells Fargo 121042882 PPDTrans. Des 180511 0121042880000002 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000001 +705bonus pay for amazing work on #OSS 00010000001 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000002 +705bonus pay for amazing work on #OSS 00010000002 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000003 +705bonus pay for amazing work on #OSS 00010000003 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000004 +705bonus pay for amazing work on #OSS 00010000004 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000005 +705bonus pay for amazing work on #OSS 00010000005 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000006 +705bonus pay for amazing work on #OSS 00010000006 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000007 +705bonus pay for amazing work on #OSS 00010000007 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000008 +705bonus pay for amazing work on #OSS 00010000008 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000009 +705bonus pay for amazing work on #OSS 00010000009 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000010 +705bonus pay for amazing work on #OSS 00010000010 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000011 +705bonus pay for amazing work on #OSS 00010000011 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000012 +705bonus pay for amazing work on #OSS 00010000012 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000013 +705bonus pay for amazing work on #OSS 00010000013 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000014 +705bonus pay for amazing work on #OSS 00010000014 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000015 +705bonus pay for amazing work on #OSS 00010000015 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000016 +705bonus pay for amazing work on #OSS 00010000016 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000017 +705bonus pay for amazing work on #OSS 00010000017 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000018 +705bonus pay for amazing work on #OSS 00010000018 +62223138010481967038518 0000100000#UblwC2QbaSgXW#Elijah Jackson 1121042880000019 +705bonus pay for amazing work on #OSS 00010000019 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000020 +705bonus pay for amazing work on #OSS 00010000020 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000021 +705bonus pay for amazing work on #OSS 00010000021 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000022 +705bonus pay for amazing work on #OSS 00010000022 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000023 +705bonus pay for amazing work on #OSS 00010000023 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000024 +705bonus pay for amazing work on #OSS 00010000024 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000025 +705bonus pay for amazing work on #OSS 00010000025 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000026 +705bonus pay for amazing work on #OSS 00010000026 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000027 +705bonus pay for amazing work on #OSS 00010000027 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000028 +705bonus pay for amazing work on #OSS 00010000028 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000029 +705bonus pay for amazing work on #OSS 00010000029 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000030 +705bonus pay for amazing work on #OSS 00010000030 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000031 +705bonus pay for amazing work on #OSS 00010000031 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000032 +705bonus pay for amazing work on #OSS 00010000032 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000033 +705bonus pay for amazing work on #OSS 00010000033 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000034 +705bonus pay for amazing work on #OSS 00010000034 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000035 +705bonus pay for amazing work on #OSS 00010000035 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000036 +705bonus pay for amazing work on #OSS 00010000036 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000037 +705bonus pay for amazing work on #OSS 00010000037 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000038 +705bonus pay for amazing work on #OSS 00010000038 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000039 +705bonus pay for amazing work on #OSS 00010000039 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000040 +705bonus pay for amazing work on #OSS 00010000040 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000041 +705bonus pay for amazing work on #OSS 00010000041 +62223138010481967038518 0000100000#fbSblT11POutp#Lily Martin 1121042880000042 +705bonus pay for amazing work on #OSS 00010000042 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Lily Garcia 1121042880000043 +705bonus pay for amazing work on #OSS 00010000043 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000044 +705bonus pay for amazing work on #OSS 00010000044 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000045 +705bonus pay for amazing work on #OSS 00010000045 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000046 +705bonus pay for amazing work on #OSS 00010000046 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000047 +705bonus pay for amazing work on #OSS 00010000047 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000048 +705bonus pay for amazing work on #OSS 00010000048 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000049 +705bonus pay for amazing work on #OSS 00010000049 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000050 +705bonus pay for amazing work on #OSS 00010000050 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000051 +705bonus pay for amazing work on #OSS 00010000051 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000052 +705bonus pay for amazing work on #OSS 00010000052 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000053 +705bonus pay for amazing work on #OSS 00010000053 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000054 +705bonus pay for amazing work on #OSS 00010000054 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000055 +705bonus pay for amazing work on #OSS 00010000055 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000056 +705bonus pay for amazing work on #OSS 00010000056 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000057 +705bonus pay for amazing work on #OSS 00010000057 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000058 +705bonus pay for amazing work on #OSS 00010000058 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000059 +705bonus pay for amazing work on #OSS 00010000059 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000060 +705bonus pay for amazing work on #OSS 00010000060 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000061 +705bonus pay for amazing work on #OSS 00010000061 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000062 +705bonus pay for amazing work on #OSS 00010000062 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000063 +705bonus pay for amazing work on #OSS 00010000063 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000064 +705bonus pay for amazing work on #OSS 00010000064 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000065 +705bonus pay for amazing work on #OSS 00010000065 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000066 +705bonus pay for amazing work on #OSS 00010000066 +62223138010481967038518 0000100000#ueU3dbZ2PSZwB#Sofia Garcia 1121042880000067 +705bonus pay for amazing work on #OSS 00010000067 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000068 +705bonus pay for amazing work on #OSS 00010000068 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000069 +705bonus pay for amazing work on #OSS 00010000069 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000070 +705bonus pay for amazing work on #OSS 00010000070 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000071 +705bonus pay for amazing work on #OSS 00010000071 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000072 +705bonus pay for amazing work on #OSS 00010000072 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000073 +705bonus pay for amazing work on #OSS 00010000073 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000074 +705bonus pay for amazing work on #OSS 00010000074 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000075 +705bonus pay for amazing work on #OSS 00010000075 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000076 +705bonus pay for amazing work on #OSS 00010000076 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000077 +705bonus pay for amazing work on #OSS 00010000077 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000078 +705bonus pay for amazing work on #OSS 00010000078 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000079 +705bonus pay for amazing work on #OSS 00010000079 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000080 +705bonus pay for amazing work on #OSS 00010000080 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000081 +705bonus pay for amazing work on #OSS 00010000081 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000082 +705bonus pay for amazing work on #OSS 00010000082 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000083 +705bonus pay for amazing work on #OSS 00010000083 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000084 +705bonus pay for amazing work on #OSS 00010000084 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000085 +705bonus pay for amazing work on #OSS 00010000085 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000086 +705bonus pay for amazing work on #OSS 00010000086 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000087 +705bonus pay for amazing work on #OSS 00010000087 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000088 +705bonus pay for amazing work on #OSS 00010000088 +62223138010481967038518 0000100000#JsJvCEjt2ccRG#Ethan Williams 1121042880000089 +705bonus pay for amazing work on #OSS 00010000089 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000090 +705bonus pay for amazing work on #OSS 00010000090 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000091 +705bonus pay for amazing work on #OSS 00010000091 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000092 +705bonus pay for amazing work on #OSS 00010000092 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000093 +705bonus pay for amazing work on #OSS 00010000093 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000094 +705bonus pay for amazing work on #OSS 00010000094 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000095 +705bonus pay for amazing work on #OSS 00010000095 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000096 +705bonus pay for amazing work on #OSS 00010000096 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000097 +705bonus pay for amazing work on #OSS 00010000097 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000098 +705bonus pay for amazing work on #OSS 00010000098 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000099 +705bonus pay for amazing work on #OSS 00010000099 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000100 +705bonus pay for amazing work on #OSS 00010000100 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000101 +705bonus pay for amazing work on #OSS 00010000101 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000102 +705bonus pay for amazing work on #OSS 00010000102 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000103 +705bonus pay for amazing work on #OSS 00010000103 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000104 +705bonus pay for amazing work on #OSS 00010000104 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000105 +705bonus pay for amazing work on #OSS 00010000105 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000106 +705bonus pay for amazing work on #OSS 00010000106 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000107 +705bonus pay for amazing work on #OSS 00010000107 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000108 +705bonus pay for amazing work on #OSS 00010000108 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000109 +705bonus pay for amazing work on #OSS 00010000109 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000110 +705bonus pay for amazing work on #OSS 00010000110 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000111 +705bonus pay for amazing work on #OSS 00010000111 +62223138010481967038518 0000100000#S2pC69N62jFlf#Joshua Thompson 1121042880000112 +705bonus pay for amazing work on #OSS 00010000112 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Joshua Anderson 1121042880000113 +705bonus pay for amazing work on #OSS 00010000113 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000114 +705bonus pay for amazing work on #OSS 00010000114 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000115 +705bonus pay for amazing work on #OSS 00010000115 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000116 +705bonus pay for amazing work on #OSS 00010000116 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000117 +705bonus pay for amazing work on #OSS 00010000117 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000118 +705bonus pay for amazing work on #OSS 00010000118 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000119 +705bonus pay for amazing work on #OSS 00010000119 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000120 +705bonus pay for amazing work on #OSS 00010000120 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000121 +705bonus pay for amazing work on #OSS 00010000121 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000122 +705bonus pay for amazing work on #OSS 00010000122 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000123 +705bonus pay for amazing work on #OSS 00010000123 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000124 +705bonus pay for amazing work on #OSS 00010000124 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000125 +705bonus pay for amazing work on #OSS 00010000125 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000126 +705bonus pay for amazing work on #OSS 00010000126 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000127 +705bonus pay for amazing work on #OSS 00010000127 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000128 +705bonus pay for amazing work on #OSS 00010000128 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000129 +705bonus pay for amazing work on #OSS 00010000129 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000130 +705bonus pay for amazing work on #OSS 00010000130 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000131 +705bonus pay for amazing work on #OSS 00010000131 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000132 +705bonus pay for amazing work on #OSS 00010000132 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000133 +705bonus pay for amazing work on #OSS 00010000133 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000134 +705bonus pay for amazing work on #OSS 00010000134 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000135 +705bonus pay for amazing work on #OSS 00010000135 +62223138010481967038518 0000100000#5UYSJD0qQPpGj#Daniel Anderson 1121042880000136 +705bonus pay for amazing work on #OSS 00010000136 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000137 +705bonus pay for amazing work on #OSS 00010000137 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000138 +705bonus pay for amazing work on #OSS 00010000138 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000139 +705bonus pay for amazing work on #OSS 00010000139 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000140 +705bonus pay for amazing work on #OSS 00010000140 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000141 +705bonus pay for amazing work on #OSS 00010000141 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000142 +705bonus pay for amazing work on #OSS 00010000142 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000143 +705bonus pay for amazing work on #OSS 00010000143 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000144 +705bonus pay for amazing work on #OSS 00010000144 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000145 +705bonus pay for amazing work on #OSS 00010000145 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000146 +705bonus pay for amazing work on #OSS 00010000146 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000147 +705bonus pay for amazing work on #OSS 00010000147 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000148 +705bonus pay for amazing work on #OSS 00010000148 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000149 +705bonus pay for amazing work on #OSS 00010000149 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000150 +705bonus pay for amazing work on #OSS 00010000150 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000151 +705bonus pay for amazing work on #OSS 00010000151 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000152 +705bonus pay for amazing work on #OSS 00010000152 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000153 +705bonus pay for amazing work on #OSS 00010000153 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000154 +705bonus pay for amazing work on #OSS 00010000154 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000155 +705bonus pay for amazing work on #OSS 00010000155 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000156 +705bonus pay for amazing work on #OSS 00010000156 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000157 +705bonus pay for amazing work on #OSS 00010000157 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000158 +705bonus pay for amazing work on #OSS 00010000158 +62223138010481967038518 0000100000#CxVGsgyXGcfPW#Jacob Smith 1121042880000159 +705bonus pay for amazing work on #OSS 00010000159 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000160 +705bonus pay for amazing work on #OSS 00010000160 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000161 +705bonus pay for amazing work on #OSS 00010000161 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000162 +705bonus pay for amazing work on #OSS 00010000162 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000163 +705bonus pay for amazing work on #OSS 00010000163 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000164 +705bonus pay for amazing work on #OSS 00010000164 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000165 +705bonus pay for amazing work on #OSS 00010000165 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000166 +705bonus pay for amazing work on #OSS 00010000166 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000167 +705bonus pay for amazing work on #OSS 00010000167 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000168 +705bonus pay for amazing work on #OSS 00010000168 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000169 +705bonus pay for amazing work on #OSS 00010000169 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000170 +705bonus pay for amazing work on #OSS 00010000170 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000171 +705bonus pay for amazing work on #OSS 00010000171 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000172 +705bonus pay for amazing work on #OSS 00010000172 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000173 +705bonus pay for amazing work on #OSS 00010000173 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000174 +705bonus pay for amazing work on #OSS 00010000174 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000175 +705bonus pay for amazing work on #OSS 00010000175 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000176 +705bonus pay for amazing work on #OSS 00010000176 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000177 +705bonus pay for amazing work on #OSS 00010000177 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000178 +705bonus pay for amazing work on #OSS 00010000178 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000179 +705bonus pay for amazing work on #OSS 00010000179 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000180 +705bonus pay for amazing work on #OSS 00010000180 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000181 +705bonus pay for amazing work on #OSS 00010000181 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000182 +705bonus pay for amazing work on #OSS 00010000182 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000183 +705bonus pay for amazing work on #OSS 00010000183 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000184 +705bonus pay for amazing work on #OSS 00010000184 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000185 +705bonus pay for amazing work on #OSS 00010000185 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000186 +705bonus pay for amazing work on #OSS 00010000186 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000187 +705bonus pay for amazing work on #OSS 00010000187 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000188 +705bonus pay for amazing work on #OSS 00010000188 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000189 +705bonus pay for amazing work on #OSS 00010000189 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000190 +705bonus pay for amazing work on #OSS 00010000190 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000191 +705bonus pay for amazing work on #OSS 00010000191 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000192 +705bonus pay for amazing work on #OSS 00010000192 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000193 +705bonus pay for amazing work on #OSS 00010000193 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000194 +705bonus pay for amazing work on #OSS 00010000194 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000195 +705bonus pay for amazing work on #OSS 00010000195 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000196 +705bonus pay for amazing work on #OSS 00010000196 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000197 +705bonus pay for amazing work on #OSS 00010000197 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000198 +705bonus pay for amazing work on #OSS 00010000198 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000199 +705bonus pay for amazing work on #OSS 00010000199 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000200 +705bonus pay for amazing work on #OSS 00010000200 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000201 +705bonus pay for amazing work on #OSS 00010000201 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000202 +705bonus pay for amazing work on #OSS 00010000202 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000203 +705bonus pay for amazing work on #OSS 00010000203 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000204 +705bonus pay for amazing work on #OSS 00010000204 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000205 +705bonus pay for amazing work on #OSS 00010000205 +62223138010481967038518 0000100000#x4eueXsuua05I#Anthony Harris 1121042880000206 +705bonus pay for amazing work on #OSS 00010000206 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000207 +705bonus pay for amazing work on #OSS 00010000207 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000208 +705bonus pay for amazing work on #OSS 00010000208 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000209 +705bonus pay for amazing work on #OSS 00010000209 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000210 +705bonus pay for amazing work on #OSS 00010000210 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000211 +705bonus pay for amazing work on #OSS 00010000211 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000212 +705bonus pay for amazing work on #OSS 00010000212 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000213 +705bonus pay for amazing work on #OSS 00010000213 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000214 +705bonus pay for amazing work on #OSS 00010000214 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000215 +705bonus pay for amazing work on #OSS 00010000215 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000216 +705bonus pay for amazing work on #OSS 00010000216 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000217 +705bonus pay for amazing work on #OSS 00010000217 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000218 +705bonus pay for amazing work on #OSS 00010000218 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000219 +705bonus pay for amazing work on #OSS 00010000219 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000220 +705bonus pay for amazing work on #OSS 00010000220 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000221 +705bonus pay for amazing work on #OSS 00010000221 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000222 +705bonus pay for amazing work on #OSS 00010000222 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000223 +705bonus pay for amazing work on #OSS 00010000223 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000224 +705bonus pay for amazing work on #OSS 00010000224 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000225 +705bonus pay for amazing work on #OSS 00010000225 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000226 +705bonus pay for amazing work on #OSS 00010000226 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000227 +705bonus pay for amazing work on #OSS 00010000227 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000228 +705bonus pay for amazing work on #OSS 00010000228 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000229 +705bonus pay for amazing work on #OSS 00010000229 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000230 +705bonus pay for amazing work on #OSS 00010000230 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000231 +705bonus pay for amazing work on #OSS 00010000231 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000232 +705bonus pay for amazing work on #OSS 00010000232 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000233 +705bonus pay for amazing work on #OSS 00010000233 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000234 +705bonus pay for amazing work on #OSS 00010000234 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000235 +705bonus pay for amazing work on #OSS 00010000235 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000236 +705bonus pay for amazing work on #OSS 00010000236 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000237 +705bonus pay for amazing work on #OSS 00010000237 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000238 +705bonus pay for amazing work on #OSS 00010000238 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000239 +705bonus pay for amazing work on #OSS 00010000239 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000240 +705bonus pay for amazing work on #OSS 00010000240 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000241 +705bonus pay for amazing work on #OSS 00010000241 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000242 +705bonus pay for amazing work on #OSS 00010000242 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000243 +705bonus pay for amazing work on #OSS 00010000243 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000244 +705bonus pay for amazing work on #OSS 00010000244 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000245 +705bonus pay for amazing work on #OSS 00010000245 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000246 +705bonus pay for amazing work on #OSS 00010000246 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000247 +705bonus pay for amazing work on #OSS 00010000247 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000248 +705bonus pay for amazing work on #OSS 00010000248 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000249 +705bonus pay for amazing work on #OSS 00010000249 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000250 +705bonus pay for amazing work on #OSS 00010000250 +62223138010481967038518 0000100000#TvpBT2cs8n58E#David Martinez 1121042880000251 +705bonus pay for amazing work on #OSS 00010000251 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000252 +705bonus pay for amazing work on #OSS 00010000252 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000253 +705bonus pay for amazing work on #OSS 00010000253 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000254 +705bonus pay for amazing work on #OSS 00010000254 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000255 +705bonus pay for amazing work on #OSS 00010000255 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000256 +705bonus pay for amazing work on #OSS 00010000256 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000257 +705bonus pay for amazing work on #OSS 00010000257 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000258 +705bonus pay for amazing work on #OSS 00010000258 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000259 +705bonus pay for amazing work on #OSS 00010000259 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000260 +705bonus pay for amazing work on #OSS 00010000260 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000261 +705bonus pay for amazing work on #OSS 00010000261 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000262 +705bonus pay for amazing work on #OSS 00010000262 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000263 +705bonus pay for amazing work on #OSS 00010000263 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000264 +705bonus pay for amazing work on #OSS 00010000264 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000265 +705bonus pay for amazing work on #OSS 00010000265 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000266 +705bonus pay for amazing work on #OSS 00010000266 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000267 +705bonus pay for amazing work on #OSS 00010000267 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000268 +705bonus pay for amazing work on #OSS 00010000268 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000269 +705bonus pay for amazing work on #OSS 00010000269 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000270 +705bonus pay for amazing work on #OSS 00010000270 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000271 +705bonus pay for amazing work on #OSS 00010000271 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000272 +705bonus pay for amazing work on #OSS 00010000272 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000273 +705bonus pay for amazing work on #OSS 00010000273 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000274 +705bonus pay for amazing work on #OSS 00010000274 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000275 +705bonus pay for amazing work on #OSS 00010000275 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000276 +705bonus pay for amazing work on #OSS 00010000276 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000277 +705bonus pay for amazing work on #OSS 00010000277 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000278 +705bonus pay for amazing work on #OSS 00010000278 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000279 +705bonus pay for amazing work on #OSS 00010000279 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000280 +705bonus pay for amazing work on #OSS 00010000280 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000281 +705bonus pay for amazing work on #OSS 00010000281 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000282 +705bonus pay for amazing work on #OSS 00010000282 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000283 +705bonus pay for amazing work on #OSS 00010000283 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000284 +705bonus pay for amazing work on #OSS 00010000284 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000285 +705bonus pay for amazing work on #OSS 00010000285 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000286 +705bonus pay for amazing work on #OSS 00010000286 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000287 +705bonus pay for amazing work on #OSS 00010000287 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000288 +705bonus pay for amazing work on #OSS 00010000288 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000289 +705bonus pay for amazing work on #OSS 00010000289 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000290 +705bonus pay for amazing work on #OSS 00010000290 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000291 +705bonus pay for amazing work on #OSS 00010000291 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000292 +705bonus pay for amazing work on #OSS 00010000292 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000293 +705bonus pay for amazing work on #OSS 00010000293 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000294 +705bonus pay for amazing work on #OSS 00010000294 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000295 +705bonus pay for amazing work on #OSS 00010000295 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000296 +705bonus pay for amazing work on #OSS 00010000296 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000297 +705bonus pay for amazing work on #OSS 00010000297 +62223138010481967038518 0000100000#zpvwqt4IJs137#Daniel Anderson 1121042880000298 +705bonus pay for amazing work on #OSS 00010000298 +62223138010481967038518 0000100000#qQSukNLhk140y#Daniel Moore 1121042880000299 +705bonus pay for amazing work on #OSS 00010000299 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000300 +705bonus pay for amazing work on #OSS 00010000300 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000301 +705bonus pay for amazing work on #OSS 00010000301 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000302 +705bonus pay for amazing work on #OSS 00010000302 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000303 +705bonus pay for amazing work on #OSS 00010000303 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000304 +705bonus pay for amazing work on #OSS 00010000304 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000305 +705bonus pay for amazing work on #OSS 00010000305 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000306 +705bonus pay for amazing work on #OSS 00010000306 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000307 +705bonus pay for amazing work on #OSS 00010000307 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000308 +705bonus pay for amazing work on #OSS 00010000308 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000309 +705bonus pay for amazing work on #OSS 00010000309 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000310 +705bonus pay for amazing work on #OSS 00010000310 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000311 +705bonus pay for amazing work on #OSS 00010000311 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000312 +705bonus pay for amazing work on #OSS 00010000312 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000313 +705bonus pay for amazing work on #OSS 00010000313 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000314 +705bonus pay for amazing work on #OSS 00010000314 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000315 +705bonus pay for amazing work on #OSS 00010000315 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000316 +705bonus pay for amazing work on #OSS 00010000316 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000317 +705bonus pay for amazing work on #OSS 00010000317 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000318 +705bonus pay for amazing work on #OSS 00010000318 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000319 +705bonus pay for amazing work on #OSS 00010000319 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000320 +705bonus pay for amazing work on #OSS 00010000320 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000321 +705bonus pay for amazing work on #OSS 00010000321 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000322 +705bonus pay for amazing work on #OSS 00010000322 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000323 +705bonus pay for amazing work on #OSS 00010000323 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000324 +705bonus pay for amazing work on #OSS 00010000324 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000325 +705bonus pay for amazing work on #OSS 00010000325 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000326 +705bonus pay for amazing work on #OSS 00010000326 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000327 +705bonus pay for amazing work on #OSS 00010000327 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000328 +705bonus pay for amazing work on #OSS 00010000328 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000329 +705bonus pay for amazing work on #OSS 00010000329 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000330 +705bonus pay for amazing work on #OSS 00010000330 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000331 +705bonus pay for amazing work on #OSS 00010000331 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000332 +705bonus pay for amazing work on #OSS 00010000332 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000333 +705bonus pay for amazing work on #OSS 00010000333 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000334 +705bonus pay for amazing work on #OSS 00010000334 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000335 +705bonus pay for amazing work on #OSS 00010000335 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000336 +705bonus pay for amazing work on #OSS 00010000336 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000337 +705bonus pay for amazing work on #OSS 00010000337 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000338 +705bonus pay for amazing work on #OSS 00010000338 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000339 +705bonus pay for amazing work on #OSS 00010000339 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000340 +705bonus pay for amazing work on #OSS 00010000340 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000341 +705bonus pay for amazing work on #OSS 00010000341 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000342 +705bonus pay for amazing work on #OSS 00010000342 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000343 +705bonus pay for amazing work on #OSS 00010000343 +62223138010481967038518 0000100000#qQSukNLhk140y#Alexander Moore 1121042880000344 +705bonus pay for amazing work on #OSS 00010000344 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Alexander Jones 1121042880000345 +705bonus pay for amazing work on #OSS 00010000345 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000346 +705bonus pay for amazing work on #OSS 00010000346 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000347 +705bonus pay for amazing work on #OSS 00010000347 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000348 +705bonus pay for amazing work on #OSS 00010000348 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000349 +705bonus pay for amazing work on #OSS 00010000349 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000350 +705bonus pay for amazing work on #OSS 00010000350 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000351 +705bonus pay for amazing work on #OSS 00010000351 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000352 +705bonus pay for amazing work on #OSS 00010000352 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000353 +705bonus pay for amazing work on #OSS 00010000353 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000354 +705bonus pay for amazing work on #OSS 00010000354 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000355 +705bonus pay for amazing work on #OSS 00010000355 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000356 +705bonus pay for amazing work on #OSS 00010000356 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000357 +705bonus pay for amazing work on #OSS 00010000357 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000358 +705bonus pay for amazing work on #OSS 00010000358 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000359 +705bonus pay for amazing work on #OSS 00010000359 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000360 +705bonus pay for amazing work on #OSS 00010000360 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000361 +705bonus pay for amazing work on #OSS 00010000361 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000362 +705bonus pay for amazing work on #OSS 00010000362 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000363 +705bonus pay for amazing work on #OSS 00010000363 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000364 +705bonus pay for amazing work on #OSS 00010000364 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000365 +705bonus pay for amazing work on #OSS 00010000365 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000366 +705bonus pay for amazing work on #OSS 00010000366 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000367 +705bonus pay for amazing work on #OSS 00010000367 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000368 +705bonus pay for amazing work on #OSS 00010000368 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000369 +705bonus pay for amazing work on #OSS 00010000369 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000370 +705bonus pay for amazing work on #OSS 00010000370 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000371 +705bonus pay for amazing work on #OSS 00010000371 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000372 +705bonus pay for amazing work on #OSS 00010000372 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000373 +705bonus pay for amazing work on #OSS 00010000373 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000374 +705bonus pay for amazing work on #OSS 00010000374 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000375 +705bonus pay for amazing work on #OSS 00010000375 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000376 +705bonus pay for amazing work on #OSS 00010000376 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000377 +705bonus pay for amazing work on #OSS 00010000377 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000378 +705bonus pay for amazing work on #OSS 00010000378 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000379 +705bonus pay for amazing work on #OSS 00010000379 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000380 +705bonus pay for amazing work on #OSS 00010000380 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000381 +705bonus pay for amazing work on #OSS 00010000381 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000382 +705bonus pay for amazing work on #OSS 00010000382 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000383 +705bonus pay for amazing work on #OSS 00010000383 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000384 +705bonus pay for amazing work on #OSS 00010000384 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000385 +705bonus pay for amazing work on #OSS 00010000385 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000386 +705bonus pay for amazing work on #OSS 00010000386 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000387 +705bonus pay for amazing work on #OSS 00010000387 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000388 +705bonus pay for amazing work on #OSS 00010000388 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000389 +705bonus pay for amazing work on #OSS 00010000389 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000390 +705bonus pay for amazing work on #OSS 00010000390 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000391 +705bonus pay for amazing work on #OSS 00010000391 +62223138010481967038518 0000100000#KSROnbEzOzZz5#Olivia Jones 1121042880000392 +705bonus pay for amazing work on #OSS 00010000392 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000393 +705bonus pay for amazing work on #OSS 00010000393 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000394 +705bonus pay for amazing work on #OSS 00010000394 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000395 +705bonus pay for amazing work on #OSS 00010000395 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000396 +705bonus pay for amazing work on #OSS 00010000396 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000397 +705bonus pay for amazing work on #OSS 00010000397 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000398 +705bonus pay for amazing work on #OSS 00010000398 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000399 +705bonus pay for amazing work on #OSS 00010000399 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000400 +705bonus pay for amazing work on #OSS 00010000400 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000401 +705bonus pay for amazing work on #OSS 00010000401 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000402 +705bonus pay for amazing work on #OSS 00010000402 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000403 +705bonus pay for amazing work on #OSS 00010000403 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000404 +705bonus pay for amazing work on #OSS 00010000404 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000405 +705bonus pay for amazing work on #OSS 00010000405 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000406 +705bonus pay for amazing work on #OSS 00010000406 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000407 +705bonus pay for amazing work on #OSS 00010000407 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000408 +705bonus pay for amazing work on #OSS 00010000408 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000409 +705bonus pay for amazing work on #OSS 00010000409 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000410 +705bonus pay for amazing work on #OSS 00010000410 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000411 +705bonus pay for amazing work on #OSS 00010000411 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000412 +705bonus pay for amazing work on #OSS 00010000412 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000413 +705bonus pay for amazing work on #OSS 00010000413 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000414 +705bonus pay for amazing work on #OSS 00010000414 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000415 +705bonus pay for amazing work on #OSS 00010000415 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000416 +705bonus pay for amazing work on #OSS 00010000416 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000417 +705bonus pay for amazing work on #OSS 00010000417 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000418 +705bonus pay for amazing work on #OSS 00010000418 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000419 +705bonus pay for amazing work on #OSS 00010000419 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000420 +705bonus pay for amazing work on #OSS 00010000420 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000421 +705bonus pay for amazing work on #OSS 00010000421 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000422 +705bonus pay for amazing work on #OSS 00010000422 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000423 +705bonus pay for amazing work on #OSS 00010000423 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000424 +705bonus pay for amazing work on #OSS 00010000424 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000425 +705bonus pay for amazing work on #OSS 00010000425 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000426 +705bonus pay for amazing work on #OSS 00010000426 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000427 +705bonus pay for amazing work on #OSS 00010000427 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000428 +705bonus pay for amazing work on #OSS 00010000428 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000429 +705bonus pay for amazing work on #OSS 00010000429 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000430 +705bonus pay for amazing work on #OSS 00010000430 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000431 +705bonus pay for amazing work on #OSS 00010000431 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000432 +705bonus pay for amazing work on #OSS 00010000432 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000433 +705bonus pay for amazing work on #OSS 00010000433 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000434 +705bonus pay for amazing work on #OSS 00010000434 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000435 +705bonus pay for amazing work on #OSS 00010000435 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000436 +705bonus pay for amazing work on #OSS 00010000436 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000437 +705bonus pay for amazing work on #OSS 00010000437 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000438 +705bonus pay for amazing work on #OSS 00010000438 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000439 +705bonus pay for amazing work on #OSS 00010000439 +62223138010481967038518 0000100000#qGfC4CEkKi5ty#Olivia Jones 1121042880000440 +705bonus pay for amazing work on #OSS 00010000440 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000441 +705bonus pay for amazing work on #OSS 00010000441 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000442 +705bonus pay for amazing work on #OSS 00010000442 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000443 +705bonus pay for amazing work on #OSS 00010000443 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000444 +705bonus pay for amazing work on #OSS 00010000444 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000445 +705bonus pay for amazing work on #OSS 00010000445 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000446 +705bonus pay for amazing work on #OSS 00010000446 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000447 +705bonus pay for amazing work on #OSS 00010000447 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000448 +705bonus pay for amazing work on #OSS 00010000448 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000449 +705bonus pay for amazing work on #OSS 00010000449 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000450 +705bonus pay for amazing work on #OSS 00010000450 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000451 +705bonus pay for amazing work on #OSS 00010000451 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000452 +705bonus pay for amazing work on #OSS 00010000452 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000453 +705bonus pay for amazing work on #OSS 00010000453 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000454 +705bonus pay for amazing work on #OSS 00010000454 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000455 +705bonus pay for amazing work on #OSS 00010000455 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000456 +705bonus pay for amazing work on #OSS 00010000456 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000457 +705bonus pay for amazing work on #OSS 00010000457 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000458 +705bonus pay for amazing work on #OSS 00010000458 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000459 +705bonus pay for amazing work on #OSS 00010000459 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000460 +705bonus pay for amazing work on #OSS 00010000460 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000461 +705bonus pay for amazing work on #OSS 00010000461 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000462 +705bonus pay for amazing work on #OSS 00010000462 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000463 +705bonus pay for amazing work on #OSS 00010000463 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000464 +705bonus pay for amazing work on #OSS 00010000464 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000465 +705bonus pay for amazing work on #OSS 00010000465 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000466 +705bonus pay for amazing work on #OSS 00010000466 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000467 +705bonus pay for amazing work on #OSS 00010000467 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000468 +705bonus pay for amazing work on #OSS 00010000468 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000469 +705bonus pay for amazing work on #OSS 00010000469 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000470 +705bonus pay for amazing work on #OSS 00010000470 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000471 +705bonus pay for amazing work on #OSS 00010000471 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000472 +705bonus pay for amazing work on #OSS 00010000472 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000473 +705bonus pay for amazing work on #OSS 00010000473 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000474 +705bonus pay for amazing work on #OSS 00010000474 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000475 +705bonus pay for amazing work on #OSS 00010000475 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000476 +705bonus pay for amazing work on #OSS 00010000476 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000477 +705bonus pay for amazing work on #OSS 00010000477 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000478 +705bonus pay for amazing work on #OSS 00010000478 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000479 +705bonus pay for amazing work on #OSS 00010000479 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000480 +705bonus pay for amazing work on #OSS 00010000480 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000481 +705bonus pay for amazing work on #OSS 00010000481 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000482 +705bonus pay for amazing work on #OSS 00010000482 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000483 +705bonus pay for amazing work on #OSS 00010000483 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000484 +705bonus pay for amazing work on #OSS 00010000484 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000485 +705bonus pay for amazing work on #OSS 00010000485 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000486 +705bonus pay for amazing work on #OSS 00010000486 +62223138010481967038518 0000100000#Zf9gxgQdwtwjb#Elijah Jackson 1121042880000487 +705bonus pay for amazing work on #OSS 00010000487 +62223138010481967038518 0000100000#9CIelVIfeAByE#Aiden Taylor 1121042880000488 +705bonus pay for amazing work on #OSS 00010000488 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000489 +705bonus pay for amazing work on #OSS 00010000489 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000490 +705bonus pay for amazing work on #OSS 00010000490 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000491 +705bonus pay for amazing work on #OSS 00010000491 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000492 +705bonus pay for amazing work on #OSS 00010000492 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000493 +705bonus pay for amazing work on #OSS 00010000493 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000494 +705bonus pay for amazing work on #OSS 00010000494 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000495 +705bonus pay for amazing work on #OSS 00010000495 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000496 +705bonus pay for amazing work on #OSS 00010000496 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000497 +705bonus pay for amazing work on #OSS 00010000497 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000498 +705bonus pay for amazing work on #OSS 00010000498 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000499 +705bonus pay for amazing work on #OSS 00010000499 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000500 +705bonus pay for amazing work on #OSS 00010000500 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000501 +705bonus pay for amazing work on #OSS 00010000501 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000502 +705bonus pay for amazing work on #OSS 00010000502 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000503 +705bonus pay for amazing work on #OSS 00010000503 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000504 +705bonus pay for amazing work on #OSS 00010000504 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000505 +705bonus pay for amazing work on #OSS 00010000505 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000506 +705bonus pay for amazing work on #OSS 00010000506 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000507 +705bonus pay for amazing work on #OSS 00010000507 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000508 +705bonus pay for amazing work on #OSS 00010000508 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000509 +705bonus pay for amazing work on #OSS 00010000509 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000510 +705bonus pay for amazing work on #OSS 00010000510 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000511 +705bonus pay for amazing work on #OSS 00010000511 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000512 +705bonus pay for amazing work on #OSS 00010000512 +62223138010481967038518 0000100000#9CIelVIfeAByE#Elizabeth Taylor 1121042880000513 +705bonus pay for amazing work on #OSS 00010000513 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Elizabeth Thompson 1121042880000514 +705bonus pay for amazing work on #OSS 00010000514 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000515 +705bonus pay for amazing work on #OSS 00010000515 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000516 +705bonus pay for amazing work on #OSS 00010000516 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000517 +705bonus pay for amazing work on #OSS 00010000517 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000518 +705bonus pay for amazing work on #OSS 00010000518 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000519 +705bonus pay for amazing work on #OSS 00010000519 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000520 +705bonus pay for amazing work on #OSS 00010000520 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000521 +705bonus pay for amazing work on #OSS 00010000521 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000522 +705bonus pay for amazing work on #OSS 00010000522 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000523 +705bonus pay for amazing work on #OSS 00010000523 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000524 +705bonus pay for amazing work on #OSS 00010000524 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000525 +705bonus pay for amazing work on #OSS 00010000525 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000526 +705bonus pay for amazing work on #OSS 00010000526 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000527 +705bonus pay for amazing work on #OSS 00010000527 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000528 +705bonus pay for amazing work on #OSS 00010000528 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000529 +705bonus pay for amazing work on #OSS 00010000529 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000530 +705bonus pay for amazing work on #OSS 00010000530 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000531 +705bonus pay for amazing work on #OSS 00010000531 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000532 +705bonus pay for amazing work on #OSS 00010000532 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000533 +705bonus pay for amazing work on #OSS 00010000533 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000534 +705bonus pay for amazing work on #OSS 00010000534 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000535 +705bonus pay for amazing work on #OSS 00010000535 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000536 +705bonus pay for amazing work on #OSS 00010000536 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000537 +705bonus pay for amazing work on #OSS 00010000537 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000538 +705bonus pay for amazing work on #OSS 00010000538 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000539 +705bonus pay for amazing work on #OSS 00010000539 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000540 +705bonus pay for amazing work on #OSS 00010000540 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000541 +705bonus pay for amazing work on #OSS 00010000541 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000542 +705bonus pay for amazing work on #OSS 00010000542 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000543 +705bonus pay for amazing work on #OSS 00010000543 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000544 +705bonus pay for amazing work on #OSS 00010000544 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000545 +705bonus pay for amazing work on #OSS 00010000545 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000546 +705bonus pay for amazing work on #OSS 00010000546 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000547 +705bonus pay for amazing work on #OSS 00010000547 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000548 +705bonus pay for amazing work on #OSS 00010000548 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000549 +705bonus pay for amazing work on #OSS 00010000549 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000550 +705bonus pay for amazing work on #OSS 00010000550 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000551 +705bonus pay for amazing work on #OSS 00010000551 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000552 +705bonus pay for amazing work on #OSS 00010000552 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000553 +705bonus pay for amazing work on #OSS 00010000553 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000554 +705bonus pay for amazing work on #OSS 00010000554 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000555 +705bonus pay for amazing work on #OSS 00010000555 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000556 +705bonus pay for amazing work on #OSS 00010000556 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000557 +705bonus pay for amazing work on #OSS 00010000557 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000558 +705bonus pay for amazing work on #OSS 00010000558 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000559 +705bonus pay for amazing work on #OSS 00010000559 +62223138010481967038518 0000100000#3M2BkWkB9nKTG#Joshua Thompson 1121042880000560 +705bonus pay for amazing work on #OSS 00010000560 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000561 +705bonus pay for amazing work on #OSS 00010000561 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000562 +705bonus pay for amazing work on #OSS 00010000562 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000563 +705bonus pay for amazing work on #OSS 00010000563 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000564 +705bonus pay for amazing work on #OSS 00010000564 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000565 +705bonus pay for amazing work on #OSS 00010000565 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000566 +705bonus pay for amazing work on #OSS 00010000566 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000567 +705bonus pay for amazing work on #OSS 00010000567 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000568 +705bonus pay for amazing work on #OSS 00010000568 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000569 +705bonus pay for amazing work on #OSS 00010000569 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000570 +705bonus pay for amazing work on #OSS 00010000570 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000571 +705bonus pay for amazing work on #OSS 00010000571 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000572 +705bonus pay for amazing work on #OSS 00010000572 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000573 +705bonus pay for amazing work on #OSS 00010000573 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000574 +705bonus pay for amazing work on #OSS 00010000574 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000575 +705bonus pay for amazing work on #OSS 00010000575 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000576 +705bonus pay for amazing work on #OSS 00010000576 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000577 +705bonus pay for amazing work on #OSS 00010000577 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000578 +705bonus pay for amazing work on #OSS 00010000578 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000579 +705bonus pay for amazing work on #OSS 00010000579 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000580 +705bonus pay for amazing work on #OSS 00010000580 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000581 +705bonus pay for amazing work on #OSS 00010000581 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000582 +705bonus pay for amazing work on #OSS 00010000582 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000583 +705bonus pay for amazing work on #OSS 00010000583 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000584 +705bonus pay for amazing work on #OSS 00010000584 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000585 +705bonus pay for amazing work on #OSS 00010000585 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000586 +705bonus pay for amazing work on #OSS 00010000586 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000587 +705bonus pay for amazing work on #OSS 00010000587 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000588 +705bonus pay for amazing work on #OSS 00010000588 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000589 +705bonus pay for amazing work on #OSS 00010000589 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000590 +705bonus pay for amazing work on #OSS 00010000590 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000591 +705bonus pay for amazing work on #OSS 00010000591 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000592 +705bonus pay for amazing work on #OSS 00010000592 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000593 +705bonus pay for amazing work on #OSS 00010000593 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000594 +705bonus pay for amazing work on #OSS 00010000594 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000595 +705bonus pay for amazing work on #OSS 00010000595 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000596 +705bonus pay for amazing work on #OSS 00010000596 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000597 +705bonus pay for amazing work on #OSS 00010000597 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000598 +705bonus pay for amazing work on #OSS 00010000598 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000599 +705bonus pay for amazing work on #OSS 00010000599 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000600 +705bonus pay for amazing work on #OSS 00010000600 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000601 +705bonus pay for amazing work on #OSS 00010000601 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000602 +705bonus pay for amazing work on #OSS 00010000602 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000603 +705bonus pay for amazing work on #OSS 00010000603 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000604 +705bonus pay for amazing work on #OSS 00010000604 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000605 +705bonus pay for amazing work on #OSS 00010000605 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000606 +705bonus pay for amazing work on #OSS 00010000606 +62223138010481967038518 0000100000#Mk3vmQ29Wqp6P#David Martinez 1121042880000607 +705bonus pay for amazing work on #OSS 00010000607 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#David Brown 1121042880000608 +705bonus pay for amazing work on #OSS 00010000608 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000609 +705bonus pay for amazing work on #OSS 00010000609 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000610 +705bonus pay for amazing work on #OSS 00010000610 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000611 +705bonus pay for amazing work on #OSS 00010000611 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000612 +705bonus pay for amazing work on #OSS 00010000612 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000613 +705bonus pay for amazing work on #OSS 00010000613 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000614 +705bonus pay for amazing work on #OSS 00010000614 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000615 +705bonus pay for amazing work on #OSS 00010000615 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000616 +705bonus pay for amazing work on #OSS 00010000616 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000617 +705bonus pay for amazing work on #OSS 00010000617 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000618 +705bonus pay for amazing work on #OSS 00010000618 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000619 +705bonus pay for amazing work on #OSS 00010000619 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000620 +705bonus pay for amazing work on #OSS 00010000620 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000621 +705bonus pay for amazing work on #OSS 00010000621 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000622 +705bonus pay for amazing work on #OSS 00010000622 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000623 +705bonus pay for amazing work on #OSS 00010000623 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000624 +705bonus pay for amazing work on #OSS 00010000624 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000625 +705bonus pay for amazing work on #OSS 00010000625 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000626 +705bonus pay for amazing work on #OSS 00010000626 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000627 +705bonus pay for amazing work on #OSS 00010000627 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000628 +705bonus pay for amazing work on #OSS 00010000628 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000629 +705bonus pay for amazing work on #OSS 00010000629 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000630 +705bonus pay for amazing work on #OSS 00010000630 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000631 +705bonus pay for amazing work on #OSS 00010000631 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000632 +705bonus pay for amazing work on #OSS 00010000632 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000633 +705bonus pay for amazing work on #OSS 00010000633 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000634 +705bonus pay for amazing work on #OSS 00010000634 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000635 +705bonus pay for amazing work on #OSS 00010000635 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000636 +705bonus pay for amazing work on #OSS 00010000636 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000637 +705bonus pay for amazing work on #OSS 00010000637 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000638 +705bonus pay for amazing work on #OSS 00010000638 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000639 +705bonus pay for amazing work on #OSS 00010000639 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000640 +705bonus pay for amazing work on #OSS 00010000640 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000641 +705bonus pay for amazing work on #OSS 00010000641 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000642 +705bonus pay for amazing work on #OSS 00010000642 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000643 +705bonus pay for amazing work on #OSS 00010000643 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000644 +705bonus pay for amazing work on #OSS 00010000644 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000645 +705bonus pay for amazing work on #OSS 00010000645 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000646 +705bonus pay for amazing work on #OSS 00010000646 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000647 +705bonus pay for amazing work on #OSS 00010000647 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000648 +705bonus pay for amazing work on #OSS 00010000648 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000649 +705bonus pay for amazing work on #OSS 00010000649 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000650 +705bonus pay for amazing work on #OSS 00010000650 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000651 +705bonus pay for amazing work on #OSS 00010000651 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000652 +705bonus pay for amazing work on #OSS 00010000652 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000653 +705bonus pay for amazing work on #OSS 00010000653 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000654 +705bonus pay for amazing work on #OSS 00010000654 +62223138010481967038518 0000100000#sAQg9HmBHf3P9#William Brown 1121042880000655 +705bonus pay for amazing work on #OSS 00010000655 +62223138010481967038518 0000100000#yjryMojKJvGFH#Michael Wilson 1121042880000656 +705bonus pay for amazing work on #OSS 00010000656 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000657 +705bonus pay for amazing work on #OSS 00010000657 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000658 +705bonus pay for amazing work on #OSS 00010000658 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000659 +705bonus pay for amazing work on #OSS 00010000659 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000660 +705bonus pay for amazing work on #OSS 00010000660 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000661 +705bonus pay for amazing work on #OSS 00010000661 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000662 +705bonus pay for amazing work on #OSS 00010000662 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000663 +705bonus pay for amazing work on #OSS 00010000663 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000664 +705bonus pay for amazing work on #OSS 00010000664 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000665 +705bonus pay for amazing work on #OSS 00010000665 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000666 +705bonus pay for amazing work on #OSS 00010000666 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000667 +705bonus pay for amazing work on #OSS 00010000667 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000668 +705bonus pay for amazing work on #OSS 00010000668 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000669 +705bonus pay for amazing work on #OSS 00010000669 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000670 +705bonus pay for amazing work on #OSS 00010000670 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000671 +705bonus pay for amazing work on #OSS 00010000671 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000672 +705bonus pay for amazing work on #OSS 00010000672 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000673 +705bonus pay for amazing work on #OSS 00010000673 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000674 +705bonus pay for amazing work on #OSS 00010000674 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000675 +705bonus pay for amazing work on #OSS 00010000675 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000676 +705bonus pay for amazing work on #OSS 00010000676 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000677 +705bonus pay for amazing work on #OSS 00010000677 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000678 +705bonus pay for amazing work on #OSS 00010000678 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000679 +705bonus pay for amazing work on #OSS 00010000679 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000680 +705bonus pay for amazing work on #OSS 00010000680 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000681 +705bonus pay for amazing work on #OSS 00010000681 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000682 +705bonus pay for amazing work on #OSS 00010000682 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000683 +705bonus pay for amazing work on #OSS 00010000683 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000684 +705bonus pay for amazing work on #OSS 00010000684 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000685 +705bonus pay for amazing work on #OSS 00010000685 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000686 +705bonus pay for amazing work on #OSS 00010000686 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000687 +705bonus pay for amazing work on #OSS 00010000687 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000688 +705bonus pay for amazing work on #OSS 00010000688 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000689 +705bonus pay for amazing work on #OSS 00010000689 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000690 +705bonus pay for amazing work on #OSS 00010000690 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000691 +705bonus pay for amazing work on #OSS 00010000691 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000692 +705bonus pay for amazing work on #OSS 00010000692 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000693 +705bonus pay for amazing work on #OSS 00010000693 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000694 +705bonus pay for amazing work on #OSS 00010000694 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000695 +705bonus pay for amazing work on #OSS 00010000695 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000696 +705bonus pay for amazing work on #OSS 00010000696 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000697 +705bonus pay for amazing work on #OSS 00010000697 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000698 +705bonus pay for amazing work on #OSS 00010000698 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000699 +705bonus pay for amazing work on #OSS 00010000699 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000700 +705bonus pay for amazing work on #OSS 00010000700 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000701 +705bonus pay for amazing work on #OSS 00010000701 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000702 +705bonus pay for amazing work on #OSS 00010000702 +62223138010481967038518 0000100000#yjryMojKJvGFH#Mia Wilson 1121042880000703 +705bonus pay for amazing work on #OSS 00010000703 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000704 +705bonus pay for amazing work on #OSS 00010000704 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000705 +705bonus pay for amazing work on #OSS 00010000705 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000706 +705bonus pay for amazing work on #OSS 00010000706 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000707 +705bonus pay for amazing work on #OSS 00010000707 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000708 +705bonus pay for amazing work on #OSS 00010000708 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000709 +705bonus pay for amazing work on #OSS 00010000709 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000710 +705bonus pay for amazing work on #OSS 00010000710 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000711 +705bonus pay for amazing work on #OSS 00010000711 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000712 +705bonus pay for amazing work on #OSS 00010000712 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000713 +705bonus pay for amazing work on #OSS 00010000713 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000714 +705bonus pay for amazing work on #OSS 00010000714 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000715 +705bonus pay for amazing work on #OSS 00010000715 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000716 +705bonus pay for amazing work on #OSS 00010000716 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000717 +705bonus pay for amazing work on #OSS 00010000717 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000718 +705bonus pay for amazing work on #OSS 00010000718 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000719 +705bonus pay for amazing work on #OSS 00010000719 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000720 +705bonus pay for amazing work on #OSS 00010000720 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000721 +705bonus pay for amazing work on #OSS 00010000721 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000722 +705bonus pay for amazing work on #OSS 00010000722 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000723 +705bonus pay for amazing work on #OSS 00010000723 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000724 +705bonus pay for amazing work on #OSS 00010000724 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000725 +705bonus pay for amazing work on #OSS 00010000725 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000726 +705bonus pay for amazing work on #OSS 00010000726 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000727 +705bonus pay for amazing work on #OSS 00010000727 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000728 +705bonus pay for amazing work on #OSS 00010000728 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000729 +705bonus pay for amazing work on #OSS 00010000729 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000730 +705bonus pay for amazing work on #OSS 00010000730 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000731 +705bonus pay for amazing work on #OSS 00010000731 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000732 +705bonus pay for amazing work on #OSS 00010000732 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000733 +705bonus pay for amazing work on #OSS 00010000733 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000734 +705bonus pay for amazing work on #OSS 00010000734 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000735 +705bonus pay for amazing work on #OSS 00010000735 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000736 +705bonus pay for amazing work on #OSS 00010000736 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000737 +705bonus pay for amazing work on #OSS 00010000737 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000738 +705bonus pay for amazing work on #OSS 00010000738 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000739 +705bonus pay for amazing work on #OSS 00010000739 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000740 +705bonus pay for amazing work on #OSS 00010000740 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000741 +705bonus pay for amazing work on #OSS 00010000741 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000742 +705bonus pay for amazing work on #OSS 00010000742 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000743 +705bonus pay for amazing work on #OSS 00010000743 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000744 +705bonus pay for amazing work on #OSS 00010000744 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000745 +705bonus pay for amazing work on #OSS 00010000745 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000746 +705bonus pay for amazing work on #OSS 00010000746 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000747 +705bonus pay for amazing work on #OSS 00010000747 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000748 +705bonus pay for amazing work on #OSS 00010000748 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000749 +705bonus pay for amazing work on #OSS 00010000749 +62223138010481967038518 0000100000#UlYDGHuV1J6ZN#Joshua Thompson 1121042880000750 +705bonus pay for amazing work on #OSS 00010000750 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Joshua Martin 1121042880000751 +705bonus pay for amazing work on #OSS 00010000751 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000752 +705bonus pay for amazing work on #OSS 00010000752 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000753 +705bonus pay for amazing work on #OSS 00010000753 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000754 +705bonus pay for amazing work on #OSS 00010000754 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000755 +705bonus pay for amazing work on #OSS 00010000755 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000756 +705bonus pay for amazing work on #OSS 00010000756 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000757 +705bonus pay for amazing work on #OSS 00010000757 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000758 +705bonus pay for amazing work on #OSS 00010000758 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000759 +705bonus pay for amazing work on #OSS 00010000759 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000760 +705bonus pay for amazing work on #OSS 00010000760 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000761 +705bonus pay for amazing work on #OSS 00010000761 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000762 +705bonus pay for amazing work on #OSS 00010000762 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000763 +705bonus pay for amazing work on #OSS 00010000763 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000764 +705bonus pay for amazing work on #OSS 00010000764 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000765 +705bonus pay for amazing work on #OSS 00010000765 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000766 +705bonus pay for amazing work on #OSS 00010000766 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000767 +705bonus pay for amazing work on #OSS 00010000767 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000768 +705bonus pay for amazing work on #OSS 00010000768 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000769 +705bonus pay for amazing work on #OSS 00010000769 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000770 +705bonus pay for amazing work on #OSS 00010000770 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000771 +705bonus pay for amazing work on #OSS 00010000771 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000772 +705bonus pay for amazing work on #OSS 00010000772 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000773 +705bonus pay for amazing work on #OSS 00010000773 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000774 +705bonus pay for amazing work on #OSS 00010000774 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000775 +705bonus pay for amazing work on #OSS 00010000775 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000776 +705bonus pay for amazing work on #OSS 00010000776 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000777 +705bonus pay for amazing work on #OSS 00010000777 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000778 +705bonus pay for amazing work on #OSS 00010000778 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000779 +705bonus pay for amazing work on #OSS 00010000779 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000780 +705bonus pay for amazing work on #OSS 00010000780 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000781 +705bonus pay for amazing work on #OSS 00010000781 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000782 +705bonus pay for amazing work on #OSS 00010000782 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000783 +705bonus pay for amazing work on #OSS 00010000783 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000784 +705bonus pay for amazing work on #OSS 00010000784 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000785 +705bonus pay for amazing work on #OSS 00010000785 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000786 +705bonus pay for amazing work on #OSS 00010000786 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000787 +705bonus pay for amazing work on #OSS 00010000787 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000788 +705bonus pay for amazing work on #OSS 00010000788 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000789 +705bonus pay for amazing work on #OSS 00010000789 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000790 +705bonus pay for amazing work on #OSS 00010000790 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000791 +705bonus pay for amazing work on #OSS 00010000791 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000792 +705bonus pay for amazing work on #OSS 00010000792 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000793 +705bonus pay for amazing work on #OSS 00010000793 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000794 +705bonus pay for amazing work on #OSS 00010000794 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000795 +705bonus pay for amazing work on #OSS 00010000795 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000796 +705bonus pay for amazing work on #OSS 00010000796 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000797 +705bonus pay for amazing work on #OSS 00010000797 +62223138010481967038518 0000100000#DLkXQKNIxxpsW#Lily Martin 1121042880000798 +705bonus pay for amazing work on #OSS 00010000798 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000799 +705bonus pay for amazing work on #OSS 00010000799 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000800 +705bonus pay for amazing work on #OSS 00010000800 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000801 +705bonus pay for amazing work on #OSS 00010000801 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000802 +705bonus pay for amazing work on #OSS 00010000802 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000803 +705bonus pay for amazing work on #OSS 00010000803 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000804 +705bonus pay for amazing work on #OSS 00010000804 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000805 +705bonus pay for amazing work on #OSS 00010000805 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000806 +705bonus pay for amazing work on #OSS 00010000806 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000807 +705bonus pay for amazing work on #OSS 00010000807 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000808 +705bonus pay for amazing work on #OSS 00010000808 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000809 +705bonus pay for amazing work on #OSS 00010000809 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000810 +705bonus pay for amazing work on #OSS 00010000810 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000811 +705bonus pay for amazing work on #OSS 00010000811 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000812 +705bonus pay for amazing work on #OSS 00010000812 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000813 +705bonus pay for amazing work on #OSS 00010000813 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000814 +705bonus pay for amazing work on #OSS 00010000814 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000815 +705bonus pay for amazing work on #OSS 00010000815 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000816 +705bonus pay for amazing work on #OSS 00010000816 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000817 +705bonus pay for amazing work on #OSS 00010000817 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000818 +705bonus pay for amazing work on #OSS 00010000818 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000819 +705bonus pay for amazing work on #OSS 00010000819 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000820 +705bonus pay for amazing work on #OSS 00010000820 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000821 +705bonus pay for amazing work on #OSS 00010000821 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000822 +705bonus pay for amazing work on #OSS 00010000822 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000823 +705bonus pay for amazing work on #OSS 00010000823 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000824 +705bonus pay for amazing work on #OSS 00010000824 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000825 +705bonus pay for amazing work on #OSS 00010000825 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000826 +705bonus pay for amazing work on #OSS 00010000826 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000827 +705bonus pay for amazing work on #OSS 00010000827 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000828 +705bonus pay for amazing work on #OSS 00010000828 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000829 +705bonus pay for amazing work on #OSS 00010000829 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000830 +705bonus pay for amazing work on #OSS 00010000830 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000831 +705bonus pay for amazing work on #OSS 00010000831 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000832 +705bonus pay for amazing work on #OSS 00010000832 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000833 +705bonus pay for amazing work on #OSS 00010000833 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000834 +705bonus pay for amazing work on #OSS 00010000834 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000835 +705bonus pay for amazing work on #OSS 00010000835 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000836 +705bonus pay for amazing work on #OSS 00010000836 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000837 +705bonus pay for amazing work on #OSS 00010000837 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000838 +705bonus pay for amazing work on #OSS 00010000838 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000839 +705bonus pay for amazing work on #OSS 00010000839 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000840 +705bonus pay for amazing work on #OSS 00010000840 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000841 +705bonus pay for amazing work on #OSS 00010000841 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000842 +705bonus pay for amazing work on #OSS 00010000842 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000843 +705bonus pay for amazing work on #OSS 00010000843 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000844 +705bonus pay for amazing work on #OSS 00010000844 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000845 +705bonus pay for amazing work on #OSS 00010000845 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000846 +705bonus pay for amazing work on #OSS 00010000846 +62223138010481967038518 0000100000#Y9L2slAFJy3Ql#Olivia Jones 1121042880000847 +705bonus pay for amazing work on #OSS 00010000847 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Olivia Anderson 1121042880000848 +705bonus pay for amazing work on #OSS 00010000848 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000849 +705bonus pay for amazing work on #OSS 00010000849 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000850 +705bonus pay for amazing work on #OSS 00010000850 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000851 +705bonus pay for amazing work on #OSS 00010000851 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000852 +705bonus pay for amazing work on #OSS 00010000852 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000853 +705bonus pay for amazing work on #OSS 00010000853 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000854 +705bonus pay for amazing work on #OSS 00010000854 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000855 +705bonus pay for amazing work on #OSS 00010000855 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000856 +705bonus pay for amazing work on #OSS 00010000856 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000857 +705bonus pay for amazing work on #OSS 00010000857 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000858 +705bonus pay for amazing work on #OSS 00010000858 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000859 +705bonus pay for amazing work on #OSS 00010000859 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000860 +705bonus pay for amazing work on #OSS 00010000860 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000861 +705bonus pay for amazing work on #OSS 00010000861 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000862 +705bonus pay for amazing work on #OSS 00010000862 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000863 +705bonus pay for amazing work on #OSS 00010000863 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000864 +705bonus pay for amazing work on #OSS 00010000864 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000865 +705bonus pay for amazing work on #OSS 00010000865 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000866 +705bonus pay for amazing work on #OSS 00010000866 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000867 +705bonus pay for amazing work on #OSS 00010000867 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000868 +705bonus pay for amazing work on #OSS 00010000868 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000869 +705bonus pay for amazing work on #OSS 00010000869 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000870 +705bonus pay for amazing work on #OSS 00010000870 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000871 +705bonus pay for amazing work on #OSS 00010000871 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000872 +705bonus pay for amazing work on #OSS 00010000872 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000873 +705bonus pay for amazing work on #OSS 00010000873 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000874 +705bonus pay for amazing work on #OSS 00010000874 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000875 +705bonus pay for amazing work on #OSS 00010000875 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000876 +705bonus pay for amazing work on #OSS 00010000876 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000877 +705bonus pay for amazing work on #OSS 00010000877 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000878 +705bonus pay for amazing work on #OSS 00010000878 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000879 +705bonus pay for amazing work on #OSS 00010000879 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000880 +705bonus pay for amazing work on #OSS 00010000880 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000881 +705bonus pay for amazing work on #OSS 00010000881 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000882 +705bonus pay for amazing work on #OSS 00010000882 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000883 +705bonus pay for amazing work on #OSS 00010000883 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000884 +705bonus pay for amazing work on #OSS 00010000884 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000885 +705bonus pay for amazing work on #OSS 00010000885 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000886 +705bonus pay for amazing work on #OSS 00010000886 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000887 +705bonus pay for amazing work on #OSS 00010000887 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000888 +705bonus pay for amazing work on #OSS 00010000888 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000889 +705bonus pay for amazing work on #OSS 00010000889 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000890 +705bonus pay for amazing work on #OSS 00010000890 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000891 +705bonus pay for amazing work on #OSS 00010000891 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000892 +705bonus pay for amazing work on #OSS 00010000892 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000893 +705bonus pay for amazing work on #OSS 00010000893 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000894 +705bonus pay for amazing work on #OSS 00010000894 +62223138010481967038518 0000100000#DhI6DBwlsf5EO#Daniel Anderson 1121042880000895 +705bonus pay for amazing work on #OSS 00010000895 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000896 +705bonus pay for amazing work on #OSS 00010000896 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000897 +705bonus pay for amazing work on #OSS 00010000897 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000898 +705bonus pay for amazing work on #OSS 00010000898 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000899 +705bonus pay for amazing work on #OSS 00010000899 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000900 +705bonus pay for amazing work on #OSS 00010000900 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000901 +705bonus pay for amazing work on #OSS 00010000901 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000902 +705bonus pay for amazing work on #OSS 00010000902 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000903 +705bonus pay for amazing work on #OSS 00010000903 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000904 +705bonus pay for amazing work on #OSS 00010000904 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000905 +705bonus pay for amazing work on #OSS 00010000905 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000906 +705bonus pay for amazing work on #OSS 00010000906 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000907 +705bonus pay for amazing work on #OSS 00010000907 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000908 +705bonus pay for amazing work on #OSS 00010000908 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000909 +705bonus pay for amazing work on #OSS 00010000909 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000910 +705bonus pay for amazing work on #OSS 00010000910 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000911 +705bonus pay for amazing work on #OSS 00010000911 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000912 +705bonus pay for amazing work on #OSS 00010000912 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000913 +705bonus pay for amazing work on #OSS 00010000913 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000914 +705bonus pay for amazing work on #OSS 00010000914 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000915 +705bonus pay for amazing work on #OSS 00010000915 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000916 +705bonus pay for amazing work on #OSS 00010000916 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000917 +705bonus pay for amazing work on #OSS 00010000917 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000918 +705bonus pay for amazing work on #OSS 00010000918 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000919 +705bonus pay for amazing work on #OSS 00010000919 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000920 +705bonus pay for amazing work on #OSS 00010000920 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000921 +705bonus pay for amazing work on #OSS 00010000921 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000922 +705bonus pay for amazing work on #OSS 00010000922 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000923 +705bonus pay for amazing work on #OSS 00010000923 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000924 +705bonus pay for amazing work on #OSS 00010000924 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000925 +705bonus pay for amazing work on #OSS 00010000925 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000926 +705bonus pay for amazing work on #OSS 00010000926 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000927 +705bonus pay for amazing work on #OSS 00010000927 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000928 +705bonus pay for amazing work on #OSS 00010000928 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000929 +705bonus pay for amazing work on #OSS 00010000929 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000930 +705bonus pay for amazing work on #OSS 00010000930 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000931 +705bonus pay for amazing work on #OSS 00010000931 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000932 +705bonus pay for amazing work on #OSS 00010000932 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000933 +705bonus pay for amazing work on #OSS 00010000933 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000934 +705bonus pay for amazing work on #OSS 00010000934 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000935 +705bonus pay for amazing work on #OSS 00010000935 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000936 +705bonus pay for amazing work on #OSS 00010000936 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000937 +705bonus pay for amazing work on #OSS 00010000937 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000938 +705bonus pay for amazing work on #OSS 00010000938 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000939 +705bonus pay for amazing work on #OSS 00010000939 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000940 +705bonus pay for amazing work on #OSS 00010000940 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000941 +705bonus pay for amazing work on #OSS 00010000941 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000942 +705bonus pay for amazing work on #OSS 00010000942 +62223138010481967038518 0000100000#xBDzlUlZ3R0jS#Sofia Garcia 1121042880000943 +705bonus pay for amazing work on #OSS 00010000943 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000944 +705bonus pay for amazing work on #OSS 00010000944 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000945 +705bonus pay for amazing work on #OSS 00010000945 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000946 +705bonus pay for amazing work on #OSS 00010000946 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000947 +705bonus pay for amazing work on #OSS 00010000947 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000948 +705bonus pay for amazing work on #OSS 00010000948 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000949 +705bonus pay for amazing work on #OSS 00010000949 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000950 +705bonus pay for amazing work on #OSS 00010000950 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000951 +705bonus pay for amazing work on #OSS 00010000951 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000952 +705bonus pay for amazing work on #OSS 00010000952 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000953 +705bonus pay for amazing work on #OSS 00010000953 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000954 +705bonus pay for amazing work on #OSS 00010000954 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000955 +705bonus pay for amazing work on #OSS 00010000955 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000956 +705bonus pay for amazing work on #OSS 00010000956 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000957 +705bonus pay for amazing work on #OSS 00010000957 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000958 +705bonus pay for amazing work on #OSS 00010000958 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000959 +705bonus pay for amazing work on #OSS 00010000959 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000960 +705bonus pay for amazing work on #OSS 00010000960 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000961 +705bonus pay for amazing work on #OSS 00010000961 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000962 +705bonus pay for amazing work on #OSS 00010000962 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000963 +705bonus pay for amazing work on #OSS 00010000963 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000964 +705bonus pay for amazing work on #OSS 00010000964 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000965 +705bonus pay for amazing work on #OSS 00010000965 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000966 +705bonus pay for amazing work on #OSS 00010000966 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000967 +705bonus pay for amazing work on #OSS 00010000967 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000968 +705bonus pay for amazing work on #OSS 00010000968 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000969 +705bonus pay for amazing work on #OSS 00010000969 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000970 +705bonus pay for amazing work on #OSS 00010000970 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000971 +705bonus pay for amazing work on #OSS 00010000971 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000972 +705bonus pay for amazing work on #OSS 00010000972 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000973 +705bonus pay for amazing work on #OSS 00010000973 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000974 +705bonus pay for amazing work on #OSS 00010000974 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000975 +705bonus pay for amazing work on #OSS 00010000975 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000976 +705bonus pay for amazing work on #OSS 00010000976 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000977 +705bonus pay for amazing work on #OSS 00010000977 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000978 +705bonus pay for amazing work on #OSS 00010000978 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000979 +705bonus pay for amazing work on #OSS 00010000979 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000980 +705bonus pay for amazing work on #OSS 00010000980 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000981 +705bonus pay for amazing work on #OSS 00010000981 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000982 +705bonus pay for amazing work on #OSS 00010000982 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000983 +705bonus pay for amazing work on #OSS 00010000983 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000984 +705bonus pay for amazing work on #OSS 00010000984 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000985 +705bonus pay for amazing work on #OSS 00010000985 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000986 +705bonus pay for amazing work on #OSS 00010000986 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000987 +705bonus pay for amazing work on #OSS 00010000987 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000988 +705bonus pay for amazing work on #OSS 00010000988 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000989 +705bonus pay for amazing work on #OSS 00010000989 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000990 +705bonus pay for amazing work on #OSS 00010000990 +62223138010481967038518 0000100000#ALvpOrtgTeGXn#William Brown 1121042880000991 +705bonus pay for amazing work on #OSS 00010000991 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880000992 +705bonus pay for amazing work on #OSS 00010000992 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880000993 +705bonus pay for amazing work on #OSS 00010000993 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880000994 +705bonus pay for amazing work on #OSS 00010000994 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880000995 +705bonus pay for amazing work on #OSS 00010000995 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880000996 +705bonus pay for amazing work on #OSS 00010000996 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880000997 +705bonus pay for amazing work on #OSS 00010000997 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880000998 +705bonus pay for amazing work on #OSS 00010000998 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880000999 +705bonus pay for amazing work on #OSS 00010000999 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001000 +705bonus pay for amazing work on #OSS 00010001000 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001001 +705bonus pay for amazing work on #OSS 00010001001 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001002 +705bonus pay for amazing work on #OSS 00010001002 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001003 +705bonus pay for amazing work on #OSS 00010001003 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001004 +705bonus pay for amazing work on #OSS 00010001004 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001005 +705bonus pay for amazing work on #OSS 00010001005 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001006 +705bonus pay for amazing work on #OSS 00010001006 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001007 +705bonus pay for amazing work on #OSS 00010001007 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001008 +705bonus pay for amazing work on #OSS 00010001008 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001009 +705bonus pay for amazing work on #OSS 00010001009 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001010 +705bonus pay for amazing work on #OSS 00010001010 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001011 +705bonus pay for amazing work on #OSS 00010001011 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001012 +705bonus pay for amazing work on #OSS 00010001012 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001013 +705bonus pay for amazing work on #OSS 00010001013 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001014 +705bonus pay for amazing work on #OSS 00010001014 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001015 +705bonus pay for amazing work on #OSS 00010001015 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001016 +705bonus pay for amazing work on #OSS 00010001016 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001017 +705bonus pay for amazing work on #OSS 00010001017 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001018 +705bonus pay for amazing work on #OSS 00010001018 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001019 +705bonus pay for amazing work on #OSS 00010001019 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001020 +705bonus pay for amazing work on #OSS 00010001020 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001021 +705bonus pay for amazing work on #OSS 00010001021 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001022 +705bonus pay for amazing work on #OSS 00010001022 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001023 +705bonus pay for amazing work on #OSS 00010001023 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001024 +705bonus pay for amazing work on #OSS 00010001024 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001025 +705bonus pay for amazing work on #OSS 00010001025 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001026 +705bonus pay for amazing work on #OSS 00010001026 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001027 +705bonus pay for amazing work on #OSS 00010001027 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001028 +705bonus pay for amazing work on #OSS 00010001028 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001029 +705bonus pay for amazing work on #OSS 00010001029 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001030 +705bonus pay for amazing work on #OSS 00010001030 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001031 +705bonus pay for amazing work on #OSS 00010001031 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001032 +705bonus pay for amazing work on #OSS 00010001032 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001033 +705bonus pay for amazing work on #OSS 00010001033 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001034 +705bonus pay for amazing work on #OSS 00010001034 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001035 +705bonus pay for amazing work on #OSS 00010001035 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001036 +705bonus pay for amazing work on #OSS 00010001036 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001037 +705bonus pay for amazing work on #OSS 00010001037 +62223138010481967038518 0000100000#a2E5OeM3niFh0#Emma Johnson 1121042880001038 +705bonus pay for amazing work on #OSS 00010001038 +62223138010481967038518 0000100000#n3JeXvOmMVOia#Charlotte Martinez 1121042880001039 +705bonus pay for amazing work on #OSS 00010001039 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001040 +705bonus pay for amazing work on #OSS 00010001040 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001041 +705bonus pay for amazing work on #OSS 00010001041 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001042 +705bonus pay for amazing work on #OSS 00010001042 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001043 +705bonus pay for amazing work on #OSS 00010001043 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001044 +705bonus pay for amazing work on #OSS 00010001044 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001045 +705bonus pay for amazing work on #OSS 00010001045 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001046 +705bonus pay for amazing work on #OSS 00010001046 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001047 +705bonus pay for amazing work on #OSS 00010001047 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001048 +705bonus pay for amazing work on #OSS 00010001048 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001049 +705bonus pay for amazing work on #OSS 00010001049 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001050 +705bonus pay for amazing work on #OSS 00010001050 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001051 +705bonus pay for amazing work on #OSS 00010001051 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001052 +705bonus pay for amazing work on #OSS 00010001052 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001053 +705bonus pay for amazing work on #OSS 00010001053 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001054 +705bonus pay for amazing work on #OSS 00010001054 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001055 +705bonus pay for amazing work on #OSS 00010001055 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001056 +705bonus pay for amazing work on #OSS 00010001056 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001057 +705bonus pay for amazing work on #OSS 00010001057 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001058 +705bonus pay for amazing work on #OSS 00010001058 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001059 +705bonus pay for amazing work on #OSS 00010001059 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001060 +705bonus pay for amazing work on #OSS 00010001060 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001061 +705bonus pay for amazing work on #OSS 00010001061 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001062 +705bonus pay for amazing work on #OSS 00010001062 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001063 +705bonus pay for amazing work on #OSS 00010001063 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001064 +705bonus pay for amazing work on #OSS 00010001064 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001065 +705bonus pay for amazing work on #OSS 00010001065 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001066 +705bonus pay for amazing work on #OSS 00010001066 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001067 +705bonus pay for amazing work on #OSS 00010001067 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001068 +705bonus pay for amazing work on #OSS 00010001068 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001069 +705bonus pay for amazing work on #OSS 00010001069 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001070 +705bonus pay for amazing work on #OSS 00010001070 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001071 +705bonus pay for amazing work on #OSS 00010001071 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001072 +705bonus pay for amazing work on #OSS 00010001072 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001073 +705bonus pay for amazing work on #OSS 00010001073 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001074 +705bonus pay for amazing work on #OSS 00010001074 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001075 +705bonus pay for amazing work on #OSS 00010001075 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001076 +705bonus pay for amazing work on #OSS 00010001076 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001077 +705bonus pay for amazing work on #OSS 00010001077 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001078 +705bonus pay for amazing work on #OSS 00010001078 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001079 +705bonus pay for amazing work on #OSS 00010001079 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001080 +705bonus pay for amazing work on #OSS 00010001080 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001081 +705bonus pay for amazing work on #OSS 00010001081 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001082 +705bonus pay for amazing work on #OSS 00010001082 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001083 +705bonus pay for amazing work on #OSS 00010001083 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001084 +705bonus pay for amazing work on #OSS 00010001084 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001085 +705bonus pay for amazing work on #OSS 00010001085 +62223138010481967038518 0000100000#n3JeXvOmMVOia#David Martinez 1121042880001086 +705bonus pay for amazing work on #OSS 00010001086 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001087 +705bonus pay for amazing work on #OSS 00010001087 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001088 +705bonus pay for amazing work on #OSS 00010001088 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001089 +705bonus pay for amazing work on #OSS 00010001089 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001090 +705bonus pay for amazing work on #OSS 00010001090 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001091 +705bonus pay for amazing work on #OSS 00010001091 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001092 +705bonus pay for amazing work on #OSS 00010001092 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001093 +705bonus pay for amazing work on #OSS 00010001093 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001094 +705bonus pay for amazing work on #OSS 00010001094 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001095 +705bonus pay for amazing work on #OSS 00010001095 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001096 +705bonus pay for amazing work on #OSS 00010001096 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001097 +705bonus pay for amazing work on #OSS 00010001097 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001098 +705bonus pay for amazing work on #OSS 00010001098 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001099 +705bonus pay for amazing work on #OSS 00010001099 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001100 +705bonus pay for amazing work on #OSS 00010001100 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001101 +705bonus pay for amazing work on #OSS 00010001101 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001102 +705bonus pay for amazing work on #OSS 00010001102 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001103 +705bonus pay for amazing work on #OSS 00010001103 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001104 +705bonus pay for amazing work on #OSS 00010001104 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001105 +705bonus pay for amazing work on #OSS 00010001105 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001106 +705bonus pay for amazing work on #OSS 00010001106 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001107 +705bonus pay for amazing work on #OSS 00010001107 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001108 +705bonus pay for amazing work on #OSS 00010001108 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001109 +705bonus pay for amazing work on #OSS 00010001109 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001110 +705bonus pay for amazing work on #OSS 00010001110 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001111 +705bonus pay for amazing work on #OSS 00010001111 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001112 +705bonus pay for amazing work on #OSS 00010001112 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001113 +705bonus pay for amazing work on #OSS 00010001113 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001114 +705bonus pay for amazing work on #OSS 00010001114 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001115 +705bonus pay for amazing work on #OSS 00010001115 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001116 +705bonus pay for amazing work on #OSS 00010001116 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001117 +705bonus pay for amazing work on #OSS 00010001117 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001118 +705bonus pay for amazing work on #OSS 00010001118 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001119 +705bonus pay for amazing work on #OSS 00010001119 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001120 +705bonus pay for amazing work on #OSS 00010001120 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001121 +705bonus pay for amazing work on #OSS 00010001121 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001122 +705bonus pay for amazing work on #OSS 00010001122 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001123 +705bonus pay for amazing work on #OSS 00010001123 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001124 +705bonus pay for amazing work on #OSS 00010001124 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001125 +705bonus pay for amazing work on #OSS 00010001125 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001126 +705bonus pay for amazing work on #OSS 00010001126 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001127 +705bonus pay for amazing work on #OSS 00010001127 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001128 +705bonus pay for amazing work on #OSS 00010001128 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001129 +705bonus pay for amazing work on #OSS 00010001129 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001130 +705bonus pay for amazing work on #OSS 00010001130 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001131 +705bonus pay for amazing work on #OSS 00010001131 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001132 +705bonus pay for amazing work on #OSS 00010001132 +62223138010481967038518 0000100000#wVroM4XbzKmLE#William Brown 1121042880001133 +705bonus pay for amazing work on #OSS 00010001133 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#William Thomas 1121042880001134 +705bonus pay for amazing work on #OSS 00010001134 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001135 +705bonus pay for amazing work on #OSS 00010001135 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001136 +705bonus pay for amazing work on #OSS 00010001136 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001137 +705bonus pay for amazing work on #OSS 00010001137 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001138 +705bonus pay for amazing work on #OSS 00010001138 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001139 +705bonus pay for amazing work on #OSS 00010001139 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001140 +705bonus pay for amazing work on #OSS 00010001140 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001141 +705bonus pay for amazing work on #OSS 00010001141 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001142 +705bonus pay for amazing work on #OSS 00010001142 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001143 +705bonus pay for amazing work on #OSS 00010001143 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001144 +705bonus pay for amazing work on #OSS 00010001144 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001145 +705bonus pay for amazing work on #OSS 00010001145 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001146 +705bonus pay for amazing work on #OSS 00010001146 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001147 +705bonus pay for amazing work on #OSS 00010001147 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001148 +705bonus pay for amazing work on #OSS 00010001148 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001149 +705bonus pay for amazing work on #OSS 00010001149 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001150 +705bonus pay for amazing work on #OSS 00010001150 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001151 +705bonus pay for amazing work on #OSS 00010001151 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001152 +705bonus pay for amazing work on #OSS 00010001152 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001153 +705bonus pay for amazing work on #OSS 00010001153 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001154 +705bonus pay for amazing work on #OSS 00010001154 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001155 +705bonus pay for amazing work on #OSS 00010001155 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001156 +705bonus pay for amazing work on #OSS 00010001156 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001157 +705bonus pay for amazing work on #OSS 00010001157 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001158 +705bonus pay for amazing work on #OSS 00010001158 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001159 +705bonus pay for amazing work on #OSS 00010001159 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001160 +705bonus pay for amazing work on #OSS 00010001160 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001161 +705bonus pay for amazing work on #OSS 00010001161 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001162 +705bonus pay for amazing work on #OSS 00010001162 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001163 +705bonus pay for amazing work on #OSS 00010001163 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001164 +705bonus pay for amazing work on #OSS 00010001164 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001165 +705bonus pay for amazing work on #OSS 00010001165 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001166 +705bonus pay for amazing work on #OSS 00010001166 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001167 +705bonus pay for amazing work on #OSS 00010001167 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001168 +705bonus pay for amazing work on #OSS 00010001168 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001169 +705bonus pay for amazing work on #OSS 00010001169 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001170 +705bonus pay for amazing work on #OSS 00010001170 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001171 +705bonus pay for amazing work on #OSS 00010001171 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001172 +705bonus pay for amazing work on #OSS 00010001172 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001173 +705bonus pay for amazing work on #OSS 00010001173 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001174 +705bonus pay for amazing work on #OSS 00010001174 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001175 +705bonus pay for amazing work on #OSS 00010001175 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001176 +705bonus pay for amazing work on #OSS 00010001176 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001177 +705bonus pay for amazing work on #OSS 00010001177 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001178 +705bonus pay for amazing work on #OSS 00010001178 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001179 +705bonus pay for amazing work on #OSS 00010001179 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001180 +705bonus pay for amazing work on #OSS 00010001180 +62223138010481967038518 0000100000#MgSwIoaqd9HFn#Ella Thomas 1121042880001181 +705bonus pay for amazing work on #OSS 00010001181 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001182 +705bonus pay for amazing work on #OSS 00010001182 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001183 +705bonus pay for amazing work on #OSS 00010001183 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001184 +705bonus pay for amazing work on #OSS 00010001184 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001185 +705bonus pay for amazing work on #OSS 00010001185 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001186 +705bonus pay for amazing work on #OSS 00010001186 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001187 +705bonus pay for amazing work on #OSS 00010001187 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001188 +705bonus pay for amazing work on #OSS 00010001188 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001189 +705bonus pay for amazing work on #OSS 00010001189 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001190 +705bonus pay for amazing work on #OSS 00010001190 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001191 +705bonus pay for amazing work on #OSS 00010001191 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001192 +705bonus pay for amazing work on #OSS 00010001192 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001193 +705bonus pay for amazing work on #OSS 00010001193 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001194 +705bonus pay for amazing work on #OSS 00010001194 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001195 +705bonus pay for amazing work on #OSS 00010001195 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001196 +705bonus pay for amazing work on #OSS 00010001196 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001197 +705bonus pay for amazing work on #OSS 00010001197 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001198 +705bonus pay for amazing work on #OSS 00010001198 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001199 +705bonus pay for amazing work on #OSS 00010001199 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001200 +705bonus pay for amazing work on #OSS 00010001200 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001201 +705bonus pay for amazing work on #OSS 00010001201 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001202 +705bonus pay for amazing work on #OSS 00010001202 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001203 +705bonus pay for amazing work on #OSS 00010001203 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001204 +705bonus pay for amazing work on #OSS 00010001204 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001205 +705bonus pay for amazing work on #OSS 00010001205 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001206 +705bonus pay for amazing work on #OSS 00010001206 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001207 +705bonus pay for amazing work on #OSS 00010001207 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001208 +705bonus pay for amazing work on #OSS 00010001208 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001209 +705bonus pay for amazing work on #OSS 00010001209 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001210 +705bonus pay for amazing work on #OSS 00010001210 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001211 +705bonus pay for amazing work on #OSS 00010001211 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001212 +705bonus pay for amazing work on #OSS 00010001212 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001213 +705bonus pay for amazing work on #OSS 00010001213 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001214 +705bonus pay for amazing work on #OSS 00010001214 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001215 +705bonus pay for amazing work on #OSS 00010001215 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001216 +705bonus pay for amazing work on #OSS 00010001216 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001217 +705bonus pay for amazing work on #OSS 00010001217 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001218 +705bonus pay for amazing work on #OSS 00010001218 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001219 +705bonus pay for amazing work on #OSS 00010001219 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001220 +705bonus pay for amazing work on #OSS 00010001220 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001221 +705bonus pay for amazing work on #OSS 00010001221 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001222 +705bonus pay for amazing work on #OSS 00010001222 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001223 +705bonus pay for amazing work on #OSS 00010001223 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001224 +705bonus pay for amazing work on #OSS 00010001224 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001225 +705bonus pay for amazing work on #OSS 00010001225 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001226 +705bonus pay for amazing work on #OSS 00010001226 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001227 +705bonus pay for amazing work on #OSS 00010001227 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001228 +705bonus pay for amazing work on #OSS 00010001228 +62223138010481967038518 0000100000#0HIKgOVOQQKfU#Sofia Garcia 1121042880001229 +705bonus pay for amazing work on #OSS 00010001229 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001230 +705bonus pay for amazing work on #OSS 00010001230 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001231 +705bonus pay for amazing work on #OSS 00010001231 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001232 +705bonus pay for amazing work on #OSS 00010001232 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001233 +705bonus pay for amazing work on #OSS 00010001233 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001234 +705bonus pay for amazing work on #OSS 00010001234 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001235 +705bonus pay for amazing work on #OSS 00010001235 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001236 +705bonus pay for amazing work on #OSS 00010001236 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001237 +705bonus pay for amazing work on #OSS 00010001237 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001238 +705bonus pay for amazing work on #OSS 00010001238 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001239 +705bonus pay for amazing work on #OSS 00010001239 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001240 +705bonus pay for amazing work on #OSS 00010001240 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001241 +705bonus pay for amazing work on #OSS 00010001241 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001242 +705bonus pay for amazing work on #OSS 00010001242 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001243 +705bonus pay for amazing work on #OSS 00010001243 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001244 +705bonus pay for amazing work on #OSS 00010001244 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001245 +705bonus pay for amazing work on #OSS 00010001245 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001246 +705bonus pay for amazing work on #OSS 00010001246 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001247 +705bonus pay for amazing work on #OSS 00010001247 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001248 +705bonus pay for amazing work on #OSS 00010001248 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001249 +705bonus pay for amazing work on #OSS 00010001249 +62223138010481967038518 0000100000#i78CAmFG0zV4o#Anthony Harris 1121042880001250 +705bonus pay for amazing work on #OSS 00010001250 +82000025008922512500000000000000000125000000121042882 121042880000002 +5200Wells Fargo 121042882 PPDTrans. Des 180511 0121042880000003 +62223138010481967038518 0000100000#KSjXqrPKbL3QN#Daniel Anderson 1121042880000001 +705bonus pay for amazing work on #OSS 00010000001 +62223138010481967038518 0000100000#KSjXqrPKbL3QN#Daniel Anderson 1121042880000002 +705bonus pay for amazing work on #OSS 00010000002 +62223138010481967038518 0000100000#KSjXqrPKbL3QN#Daniel Anderson 1121042880000003 +705bonus pay for amazing work on #OSS 00010000003 +62223138010481967038518 0000100000#KSjXqrPKbL3QN#Daniel Anderson 1121042880000004 +705bonus pay for amazing work on #OSS 00010000004 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Daniel Jones 1121042880000005 +705bonus pay for amazing work on #OSS 00010000005 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000006 +705bonus pay for amazing work on #OSS 00010000006 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000007 +705bonus pay for amazing work on #OSS 00010000007 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000008 +705bonus pay for amazing work on #OSS 00010000008 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000009 +705bonus pay for amazing work on #OSS 00010000009 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000010 +705bonus pay for amazing work on #OSS 00010000010 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000011 +705bonus pay for amazing work on #OSS 00010000011 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000012 +705bonus pay for amazing work on #OSS 00010000012 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000013 +705bonus pay for amazing work on #OSS 00010000013 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000014 +705bonus pay for amazing work on #OSS 00010000014 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000015 +705bonus pay for amazing work on #OSS 00010000015 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000016 +705bonus pay for amazing work on #OSS 00010000016 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000017 +705bonus pay for amazing work on #OSS 00010000017 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000018 +705bonus pay for amazing work on #OSS 00010000018 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000019 +705bonus pay for amazing work on #OSS 00010000019 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000020 +705bonus pay for amazing work on #OSS 00010000020 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000021 +705bonus pay for amazing work on #OSS 00010000021 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000022 +705bonus pay for amazing work on #OSS 00010000022 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000023 +705bonus pay for amazing work on #OSS 00010000023 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000024 +705bonus pay for amazing work on #OSS 00010000024 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000025 +705bonus pay for amazing work on #OSS 00010000025 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000026 +705bonus pay for amazing work on #OSS 00010000026 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000027 +705bonus pay for amazing work on #OSS 00010000027 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000028 +705bonus pay for amazing work on #OSS 00010000028 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000029 +705bonus pay for amazing work on #OSS 00010000029 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000030 +705bonus pay for amazing work on #OSS 00010000030 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000031 +705bonus pay for amazing work on #OSS 00010000031 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000032 +705bonus pay for amazing work on #OSS 00010000032 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000033 +705bonus pay for amazing work on #OSS 00010000033 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000034 +705bonus pay for amazing work on #OSS 00010000034 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000035 +705bonus pay for amazing work on #OSS 00010000035 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000036 +705bonus pay for amazing work on #OSS 00010000036 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000037 +705bonus pay for amazing work on #OSS 00010000037 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000038 +705bonus pay for amazing work on #OSS 00010000038 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000039 +705bonus pay for amazing work on #OSS 00010000039 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000040 +705bonus pay for amazing work on #OSS 00010000040 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000041 +705bonus pay for amazing work on #OSS 00010000041 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000042 +705bonus pay for amazing work on #OSS 00010000042 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000043 +705bonus pay for amazing work on #OSS 00010000043 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000044 +705bonus pay for amazing work on #OSS 00010000044 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000045 +705bonus pay for amazing work on #OSS 00010000045 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000046 +705bonus pay for amazing work on #OSS 00010000046 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000047 +705bonus pay for amazing work on #OSS 00010000047 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000048 +705bonus pay for amazing work on #OSS 00010000048 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000049 +705bonus pay for amazing work on #OSS 00010000049 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000050 +705bonus pay for amazing work on #OSS 00010000050 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000051 +705bonus pay for amazing work on #OSS 00010000051 +62223138010481967038518 0000100000#zGKpc8VDEhX0N#Olivia Jones 1121042880000052 +705bonus pay for amazing work on #OSS 00010000052 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000053 +705bonus pay for amazing work on #OSS 00010000053 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000054 +705bonus pay for amazing work on #OSS 00010000054 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000055 +705bonus pay for amazing work on #OSS 00010000055 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000056 +705bonus pay for amazing work on #OSS 00010000056 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000057 +705bonus pay for amazing work on #OSS 00010000057 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000058 +705bonus pay for amazing work on #OSS 00010000058 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000059 +705bonus pay for amazing work on #OSS 00010000059 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000060 +705bonus pay for amazing work on #OSS 00010000060 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000061 +705bonus pay for amazing work on #OSS 00010000061 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000062 +705bonus pay for amazing work on #OSS 00010000062 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000063 +705bonus pay for amazing work on #OSS 00010000063 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000064 +705bonus pay for amazing work on #OSS 00010000064 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000065 +705bonus pay for amazing work on #OSS 00010000065 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000066 +705bonus pay for amazing work on #OSS 00010000066 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000067 +705bonus pay for amazing work on #OSS 00010000067 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000068 +705bonus pay for amazing work on #OSS 00010000068 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000069 +705bonus pay for amazing work on #OSS 00010000069 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000070 +705bonus pay for amazing work on #OSS 00010000070 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000071 +705bonus pay for amazing work on #OSS 00010000071 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000072 +705bonus pay for amazing work on #OSS 00010000072 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000073 +705bonus pay for amazing work on #OSS 00010000073 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000074 +705bonus pay for amazing work on #OSS 00010000074 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000075 +705bonus pay for amazing work on #OSS 00010000075 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000076 +705bonus pay for amazing work on #OSS 00010000076 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000077 +705bonus pay for amazing work on #OSS 00010000077 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000078 +705bonus pay for amazing work on #OSS 00010000078 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000079 +705bonus pay for amazing work on #OSS 00010000079 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000080 +705bonus pay for amazing work on #OSS 00010000080 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000081 +705bonus pay for amazing work on #OSS 00010000081 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000082 +705bonus pay for amazing work on #OSS 00010000082 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000083 +705bonus pay for amazing work on #OSS 00010000083 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000084 +705bonus pay for amazing work on #OSS 00010000084 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000085 +705bonus pay for amazing work on #OSS 00010000085 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000086 +705bonus pay for amazing work on #OSS 00010000086 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000087 +705bonus pay for amazing work on #OSS 00010000087 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000088 +705bonus pay for amazing work on #OSS 00010000088 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000089 +705bonus pay for amazing work on #OSS 00010000089 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000090 +705bonus pay for amazing work on #OSS 00010000090 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000091 +705bonus pay for amazing work on #OSS 00010000091 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000092 +705bonus pay for amazing work on #OSS 00010000092 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000093 +705bonus pay for amazing work on #OSS 00010000093 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000094 +705bonus pay for amazing work on #OSS 00010000094 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000095 +705bonus pay for amazing work on #OSS 00010000095 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000096 +705bonus pay for amazing work on #OSS 00010000096 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000097 +705bonus pay for amazing work on #OSS 00010000097 +62223138010481967038518 0000100000#Pcr57pdyMjUpk#William Brown 1121042880000098 +705bonus pay for amazing work on #OSS 00010000098 +62223138010481967038518 0000100000#x4APZn9K6uofd#Michael Wilson 1121042880000099 +705bonus pay for amazing work on #OSS 00010000099 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000100 +705bonus pay for amazing work on #OSS 00010000100 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000101 +705bonus pay for amazing work on #OSS 00010000101 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000102 +705bonus pay for amazing work on #OSS 00010000102 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000103 +705bonus pay for amazing work on #OSS 00010000103 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000104 +705bonus pay for amazing work on #OSS 00010000104 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000105 +705bonus pay for amazing work on #OSS 00010000105 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000106 +705bonus pay for amazing work on #OSS 00010000106 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000107 +705bonus pay for amazing work on #OSS 00010000107 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000108 +705bonus pay for amazing work on #OSS 00010000108 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000109 +705bonus pay for amazing work on #OSS 00010000109 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000110 +705bonus pay for amazing work on #OSS 00010000110 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000111 +705bonus pay for amazing work on #OSS 00010000111 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000112 +705bonus pay for amazing work on #OSS 00010000112 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000113 +705bonus pay for amazing work on #OSS 00010000113 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000114 +705bonus pay for amazing work on #OSS 00010000114 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000115 +705bonus pay for amazing work on #OSS 00010000115 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000116 +705bonus pay for amazing work on #OSS 00010000116 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000117 +705bonus pay for amazing work on #OSS 00010000117 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000118 +705bonus pay for amazing work on #OSS 00010000118 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000119 +705bonus pay for amazing work on #OSS 00010000119 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000120 +705bonus pay for amazing work on #OSS 00010000120 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000121 +705bonus pay for amazing work on #OSS 00010000121 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000122 +705bonus pay for amazing work on #OSS 00010000122 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000123 +705bonus pay for amazing work on #OSS 00010000123 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000124 +705bonus pay for amazing work on #OSS 00010000124 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000125 +705bonus pay for amazing work on #OSS 00010000125 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000126 +705bonus pay for amazing work on #OSS 00010000126 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000127 +705bonus pay for amazing work on #OSS 00010000127 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000128 +705bonus pay for amazing work on #OSS 00010000128 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000129 +705bonus pay for amazing work on #OSS 00010000129 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000130 +705bonus pay for amazing work on #OSS 00010000130 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000131 +705bonus pay for amazing work on #OSS 00010000131 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000132 +705bonus pay for amazing work on #OSS 00010000132 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000133 +705bonus pay for amazing work on #OSS 00010000133 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000134 +705bonus pay for amazing work on #OSS 00010000134 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000135 +705bonus pay for amazing work on #OSS 00010000135 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000136 +705bonus pay for amazing work on #OSS 00010000136 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000137 +705bonus pay for amazing work on #OSS 00010000137 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000138 +705bonus pay for amazing work on #OSS 00010000138 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000139 +705bonus pay for amazing work on #OSS 00010000139 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000140 +705bonus pay for amazing work on #OSS 00010000140 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000141 +705bonus pay for amazing work on #OSS 00010000141 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000142 +705bonus pay for amazing work on #OSS 00010000142 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000143 +705bonus pay for amazing work on #OSS 00010000143 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000144 +705bonus pay for amazing work on #OSS 00010000144 +62223138010481967038518 0000100000#x4APZn9K6uofd#Mia Wilson 1121042880000145 +705bonus pay for amazing work on #OSS 00010000145 +62223138010481967038518 0000100000#qBORKg23OkXFB#Mia Miller 1121042880000146 +705bonus pay for amazing work on #OSS 00010000146 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000147 +705bonus pay for amazing work on #OSS 00010000147 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000148 +705bonus pay for amazing work on #OSS 00010000148 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000149 +705bonus pay for amazing work on #OSS 00010000149 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000150 +705bonus pay for amazing work on #OSS 00010000150 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000151 +705bonus pay for amazing work on #OSS 00010000151 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000152 +705bonus pay for amazing work on #OSS 00010000152 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000153 +705bonus pay for amazing work on #OSS 00010000153 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000154 +705bonus pay for amazing work on #OSS 00010000154 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000155 +705bonus pay for amazing work on #OSS 00010000155 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000156 +705bonus pay for amazing work on #OSS 00010000156 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000157 +705bonus pay for amazing work on #OSS 00010000157 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000158 +705bonus pay for amazing work on #OSS 00010000158 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000159 +705bonus pay for amazing work on #OSS 00010000159 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000160 +705bonus pay for amazing work on #OSS 00010000160 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000161 +705bonus pay for amazing work on #OSS 00010000161 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000162 +705bonus pay for amazing work on #OSS 00010000162 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000163 +705bonus pay for amazing work on #OSS 00010000163 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000164 +705bonus pay for amazing work on #OSS 00010000164 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000165 +705bonus pay for amazing work on #OSS 00010000165 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000166 +705bonus pay for amazing work on #OSS 00010000166 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000167 +705bonus pay for amazing work on #OSS 00010000167 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000168 +705bonus pay for amazing work on #OSS 00010000168 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000169 +705bonus pay for amazing work on #OSS 00010000169 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000170 +705bonus pay for amazing work on #OSS 00010000170 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000171 +705bonus pay for amazing work on #OSS 00010000171 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000172 +705bonus pay for amazing work on #OSS 00010000172 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000173 +705bonus pay for amazing work on #OSS 00010000173 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000174 +705bonus pay for amazing work on #OSS 00010000174 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000175 +705bonus pay for amazing work on #OSS 00010000175 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000176 +705bonus pay for amazing work on #OSS 00010000176 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000177 +705bonus pay for amazing work on #OSS 00010000177 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000178 +705bonus pay for amazing work on #OSS 00010000178 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000179 +705bonus pay for amazing work on #OSS 00010000179 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000180 +705bonus pay for amazing work on #OSS 00010000180 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000181 +705bonus pay for amazing work on #OSS 00010000181 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000182 +705bonus pay for amazing work on #OSS 00010000182 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000183 +705bonus pay for amazing work on #OSS 00010000183 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000184 +705bonus pay for amazing work on #OSS 00010000184 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000185 +705bonus pay for amazing work on #OSS 00010000185 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000186 +705bonus pay for amazing work on #OSS 00010000186 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000187 +705bonus pay for amazing work on #OSS 00010000187 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000188 +705bonus pay for amazing work on #OSS 00010000188 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000189 +705bonus pay for amazing work on #OSS 00010000189 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000190 +705bonus pay for amazing work on #OSS 00010000190 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000191 +705bonus pay for amazing work on #OSS 00010000191 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000192 +705bonus pay for amazing work on #OSS 00010000192 +62223138010481967038518 0000100000#qBORKg23OkXFB#Jayden Miller 1121042880000193 +705bonus pay for amazing work on #OSS 00010000193 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000194 +705bonus pay for amazing work on #OSS 00010000194 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000195 +705bonus pay for amazing work on #OSS 00010000195 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000196 +705bonus pay for amazing work on #OSS 00010000196 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000197 +705bonus pay for amazing work on #OSS 00010000197 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000198 +705bonus pay for amazing work on #OSS 00010000198 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000199 +705bonus pay for amazing work on #OSS 00010000199 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000200 +705bonus pay for amazing work on #OSS 00010000200 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000201 +705bonus pay for amazing work on #OSS 00010000201 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000202 +705bonus pay for amazing work on #OSS 00010000202 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000203 +705bonus pay for amazing work on #OSS 00010000203 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000204 +705bonus pay for amazing work on #OSS 00010000204 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000205 +705bonus pay for amazing work on #OSS 00010000205 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000206 +705bonus pay for amazing work on #OSS 00010000206 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000207 +705bonus pay for amazing work on #OSS 00010000207 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000208 +705bonus pay for amazing work on #OSS 00010000208 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000209 +705bonus pay for amazing work on #OSS 00010000209 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000210 +705bonus pay for amazing work on #OSS 00010000210 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000211 +705bonus pay for amazing work on #OSS 00010000211 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000212 +705bonus pay for amazing work on #OSS 00010000212 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000213 +705bonus pay for amazing work on #OSS 00010000213 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000214 +705bonus pay for amazing work on #OSS 00010000214 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000215 +705bonus pay for amazing work on #OSS 00010000215 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000216 +705bonus pay for amazing work on #OSS 00010000216 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000217 +705bonus pay for amazing work on #OSS 00010000217 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000218 +705bonus pay for amazing work on #OSS 00010000218 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000219 +705bonus pay for amazing work on #OSS 00010000219 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000220 +705bonus pay for amazing work on #OSS 00010000220 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000221 +705bonus pay for amazing work on #OSS 00010000221 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000222 +705bonus pay for amazing work on #OSS 00010000222 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000223 +705bonus pay for amazing work on #OSS 00010000223 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000224 +705bonus pay for amazing work on #OSS 00010000224 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000225 +705bonus pay for amazing work on #OSS 00010000225 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000226 +705bonus pay for amazing work on #OSS 00010000226 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000227 +705bonus pay for amazing work on #OSS 00010000227 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000228 +705bonus pay for amazing work on #OSS 00010000228 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000229 +705bonus pay for amazing work on #OSS 00010000229 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000230 +705bonus pay for amazing work on #OSS 00010000230 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000231 +705bonus pay for amazing work on #OSS 00010000231 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000232 +705bonus pay for amazing work on #OSS 00010000232 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000233 +705bonus pay for amazing work on #OSS 00010000233 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000234 +705bonus pay for amazing work on #OSS 00010000234 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000235 +705bonus pay for amazing work on #OSS 00010000235 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000236 +705bonus pay for amazing work on #OSS 00010000236 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000237 +705bonus pay for amazing work on #OSS 00010000237 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000238 +705bonus pay for amazing work on #OSS 00010000238 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000239 +705bonus pay for amazing work on #OSS 00010000239 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000240 +705bonus pay for amazing work on #OSS 00010000240 +62223138010481967038518 0000100000#H0tJGmF71ryM6#Elijah Jackson 1121042880000241 +705bonus pay for amazing work on #OSS 00010000241 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000242 +705bonus pay for amazing work on #OSS 00010000242 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000243 +705bonus pay for amazing work on #OSS 00010000243 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000244 +705bonus pay for amazing work on #OSS 00010000244 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000245 +705bonus pay for amazing work on #OSS 00010000245 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000246 +705bonus pay for amazing work on #OSS 00010000246 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000247 +705bonus pay for amazing work on #OSS 00010000247 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000248 +705bonus pay for amazing work on #OSS 00010000248 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000249 +705bonus pay for amazing work on #OSS 00010000249 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000250 +705bonus pay for amazing work on #OSS 00010000250 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000251 +705bonus pay for amazing work on #OSS 00010000251 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000252 +705bonus pay for amazing work on #OSS 00010000252 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000253 +705bonus pay for amazing work on #OSS 00010000253 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000254 +705bonus pay for amazing work on #OSS 00010000254 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000255 +705bonus pay for amazing work on #OSS 00010000255 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000256 +705bonus pay for amazing work on #OSS 00010000256 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000257 +705bonus pay for amazing work on #OSS 00010000257 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000258 +705bonus pay for amazing work on #OSS 00010000258 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000259 +705bonus pay for amazing work on #OSS 00010000259 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000260 +705bonus pay for amazing work on #OSS 00010000260 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000261 +705bonus pay for amazing work on #OSS 00010000261 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000262 +705bonus pay for amazing work on #OSS 00010000262 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000263 +705bonus pay for amazing work on #OSS 00010000263 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000264 +705bonus pay for amazing work on #OSS 00010000264 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000265 +705bonus pay for amazing work on #OSS 00010000265 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000266 +705bonus pay for amazing work on #OSS 00010000266 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000267 +705bonus pay for amazing work on #OSS 00010000267 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000268 +705bonus pay for amazing work on #OSS 00010000268 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000269 +705bonus pay for amazing work on #OSS 00010000269 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000270 +705bonus pay for amazing work on #OSS 00010000270 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000271 +705bonus pay for amazing work on #OSS 00010000271 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000272 +705bonus pay for amazing work on #OSS 00010000272 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000273 +705bonus pay for amazing work on #OSS 00010000273 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000274 +705bonus pay for amazing work on #OSS 00010000274 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000275 +705bonus pay for amazing work on #OSS 00010000275 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000276 +705bonus pay for amazing work on #OSS 00010000276 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000277 +705bonus pay for amazing work on #OSS 00010000277 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000278 +705bonus pay for amazing work on #OSS 00010000278 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000279 +705bonus pay for amazing work on #OSS 00010000279 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000280 +705bonus pay for amazing work on #OSS 00010000280 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000281 +705bonus pay for amazing work on #OSS 00010000281 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000282 +705bonus pay for amazing work on #OSS 00010000282 +62223138010481967038518 0000100000#4QaGLFW4kM4lV#Emily Davis 1121042880000283 +705bonus pay for amazing work on #OSS 00010000283 +62223138010481967038518 0000100000#3vH8OQEFduqif#Emily Thompson 1121042880000284 +705bonus pay for amazing work on #OSS 00010000284 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000285 +705bonus pay for amazing work on #OSS 00010000285 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000286 +705bonus pay for amazing work on #OSS 00010000286 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000287 +705bonus pay for amazing work on #OSS 00010000287 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000288 +705bonus pay for amazing work on #OSS 00010000288 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000289 +705bonus pay for amazing work on #OSS 00010000289 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000290 +705bonus pay for amazing work on #OSS 00010000290 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000291 +705bonus pay for amazing work on #OSS 00010000291 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000292 +705bonus pay for amazing work on #OSS 00010000292 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000293 +705bonus pay for amazing work on #OSS 00010000293 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000294 +705bonus pay for amazing work on #OSS 00010000294 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000295 +705bonus pay for amazing work on #OSS 00010000295 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000296 +705bonus pay for amazing work on #OSS 00010000296 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000297 +705bonus pay for amazing work on #OSS 00010000297 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000298 +705bonus pay for amazing work on #OSS 00010000298 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000299 +705bonus pay for amazing work on #OSS 00010000299 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000300 +705bonus pay for amazing work on #OSS 00010000300 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000301 +705bonus pay for amazing work on #OSS 00010000301 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000302 +705bonus pay for amazing work on #OSS 00010000302 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000303 +705bonus pay for amazing work on #OSS 00010000303 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000304 +705bonus pay for amazing work on #OSS 00010000304 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000305 +705bonus pay for amazing work on #OSS 00010000305 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000306 +705bonus pay for amazing work on #OSS 00010000306 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000307 +705bonus pay for amazing work on #OSS 00010000307 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000308 +705bonus pay for amazing work on #OSS 00010000308 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000309 +705bonus pay for amazing work on #OSS 00010000309 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000310 +705bonus pay for amazing work on #OSS 00010000310 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000311 +705bonus pay for amazing work on #OSS 00010000311 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000312 +705bonus pay for amazing work on #OSS 00010000312 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000313 +705bonus pay for amazing work on #OSS 00010000313 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000314 +705bonus pay for amazing work on #OSS 00010000314 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000315 +705bonus pay for amazing work on #OSS 00010000315 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000316 +705bonus pay for amazing work on #OSS 00010000316 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000317 +705bonus pay for amazing work on #OSS 00010000317 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000318 +705bonus pay for amazing work on #OSS 00010000318 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000319 +705bonus pay for amazing work on #OSS 00010000319 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000320 +705bonus pay for amazing work on #OSS 00010000320 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000321 +705bonus pay for amazing work on #OSS 00010000321 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000322 +705bonus pay for amazing work on #OSS 00010000322 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000323 +705bonus pay for amazing work on #OSS 00010000323 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000324 +705bonus pay for amazing work on #OSS 00010000324 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000325 +705bonus pay for amazing work on #OSS 00010000325 +62223138010481967038518 0000100000#3vH8OQEFduqif#Joshua Thompson 1121042880000326 +705bonus pay for amazing work on #OSS 00010000326 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000327 +705bonus pay for amazing work on #OSS 00010000327 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000328 +705bonus pay for amazing work on #OSS 00010000328 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000329 +705bonus pay for amazing work on #OSS 00010000329 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000330 +705bonus pay for amazing work on #OSS 00010000330 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000331 +705bonus pay for amazing work on #OSS 00010000331 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000332 +705bonus pay for amazing work on #OSS 00010000332 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000333 +705bonus pay for amazing work on #OSS 00010000333 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000334 +705bonus pay for amazing work on #OSS 00010000334 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000335 +705bonus pay for amazing work on #OSS 00010000335 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000336 +705bonus pay for amazing work on #OSS 00010000336 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000337 +705bonus pay for amazing work on #OSS 00010000337 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000338 +705bonus pay for amazing work on #OSS 00010000338 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000339 +705bonus pay for amazing work on #OSS 00010000339 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000340 +705bonus pay for amazing work on #OSS 00010000340 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000341 +705bonus pay for amazing work on #OSS 00010000341 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000342 +705bonus pay for amazing work on #OSS 00010000342 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000343 +705bonus pay for amazing work on #OSS 00010000343 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000344 +705bonus pay for amazing work on #OSS 00010000344 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000345 +705bonus pay for amazing work on #OSS 00010000345 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000346 +705bonus pay for amazing work on #OSS 00010000346 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000347 +705bonus pay for amazing work on #OSS 00010000347 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000348 +705bonus pay for amazing work on #OSS 00010000348 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000349 +705bonus pay for amazing work on #OSS 00010000349 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000350 +705bonus pay for amazing work on #OSS 00010000350 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000351 +705bonus pay for amazing work on #OSS 00010000351 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000352 +705bonus pay for amazing work on #OSS 00010000352 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000353 +705bonus pay for amazing work on #OSS 00010000353 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000354 +705bonus pay for amazing work on #OSS 00010000354 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000355 +705bonus pay for amazing work on #OSS 00010000355 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000356 +705bonus pay for amazing work on #OSS 00010000356 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000357 +705bonus pay for amazing work on #OSS 00010000357 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000358 +705bonus pay for amazing work on #OSS 00010000358 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000359 +705bonus pay for amazing work on #OSS 00010000359 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000360 +705bonus pay for amazing work on #OSS 00010000360 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000361 +705bonus pay for amazing work on #OSS 00010000361 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000362 +705bonus pay for amazing work on #OSS 00010000362 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000363 +705bonus pay for amazing work on #OSS 00010000363 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000364 +705bonus pay for amazing work on #OSS 00010000364 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000365 +705bonus pay for amazing work on #OSS 00010000365 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000366 +705bonus pay for amazing work on #OSS 00010000366 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000367 +705bonus pay for amazing work on #OSS 00010000367 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000368 +705bonus pay for amazing work on #OSS 00010000368 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000369 +705bonus pay for amazing work on #OSS 00010000369 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000370 +705bonus pay for amazing work on #OSS 00010000370 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000371 +705bonus pay for amazing work on #OSS 00010000371 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000372 +705bonus pay for amazing work on #OSS 00010000372 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000373 +705bonus pay for amazing work on #OSS 00010000373 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000374 +705bonus pay for amazing work on #OSS 00010000374 +62223138010481967038518 0000100000#jyJybX88Qpndo#Sofia Garcia 1121042880000375 +705bonus pay for amazing work on #OSS 00010000375 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000376 +705bonus pay for amazing work on #OSS 00010000376 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000377 +705bonus pay for amazing work on #OSS 00010000377 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000378 +705bonus pay for amazing work on #OSS 00010000378 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000379 +705bonus pay for amazing work on #OSS 00010000379 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000380 +705bonus pay for amazing work on #OSS 00010000380 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000381 +705bonus pay for amazing work on #OSS 00010000381 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000382 +705bonus pay for amazing work on #OSS 00010000382 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000383 +705bonus pay for amazing work on #OSS 00010000383 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000384 +705bonus pay for amazing work on #OSS 00010000384 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000385 +705bonus pay for amazing work on #OSS 00010000385 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000386 +705bonus pay for amazing work on #OSS 00010000386 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000387 +705bonus pay for amazing work on #OSS 00010000387 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000388 +705bonus pay for amazing work on #OSS 00010000388 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000389 +705bonus pay for amazing work on #OSS 00010000389 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000390 +705bonus pay for amazing work on #OSS 00010000390 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000391 +705bonus pay for amazing work on #OSS 00010000391 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000392 +705bonus pay for amazing work on #OSS 00010000392 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000393 +705bonus pay for amazing work on #OSS 00010000393 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000394 +705bonus pay for amazing work on #OSS 00010000394 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000395 +705bonus pay for amazing work on #OSS 00010000395 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000396 +705bonus pay for amazing work on #OSS 00010000396 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000397 +705bonus pay for amazing work on #OSS 00010000397 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000398 +705bonus pay for amazing work on #OSS 00010000398 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000399 +705bonus pay for amazing work on #OSS 00010000399 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000400 +705bonus pay for amazing work on #OSS 00010000400 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000401 +705bonus pay for amazing work on #OSS 00010000401 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000402 +705bonus pay for amazing work on #OSS 00010000402 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000403 +705bonus pay for amazing work on #OSS 00010000403 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000404 +705bonus pay for amazing work on #OSS 00010000404 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000405 +705bonus pay for amazing work on #OSS 00010000405 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000406 +705bonus pay for amazing work on #OSS 00010000406 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000407 +705bonus pay for amazing work on #OSS 00010000407 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000408 +705bonus pay for amazing work on #OSS 00010000408 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000409 +705bonus pay for amazing work on #OSS 00010000409 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000410 +705bonus pay for amazing work on #OSS 00010000410 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000411 +705bonus pay for amazing work on #OSS 00010000411 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000412 +705bonus pay for amazing work on #OSS 00010000412 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000413 +705bonus pay for amazing work on #OSS 00010000413 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000414 +705bonus pay for amazing work on #OSS 00010000414 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000415 +705bonus pay for amazing work on #OSS 00010000415 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000416 +705bonus pay for amazing work on #OSS 00010000416 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000417 +705bonus pay for amazing work on #OSS 00010000417 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000418 +705bonus pay for amazing work on #OSS 00010000418 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000419 +705bonus pay for amazing work on #OSS 00010000419 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000420 +705bonus pay for amazing work on #OSS 00010000420 +62223138010481967038518 0000100000#gvbQRxek0Dn1v#Zoey Robinson 1121042880000421 +705bonus pay for amazing work on #OSS 00010000421 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000422 +705bonus pay for amazing work on #OSS 00010000422 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000423 +705bonus pay for amazing work on #OSS 00010000423 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000424 +705bonus pay for amazing work on #OSS 00010000424 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000425 +705bonus pay for amazing work on #OSS 00010000425 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000426 +705bonus pay for amazing work on #OSS 00010000426 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000427 +705bonus pay for amazing work on #OSS 00010000427 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000428 +705bonus pay for amazing work on #OSS 00010000428 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000429 +705bonus pay for amazing work on #OSS 00010000429 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000430 +705bonus pay for amazing work on #OSS 00010000430 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000431 +705bonus pay for amazing work on #OSS 00010000431 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000432 +705bonus pay for amazing work on #OSS 00010000432 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000433 +705bonus pay for amazing work on #OSS 00010000433 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000434 +705bonus pay for amazing work on #OSS 00010000434 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000435 +705bonus pay for amazing work on #OSS 00010000435 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000436 +705bonus pay for amazing work on #OSS 00010000436 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000437 +705bonus pay for amazing work on #OSS 00010000437 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000438 +705bonus pay for amazing work on #OSS 00010000438 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000439 +705bonus pay for amazing work on #OSS 00010000439 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000440 +705bonus pay for amazing work on #OSS 00010000440 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000441 +705bonus pay for amazing work on #OSS 00010000441 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000442 +705bonus pay for amazing work on #OSS 00010000442 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000443 +705bonus pay for amazing work on #OSS 00010000443 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000444 +705bonus pay for amazing work on #OSS 00010000444 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000445 +705bonus pay for amazing work on #OSS 00010000445 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000446 +705bonus pay for amazing work on #OSS 00010000446 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000447 +705bonus pay for amazing work on #OSS 00010000447 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000448 +705bonus pay for amazing work on #OSS 00010000448 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000449 +705bonus pay for amazing work on #OSS 00010000449 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000450 +705bonus pay for amazing work on #OSS 00010000450 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000451 +705bonus pay for amazing work on #OSS 00010000451 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000452 +705bonus pay for amazing work on #OSS 00010000452 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000453 +705bonus pay for amazing work on #OSS 00010000453 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000454 +705bonus pay for amazing work on #OSS 00010000454 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000455 +705bonus pay for amazing work on #OSS 00010000455 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000456 +705bonus pay for amazing work on #OSS 00010000456 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000457 +705bonus pay for amazing work on #OSS 00010000457 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000458 +705bonus pay for amazing work on #OSS 00010000458 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000459 +705bonus pay for amazing work on #OSS 00010000459 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000460 +705bonus pay for amazing work on #OSS 00010000460 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000461 +705bonus pay for amazing work on #OSS 00010000461 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000462 +705bonus pay for amazing work on #OSS 00010000462 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000463 +705bonus pay for amazing work on #OSS 00010000463 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000464 +705bonus pay for amazing work on #OSS 00010000464 +62223138010481967038518 0000100000#upJa3WQRvPggQ#Jayden Miller 1121042880000465 +705bonus pay for amazing work on #OSS 00010000465 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Jayden Robinson 1121042880000466 +705bonus pay for amazing work on #OSS 00010000466 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000467 +705bonus pay for amazing work on #OSS 00010000467 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000468 +705bonus pay for amazing work on #OSS 00010000468 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000469 +705bonus pay for amazing work on #OSS 00010000469 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000470 +705bonus pay for amazing work on #OSS 00010000470 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000471 +705bonus pay for amazing work on #OSS 00010000471 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000472 +705bonus pay for amazing work on #OSS 00010000472 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000473 +705bonus pay for amazing work on #OSS 00010000473 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000474 +705bonus pay for amazing work on #OSS 00010000474 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000475 +705bonus pay for amazing work on #OSS 00010000475 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000476 +705bonus pay for amazing work on #OSS 00010000476 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000477 +705bonus pay for amazing work on #OSS 00010000477 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000478 +705bonus pay for amazing work on #OSS 00010000478 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000479 +705bonus pay for amazing work on #OSS 00010000479 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000480 +705bonus pay for amazing work on #OSS 00010000480 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000481 +705bonus pay for amazing work on #OSS 00010000481 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000482 +705bonus pay for amazing work on #OSS 00010000482 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000483 +705bonus pay for amazing work on #OSS 00010000483 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000484 +705bonus pay for amazing work on #OSS 00010000484 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000485 +705bonus pay for amazing work on #OSS 00010000485 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000486 +705bonus pay for amazing work on #OSS 00010000486 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000487 +705bonus pay for amazing work on #OSS 00010000487 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000488 +705bonus pay for amazing work on #OSS 00010000488 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000489 +705bonus pay for amazing work on #OSS 00010000489 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000490 +705bonus pay for amazing work on #OSS 00010000490 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000491 +705bonus pay for amazing work on #OSS 00010000491 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000492 +705bonus pay for amazing work on #OSS 00010000492 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000493 +705bonus pay for amazing work on #OSS 00010000493 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000494 +705bonus pay for amazing work on #OSS 00010000494 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000495 +705bonus pay for amazing work on #OSS 00010000495 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000496 +705bonus pay for amazing work on #OSS 00010000496 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000497 +705bonus pay for amazing work on #OSS 00010000497 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000498 +705bonus pay for amazing work on #OSS 00010000498 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000499 +705bonus pay for amazing work on #OSS 00010000499 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000500 +705bonus pay for amazing work on #OSS 00010000500 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000501 +705bonus pay for amazing work on #OSS 00010000501 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000502 +705bonus pay for amazing work on #OSS 00010000502 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000503 +705bonus pay for amazing work on #OSS 00010000503 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000504 +705bonus pay for amazing work on #OSS 00010000504 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000505 +705bonus pay for amazing work on #OSS 00010000505 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000506 +705bonus pay for amazing work on #OSS 00010000506 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000507 +705bonus pay for amazing work on #OSS 00010000507 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000508 +705bonus pay for amazing work on #OSS 00010000508 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000509 +705bonus pay for amazing work on #OSS 00010000509 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000510 +705bonus pay for amazing work on #OSS 00010000510 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000511 +705bonus pay for amazing work on #OSS 00010000511 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000512 +705bonus pay for amazing work on #OSS 00010000512 +62223138010481967038518 0000100000#dnh0jlYKCxuOO#Zoey Robinson 1121042880000513 +705bonus pay for amazing work on #OSS 00010000513 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000514 +705bonus pay for amazing work on #OSS 00010000514 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000515 +705bonus pay for amazing work on #OSS 00010000515 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000516 +705bonus pay for amazing work on #OSS 00010000516 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000517 +705bonus pay for amazing work on #OSS 00010000517 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000518 +705bonus pay for amazing work on #OSS 00010000518 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000519 +705bonus pay for amazing work on #OSS 00010000519 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000520 +705bonus pay for amazing work on #OSS 00010000520 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000521 +705bonus pay for amazing work on #OSS 00010000521 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000522 +705bonus pay for amazing work on #OSS 00010000522 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000523 +705bonus pay for amazing work on #OSS 00010000523 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000524 +705bonus pay for amazing work on #OSS 00010000524 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000525 +705bonus pay for amazing work on #OSS 00010000525 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000526 +705bonus pay for amazing work on #OSS 00010000526 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000527 +705bonus pay for amazing work on #OSS 00010000527 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000528 +705bonus pay for amazing work on #OSS 00010000528 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000529 +705bonus pay for amazing work on #OSS 00010000529 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000530 +705bonus pay for amazing work on #OSS 00010000530 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000531 +705bonus pay for amazing work on #OSS 00010000531 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000532 +705bonus pay for amazing work on #OSS 00010000532 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000533 +705bonus pay for amazing work on #OSS 00010000533 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000534 +705bonus pay for amazing work on #OSS 00010000534 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000535 +705bonus pay for amazing work on #OSS 00010000535 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000536 +705bonus pay for amazing work on #OSS 00010000536 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000537 +705bonus pay for amazing work on #OSS 00010000537 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000538 +705bonus pay for amazing work on #OSS 00010000538 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000539 +705bonus pay for amazing work on #OSS 00010000539 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000540 +705bonus pay for amazing work on #OSS 00010000540 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000541 +705bonus pay for amazing work on #OSS 00010000541 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000542 +705bonus pay for amazing work on #OSS 00010000542 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000543 +705bonus pay for amazing work on #OSS 00010000543 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000544 +705bonus pay for amazing work on #OSS 00010000544 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000545 +705bonus pay for amazing work on #OSS 00010000545 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000546 +705bonus pay for amazing work on #OSS 00010000546 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000547 +705bonus pay for amazing work on #OSS 00010000547 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000548 +705bonus pay for amazing work on #OSS 00010000548 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000549 +705bonus pay for amazing work on #OSS 00010000549 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000550 +705bonus pay for amazing work on #OSS 00010000550 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000551 +705bonus pay for amazing work on #OSS 00010000551 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000552 +705bonus pay for amazing work on #OSS 00010000552 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000553 +705bonus pay for amazing work on #OSS 00010000553 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000554 +705bonus pay for amazing work on #OSS 00010000554 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000555 +705bonus pay for amazing work on #OSS 00010000555 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000556 +705bonus pay for amazing work on #OSS 00010000556 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000557 +705bonus pay for amazing work on #OSS 00010000557 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000558 +705bonus pay for amazing work on #OSS 00010000558 +62223138010481967038518 0000100000#JCQdA4HRODPk3#Ethan Williams 1121042880000559 +705bonus pay for amazing work on #OSS 00010000559 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000560 +705bonus pay for amazing work on #OSS 00010000560 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000561 +705bonus pay for amazing work on #OSS 00010000561 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000562 +705bonus pay for amazing work on #OSS 00010000562 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000563 +705bonus pay for amazing work on #OSS 00010000563 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000564 +705bonus pay for amazing work on #OSS 00010000564 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000565 +705bonus pay for amazing work on #OSS 00010000565 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000566 +705bonus pay for amazing work on #OSS 00010000566 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000567 +705bonus pay for amazing work on #OSS 00010000567 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000568 +705bonus pay for amazing work on #OSS 00010000568 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000569 +705bonus pay for amazing work on #OSS 00010000569 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000570 +705bonus pay for amazing work on #OSS 00010000570 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000571 +705bonus pay for amazing work on #OSS 00010000571 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000572 +705bonus pay for amazing work on #OSS 00010000572 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000573 +705bonus pay for amazing work on #OSS 00010000573 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000574 +705bonus pay for amazing work on #OSS 00010000574 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000575 +705bonus pay for amazing work on #OSS 00010000575 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000576 +705bonus pay for amazing work on #OSS 00010000576 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000577 +705bonus pay for amazing work on #OSS 00010000577 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000578 +705bonus pay for amazing work on #OSS 00010000578 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000579 +705bonus pay for amazing work on #OSS 00010000579 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000580 +705bonus pay for amazing work on #OSS 00010000580 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000581 +705bonus pay for amazing work on #OSS 00010000581 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000582 +705bonus pay for amazing work on #OSS 00010000582 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000583 +705bonus pay for amazing work on #OSS 00010000583 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000584 +705bonus pay for amazing work on #OSS 00010000584 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000585 +705bonus pay for amazing work on #OSS 00010000585 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000586 +705bonus pay for amazing work on #OSS 00010000586 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000587 +705bonus pay for amazing work on #OSS 00010000587 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000588 +705bonus pay for amazing work on #OSS 00010000588 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000589 +705bonus pay for amazing work on #OSS 00010000589 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000590 +705bonus pay for amazing work on #OSS 00010000590 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000591 +705bonus pay for amazing work on #OSS 00010000591 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000592 +705bonus pay for amazing work on #OSS 00010000592 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000593 +705bonus pay for amazing work on #OSS 00010000593 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000594 +705bonus pay for amazing work on #OSS 00010000594 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000595 +705bonus pay for amazing work on #OSS 00010000595 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000596 +705bonus pay for amazing work on #OSS 00010000596 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000597 +705bonus pay for amazing work on #OSS 00010000597 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000598 +705bonus pay for amazing work on #OSS 00010000598 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000599 +705bonus pay for amazing work on #OSS 00010000599 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000600 +705bonus pay for amazing work on #OSS 00010000600 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000601 +705bonus pay for amazing work on #OSS 00010000601 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000602 +705bonus pay for amazing work on #OSS 00010000602 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000603 +705bonus pay for amazing work on #OSS 00010000603 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000604 +705bonus pay for amazing work on #OSS 00010000604 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000605 +705bonus pay for amazing work on #OSS 00010000605 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000606 +705bonus pay for amazing work on #OSS 00010000606 +62223138010481967038518 0000100000#0s1hyqkGho2Vo#Elijah Jackson 1121042880000607 +705bonus pay for amazing work on #OSS 00010000607 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000608 +705bonus pay for amazing work on #OSS 00010000608 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000609 +705bonus pay for amazing work on #OSS 00010000609 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000610 +705bonus pay for amazing work on #OSS 00010000610 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000611 +705bonus pay for amazing work on #OSS 00010000611 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000612 +705bonus pay for amazing work on #OSS 00010000612 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000613 +705bonus pay for amazing work on #OSS 00010000613 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000614 +705bonus pay for amazing work on #OSS 00010000614 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000615 +705bonus pay for amazing work on #OSS 00010000615 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000616 +705bonus pay for amazing work on #OSS 00010000616 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000617 +705bonus pay for amazing work on #OSS 00010000617 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000618 +705bonus pay for amazing work on #OSS 00010000618 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000619 +705bonus pay for amazing work on #OSS 00010000619 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000620 +705bonus pay for amazing work on #OSS 00010000620 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000621 +705bonus pay for amazing work on #OSS 00010000621 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000622 +705bonus pay for amazing work on #OSS 00010000622 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000623 +705bonus pay for amazing work on #OSS 00010000623 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000624 +705bonus pay for amazing work on #OSS 00010000624 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000625 +705bonus pay for amazing work on #OSS 00010000625 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000626 +705bonus pay for amazing work on #OSS 00010000626 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000627 +705bonus pay for amazing work on #OSS 00010000627 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000628 +705bonus pay for amazing work on #OSS 00010000628 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000629 +705bonus pay for amazing work on #OSS 00010000629 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000630 +705bonus pay for amazing work on #OSS 00010000630 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000631 +705bonus pay for amazing work on #OSS 00010000631 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000632 +705bonus pay for amazing work on #OSS 00010000632 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000633 +705bonus pay for amazing work on #OSS 00010000633 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000634 +705bonus pay for amazing work on #OSS 00010000634 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000635 +705bonus pay for amazing work on #OSS 00010000635 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000636 +705bonus pay for amazing work on #OSS 00010000636 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000637 +705bonus pay for amazing work on #OSS 00010000637 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000638 +705bonus pay for amazing work on #OSS 00010000638 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000639 +705bonus pay for amazing work on #OSS 00010000639 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000640 +705bonus pay for amazing work on #OSS 00010000640 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000641 +705bonus pay for amazing work on #OSS 00010000641 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000642 +705bonus pay for amazing work on #OSS 00010000642 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000643 +705bonus pay for amazing work on #OSS 00010000643 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000644 +705bonus pay for amazing work on #OSS 00010000644 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000645 +705bonus pay for amazing work on #OSS 00010000645 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000646 +705bonus pay for amazing work on #OSS 00010000646 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000647 +705bonus pay for amazing work on #OSS 00010000647 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000648 +705bonus pay for amazing work on #OSS 00010000648 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000649 +705bonus pay for amazing work on #OSS 00010000649 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000650 +705bonus pay for amazing work on #OSS 00010000650 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000651 +705bonus pay for amazing work on #OSS 00010000651 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000652 +705bonus pay for amazing work on #OSS 00010000652 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000653 +705bonus pay for amazing work on #OSS 00010000653 +62223138010481967038518 0000100000#BA2xs4e7S9Seu#Anthony Harris 1121042880000654 +705bonus pay for amazing work on #OSS 00010000654 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#Anthony Martinez 1121042880000655 +705bonus pay for amazing work on #OSS 00010000655 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000656 +705bonus pay for amazing work on #OSS 00010000656 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000657 +705bonus pay for amazing work on #OSS 00010000657 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000658 +705bonus pay for amazing work on #OSS 00010000658 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000659 +705bonus pay for amazing work on #OSS 00010000659 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000660 +705bonus pay for amazing work on #OSS 00010000660 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000661 +705bonus pay for amazing work on #OSS 00010000661 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000662 +705bonus pay for amazing work on #OSS 00010000662 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000663 +705bonus pay for amazing work on #OSS 00010000663 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000664 +705bonus pay for amazing work on #OSS 00010000664 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000665 +705bonus pay for amazing work on #OSS 00010000665 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000666 +705bonus pay for amazing work on #OSS 00010000666 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000667 +705bonus pay for amazing work on #OSS 00010000667 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000668 +705bonus pay for amazing work on #OSS 00010000668 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000669 +705bonus pay for amazing work on #OSS 00010000669 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000670 +705bonus pay for amazing work on #OSS 00010000670 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000671 +705bonus pay for amazing work on #OSS 00010000671 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000672 +705bonus pay for amazing work on #OSS 00010000672 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000673 +705bonus pay for amazing work on #OSS 00010000673 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000674 +705bonus pay for amazing work on #OSS 00010000674 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000675 +705bonus pay for amazing work on #OSS 00010000675 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000676 +705bonus pay for amazing work on #OSS 00010000676 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000677 +705bonus pay for amazing work on #OSS 00010000677 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000678 +705bonus pay for amazing work on #OSS 00010000678 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000679 +705bonus pay for amazing work on #OSS 00010000679 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000680 +705bonus pay for amazing work on #OSS 00010000680 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000681 +705bonus pay for amazing work on #OSS 00010000681 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000682 +705bonus pay for amazing work on #OSS 00010000682 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000683 +705bonus pay for amazing work on #OSS 00010000683 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000684 +705bonus pay for amazing work on #OSS 00010000684 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000685 +705bonus pay for amazing work on #OSS 00010000685 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000686 +705bonus pay for amazing work on #OSS 00010000686 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000687 +705bonus pay for amazing work on #OSS 00010000687 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000688 +705bonus pay for amazing work on #OSS 00010000688 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000689 +705bonus pay for amazing work on #OSS 00010000689 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000690 +705bonus pay for amazing work on #OSS 00010000690 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000691 +705bonus pay for amazing work on #OSS 00010000691 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000692 +705bonus pay for amazing work on #OSS 00010000692 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000693 +705bonus pay for amazing work on #OSS 00010000693 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000694 +705bonus pay for amazing work on #OSS 00010000694 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000695 +705bonus pay for amazing work on #OSS 00010000695 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000696 +705bonus pay for amazing work on #OSS 00010000696 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000697 +705bonus pay for amazing work on #OSS 00010000697 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000698 +705bonus pay for amazing work on #OSS 00010000698 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000699 +705bonus pay for amazing work on #OSS 00010000699 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000700 +705bonus pay for amazing work on #OSS 00010000700 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000701 +705bonus pay for amazing work on #OSS 00010000701 +62223138010481967038518 0000100000#FQDvrkpLXWUs0#David Martinez 1121042880000702 +705bonus pay for amazing work on #OSS 00010000702 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000703 +705bonus pay for amazing work on #OSS 00010000703 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000704 +705bonus pay for amazing work on #OSS 00010000704 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000705 +705bonus pay for amazing work on #OSS 00010000705 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000706 +705bonus pay for amazing work on #OSS 00010000706 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000707 +705bonus pay for amazing work on #OSS 00010000707 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000708 +705bonus pay for amazing work on #OSS 00010000708 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000709 +705bonus pay for amazing work on #OSS 00010000709 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000710 +705bonus pay for amazing work on #OSS 00010000710 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000711 +705bonus pay for amazing work on #OSS 00010000711 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000712 +705bonus pay for amazing work on #OSS 00010000712 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000713 +705bonus pay for amazing work on #OSS 00010000713 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000714 +705bonus pay for amazing work on #OSS 00010000714 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000715 +705bonus pay for amazing work on #OSS 00010000715 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000716 +705bonus pay for amazing work on #OSS 00010000716 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000717 +705bonus pay for amazing work on #OSS 00010000717 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000718 +705bonus pay for amazing work on #OSS 00010000718 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000719 +705bonus pay for amazing work on #OSS 00010000719 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000720 +705bonus pay for amazing work on #OSS 00010000720 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000721 +705bonus pay for amazing work on #OSS 00010000721 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000722 +705bonus pay for amazing work on #OSS 00010000722 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000723 +705bonus pay for amazing work on #OSS 00010000723 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000724 +705bonus pay for amazing work on #OSS 00010000724 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000725 +705bonus pay for amazing work on #OSS 00010000725 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000726 +705bonus pay for amazing work on #OSS 00010000726 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000727 +705bonus pay for amazing work on #OSS 00010000727 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000728 +705bonus pay for amazing work on #OSS 00010000728 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000729 +705bonus pay for amazing work on #OSS 00010000729 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000730 +705bonus pay for amazing work on #OSS 00010000730 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000731 +705bonus pay for amazing work on #OSS 00010000731 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000732 +705bonus pay for amazing work on #OSS 00010000732 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000733 +705bonus pay for amazing work on #OSS 00010000733 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000734 +705bonus pay for amazing work on #OSS 00010000734 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000735 +705bonus pay for amazing work on #OSS 00010000735 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000736 +705bonus pay for amazing work on #OSS 00010000736 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000737 +705bonus pay for amazing work on #OSS 00010000737 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000738 +705bonus pay for amazing work on #OSS 00010000738 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000739 +705bonus pay for amazing work on #OSS 00010000739 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000740 +705bonus pay for amazing work on #OSS 00010000740 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000741 +705bonus pay for amazing work on #OSS 00010000741 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000742 +705bonus pay for amazing work on #OSS 00010000742 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000743 +705bonus pay for amazing work on #OSS 00010000743 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000744 +705bonus pay for amazing work on #OSS 00010000744 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000745 +705bonus pay for amazing work on #OSS 00010000745 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000746 +705bonus pay for amazing work on #OSS 00010000746 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000747 +705bonus pay for amazing work on #OSS 00010000747 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000748 +705bonus pay for amazing work on #OSS 00010000748 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000749 +705bonus pay for amazing work on #OSS 00010000749 +62223138010481967038518 0000100000#fAI9xBZUPYye4#Ethan Williams 1121042880000750 +705bonus pay for amazing work on #OSS 00010000750 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000751 +705bonus pay for amazing work on #OSS 00010000751 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000752 +705bonus pay for amazing work on #OSS 00010000752 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000753 +705bonus pay for amazing work on #OSS 00010000753 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000754 +705bonus pay for amazing work on #OSS 00010000754 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000755 +705bonus pay for amazing work on #OSS 00010000755 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000756 +705bonus pay for amazing work on #OSS 00010000756 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000757 +705bonus pay for amazing work on #OSS 00010000757 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000758 +705bonus pay for amazing work on #OSS 00010000758 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000759 +705bonus pay for amazing work on #OSS 00010000759 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000760 +705bonus pay for amazing work on #OSS 00010000760 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000761 +705bonus pay for amazing work on #OSS 00010000761 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000762 +705bonus pay for amazing work on #OSS 00010000762 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000763 +705bonus pay for amazing work on #OSS 00010000763 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000764 +705bonus pay for amazing work on #OSS 00010000764 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000765 +705bonus pay for amazing work on #OSS 00010000765 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000766 +705bonus pay for amazing work on #OSS 00010000766 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000767 +705bonus pay for amazing work on #OSS 00010000767 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000768 +705bonus pay for amazing work on #OSS 00010000768 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000769 +705bonus pay for amazing work on #OSS 00010000769 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000770 +705bonus pay for amazing work on #OSS 00010000770 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000771 +705bonus pay for amazing work on #OSS 00010000771 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000772 +705bonus pay for amazing work on #OSS 00010000772 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000773 +705bonus pay for amazing work on #OSS 00010000773 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000774 +705bonus pay for amazing work on #OSS 00010000774 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000775 +705bonus pay for amazing work on #OSS 00010000775 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000776 +705bonus pay for amazing work on #OSS 00010000776 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000777 +705bonus pay for amazing work on #OSS 00010000777 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000778 +705bonus pay for amazing work on #OSS 00010000778 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000779 +705bonus pay for amazing work on #OSS 00010000779 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000780 +705bonus pay for amazing work on #OSS 00010000780 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000781 +705bonus pay for amazing work on #OSS 00010000781 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000782 +705bonus pay for amazing work on #OSS 00010000782 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000783 +705bonus pay for amazing work on #OSS 00010000783 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000784 +705bonus pay for amazing work on #OSS 00010000784 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000785 +705bonus pay for amazing work on #OSS 00010000785 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000786 +705bonus pay for amazing work on #OSS 00010000786 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000787 +705bonus pay for amazing work on #OSS 00010000787 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000788 +705bonus pay for amazing work on #OSS 00010000788 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000789 +705bonus pay for amazing work on #OSS 00010000789 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000790 +705bonus pay for amazing work on #OSS 00010000790 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000791 +705bonus pay for amazing work on #OSS 00010000791 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000792 +705bonus pay for amazing work on #OSS 00010000792 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000793 +705bonus pay for amazing work on #OSS 00010000793 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000794 +705bonus pay for amazing work on #OSS 00010000794 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000795 +705bonus pay for amazing work on #OSS 00010000795 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000796 +705bonus pay for amazing work on #OSS 00010000796 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000797 +705bonus pay for amazing work on #OSS 00010000797 +62223138010481967038518 0000100000#BkkafNgLCvTvR#Ella Thomas 1121042880000798 +705bonus pay for amazing work on #OSS 00010000798 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000799 +705bonus pay for amazing work on #OSS 00010000799 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000800 +705bonus pay for amazing work on #OSS 00010000800 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000801 +705bonus pay for amazing work on #OSS 00010000801 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000802 +705bonus pay for amazing work on #OSS 00010000802 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000803 +705bonus pay for amazing work on #OSS 00010000803 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000804 +705bonus pay for amazing work on #OSS 00010000804 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000805 +705bonus pay for amazing work on #OSS 00010000805 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000806 +705bonus pay for amazing work on #OSS 00010000806 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000807 +705bonus pay for amazing work on #OSS 00010000807 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000808 +705bonus pay for amazing work on #OSS 00010000808 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000809 +705bonus pay for amazing work on #OSS 00010000809 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000810 +705bonus pay for amazing work on #OSS 00010000810 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000811 +705bonus pay for amazing work on #OSS 00010000811 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000812 +705bonus pay for amazing work on #OSS 00010000812 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000813 +705bonus pay for amazing work on #OSS 00010000813 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000814 +705bonus pay for amazing work on #OSS 00010000814 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000815 +705bonus pay for amazing work on #OSS 00010000815 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000816 +705bonus pay for amazing work on #OSS 00010000816 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000817 +705bonus pay for amazing work on #OSS 00010000817 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000818 +705bonus pay for amazing work on #OSS 00010000818 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000819 +705bonus pay for amazing work on #OSS 00010000819 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000820 +705bonus pay for amazing work on #OSS 00010000820 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000821 +705bonus pay for amazing work on #OSS 00010000821 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000822 +705bonus pay for amazing work on #OSS 00010000822 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000823 +705bonus pay for amazing work on #OSS 00010000823 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000824 +705bonus pay for amazing work on #OSS 00010000824 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000825 +705bonus pay for amazing work on #OSS 00010000825 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000826 +705bonus pay for amazing work on #OSS 00010000826 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000827 +705bonus pay for amazing work on #OSS 00010000827 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000828 +705bonus pay for amazing work on #OSS 00010000828 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000829 +705bonus pay for amazing work on #OSS 00010000829 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000830 +705bonus pay for amazing work on #OSS 00010000830 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000831 +705bonus pay for amazing work on #OSS 00010000831 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000832 +705bonus pay for amazing work on #OSS 00010000832 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000833 +705bonus pay for amazing work on #OSS 00010000833 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000834 +705bonus pay for amazing work on #OSS 00010000834 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000835 +705bonus pay for amazing work on #OSS 00010000835 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000836 +705bonus pay for amazing work on #OSS 00010000836 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000837 +705bonus pay for amazing work on #OSS 00010000837 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000838 +705bonus pay for amazing work on #OSS 00010000838 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000839 +705bonus pay for amazing work on #OSS 00010000839 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000840 +705bonus pay for amazing work on #OSS 00010000840 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000841 +705bonus pay for amazing work on #OSS 00010000841 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000842 +705bonus pay for amazing work on #OSS 00010000842 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000843 +705bonus pay for amazing work on #OSS 00010000843 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000844 +705bonus pay for amazing work on #OSS 00010000844 +62223138010481967038518 0000100000#P6shUsdPn8luO#Emma Johnson 1121042880000845 +705bonus pay for amazing work on #OSS 00010000845 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Abigail Miller 1121042880000846 +705bonus pay for amazing work on #OSS 00010000846 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000847 +705bonus pay for amazing work on #OSS 00010000847 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000848 +705bonus pay for amazing work on #OSS 00010000848 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000849 +705bonus pay for amazing work on #OSS 00010000849 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000850 +705bonus pay for amazing work on #OSS 00010000850 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000851 +705bonus pay for amazing work on #OSS 00010000851 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000852 +705bonus pay for amazing work on #OSS 00010000852 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000853 +705bonus pay for amazing work on #OSS 00010000853 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000854 +705bonus pay for amazing work on #OSS 00010000854 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000855 +705bonus pay for amazing work on #OSS 00010000855 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000856 +705bonus pay for amazing work on #OSS 00010000856 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000857 +705bonus pay for amazing work on #OSS 00010000857 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000858 +705bonus pay for amazing work on #OSS 00010000858 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000859 +705bonus pay for amazing work on #OSS 00010000859 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000860 +705bonus pay for amazing work on #OSS 00010000860 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000861 +705bonus pay for amazing work on #OSS 00010000861 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000862 +705bonus pay for amazing work on #OSS 00010000862 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000863 +705bonus pay for amazing work on #OSS 00010000863 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000864 +705bonus pay for amazing work on #OSS 00010000864 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000865 +705bonus pay for amazing work on #OSS 00010000865 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000866 +705bonus pay for amazing work on #OSS 00010000866 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000867 +705bonus pay for amazing work on #OSS 00010000867 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000868 +705bonus pay for amazing work on #OSS 00010000868 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000869 +705bonus pay for amazing work on #OSS 00010000869 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000870 +705bonus pay for amazing work on #OSS 00010000870 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000871 +705bonus pay for amazing work on #OSS 00010000871 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000872 +705bonus pay for amazing work on #OSS 00010000872 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000873 +705bonus pay for amazing work on #OSS 00010000873 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000874 +705bonus pay for amazing work on #OSS 00010000874 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000875 +705bonus pay for amazing work on #OSS 00010000875 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000876 +705bonus pay for amazing work on #OSS 00010000876 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000877 +705bonus pay for amazing work on #OSS 00010000877 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000878 +705bonus pay for amazing work on #OSS 00010000878 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000879 +705bonus pay for amazing work on #OSS 00010000879 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000880 +705bonus pay for amazing work on #OSS 00010000880 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000881 +705bonus pay for amazing work on #OSS 00010000881 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000882 +705bonus pay for amazing work on #OSS 00010000882 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000883 +705bonus pay for amazing work on #OSS 00010000883 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000884 +705bonus pay for amazing work on #OSS 00010000884 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000885 +705bonus pay for amazing work on #OSS 00010000885 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000886 +705bonus pay for amazing work on #OSS 00010000886 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000887 +705bonus pay for amazing work on #OSS 00010000887 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000888 +705bonus pay for amazing work on #OSS 00010000888 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000889 +705bonus pay for amazing work on #OSS 00010000889 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000890 +705bonus pay for amazing work on #OSS 00010000890 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000891 +705bonus pay for amazing work on #OSS 00010000891 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000892 +705bonus pay for amazing work on #OSS 00010000892 +62223138010481967038518 0000100000#rsTxrXxzgSEAt#Jayden Miller 1121042880000893 +705bonus pay for amazing work on #OSS 00010000893 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000894 +705bonus pay for amazing work on #OSS 00010000894 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000895 +705bonus pay for amazing work on #OSS 00010000895 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000896 +705bonus pay for amazing work on #OSS 00010000896 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000897 +705bonus pay for amazing work on #OSS 00010000897 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000898 +705bonus pay for amazing work on #OSS 00010000898 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000899 +705bonus pay for amazing work on #OSS 00010000899 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000900 +705bonus pay for amazing work on #OSS 00010000900 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000901 +705bonus pay for amazing work on #OSS 00010000901 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000902 +705bonus pay for amazing work on #OSS 00010000902 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000903 +705bonus pay for amazing work on #OSS 00010000903 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000904 +705bonus pay for amazing work on #OSS 00010000904 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000905 +705bonus pay for amazing work on #OSS 00010000905 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000906 +705bonus pay for amazing work on #OSS 00010000906 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000907 +705bonus pay for amazing work on #OSS 00010000907 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000908 +705bonus pay for amazing work on #OSS 00010000908 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000909 +705bonus pay for amazing work on #OSS 00010000909 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000910 +705bonus pay for amazing work on #OSS 00010000910 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000911 +705bonus pay for amazing work on #OSS 00010000911 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000912 +705bonus pay for amazing work on #OSS 00010000912 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000913 +705bonus pay for amazing work on #OSS 00010000913 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000914 +705bonus pay for amazing work on #OSS 00010000914 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000915 +705bonus pay for amazing work on #OSS 00010000915 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000916 +705bonus pay for amazing work on #OSS 00010000916 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000917 +705bonus pay for amazing work on #OSS 00010000917 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000918 +705bonus pay for amazing work on #OSS 00010000918 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000919 +705bonus pay for amazing work on #OSS 00010000919 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000920 +705bonus pay for amazing work on #OSS 00010000920 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000921 +705bonus pay for amazing work on #OSS 00010000921 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000922 +705bonus pay for amazing work on #OSS 00010000922 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000923 +705bonus pay for amazing work on #OSS 00010000923 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000924 +705bonus pay for amazing work on #OSS 00010000924 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000925 +705bonus pay for amazing work on #OSS 00010000925 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000926 +705bonus pay for amazing work on #OSS 00010000926 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000927 +705bonus pay for amazing work on #OSS 00010000927 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000928 +705bonus pay for amazing work on #OSS 00010000928 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000929 +705bonus pay for amazing work on #OSS 00010000929 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000930 +705bonus pay for amazing work on #OSS 00010000930 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000931 +705bonus pay for amazing work on #OSS 00010000931 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000932 +705bonus pay for amazing work on #OSS 00010000932 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000933 +705bonus pay for amazing work on #OSS 00010000933 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000934 +705bonus pay for amazing work on #OSS 00010000934 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000935 +705bonus pay for amazing work on #OSS 00010000935 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000936 +705bonus pay for amazing work on #OSS 00010000936 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000937 +705bonus pay for amazing work on #OSS 00010000937 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000938 +705bonus pay for amazing work on #OSS 00010000938 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000939 +705bonus pay for amazing work on #OSS 00010000939 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000940 +705bonus pay for amazing work on #OSS 00010000940 +62223138010481967038518 0000100000#uGHgH7nEqmcET#Alexander Moore 1121042880000941 +705bonus pay for amazing work on #OSS 00010000941 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000942 +705bonus pay for amazing work on #OSS 00010000942 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000943 +705bonus pay for amazing work on #OSS 00010000943 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000944 +705bonus pay for amazing work on #OSS 00010000944 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000945 +705bonus pay for amazing work on #OSS 00010000945 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000946 +705bonus pay for amazing work on #OSS 00010000946 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000947 +705bonus pay for amazing work on #OSS 00010000947 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000948 +705bonus pay for amazing work on #OSS 00010000948 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000949 +705bonus pay for amazing work on #OSS 00010000949 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000950 +705bonus pay for amazing work on #OSS 00010000950 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000951 +705bonus pay for amazing work on #OSS 00010000951 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000952 +705bonus pay for amazing work on #OSS 00010000952 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000953 +705bonus pay for amazing work on #OSS 00010000953 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000954 +705bonus pay for amazing work on #OSS 00010000954 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000955 +705bonus pay for amazing work on #OSS 00010000955 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000956 +705bonus pay for amazing work on #OSS 00010000956 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000957 +705bonus pay for amazing work on #OSS 00010000957 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000958 +705bonus pay for amazing work on #OSS 00010000958 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000959 +705bonus pay for amazing work on #OSS 00010000959 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000960 +705bonus pay for amazing work on #OSS 00010000960 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000961 +705bonus pay for amazing work on #OSS 00010000961 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000962 +705bonus pay for amazing work on #OSS 00010000962 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000963 +705bonus pay for amazing work on #OSS 00010000963 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000964 +705bonus pay for amazing work on #OSS 00010000964 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000965 +705bonus pay for amazing work on #OSS 00010000965 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000966 +705bonus pay for amazing work on #OSS 00010000966 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000967 +705bonus pay for amazing work on #OSS 00010000967 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000968 +705bonus pay for amazing work on #OSS 00010000968 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000969 +705bonus pay for amazing work on #OSS 00010000969 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000970 +705bonus pay for amazing work on #OSS 00010000970 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000971 +705bonus pay for amazing work on #OSS 00010000971 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000972 +705bonus pay for amazing work on #OSS 00010000972 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000973 +705bonus pay for amazing work on #OSS 00010000973 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000974 +705bonus pay for amazing work on #OSS 00010000974 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000975 +705bonus pay for amazing work on #OSS 00010000975 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000976 +705bonus pay for amazing work on #OSS 00010000976 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000977 +705bonus pay for amazing work on #OSS 00010000977 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000978 +705bonus pay for amazing work on #OSS 00010000978 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000979 +705bonus pay for amazing work on #OSS 00010000979 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000980 +705bonus pay for amazing work on #OSS 00010000980 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000981 +705bonus pay for amazing work on #OSS 00010000981 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000982 +705bonus pay for amazing work on #OSS 00010000982 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000983 +705bonus pay for amazing work on #OSS 00010000983 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000984 +705bonus pay for amazing work on #OSS 00010000984 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000985 +705bonus pay for amazing work on #OSS 00010000985 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000986 +705bonus pay for amazing work on #OSS 00010000986 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000987 +705bonus pay for amazing work on #OSS 00010000987 +62223138010481967038518 0000100000#fZdKWH6VnQ7Rs#Daniel Anderson 1121042880000988 +705bonus pay for amazing work on #OSS 00010000988 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Matthew Thomas 1121042880000989 +705bonus pay for amazing work on #OSS 00010000989 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880000990 +705bonus pay for amazing work on #OSS 00010000990 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880000991 +705bonus pay for amazing work on #OSS 00010000991 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880000992 +705bonus pay for amazing work on #OSS 00010000992 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880000993 +705bonus pay for amazing work on #OSS 00010000993 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880000994 +705bonus pay for amazing work on #OSS 00010000994 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880000995 +705bonus pay for amazing work on #OSS 00010000995 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880000996 +705bonus pay for amazing work on #OSS 00010000996 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880000997 +705bonus pay for amazing work on #OSS 00010000997 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880000998 +705bonus pay for amazing work on #OSS 00010000998 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880000999 +705bonus pay for amazing work on #OSS 00010000999 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001000 +705bonus pay for amazing work on #OSS 00010001000 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001001 +705bonus pay for amazing work on #OSS 00010001001 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001002 +705bonus pay for amazing work on #OSS 00010001002 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001003 +705bonus pay for amazing work on #OSS 00010001003 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001004 +705bonus pay for amazing work on #OSS 00010001004 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001005 +705bonus pay for amazing work on #OSS 00010001005 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001006 +705bonus pay for amazing work on #OSS 00010001006 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001007 +705bonus pay for amazing work on #OSS 00010001007 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001008 +705bonus pay for amazing work on #OSS 00010001008 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001009 +705bonus pay for amazing work on #OSS 00010001009 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001010 +705bonus pay for amazing work on #OSS 00010001010 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001011 +705bonus pay for amazing work on #OSS 00010001011 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001012 +705bonus pay for amazing work on #OSS 00010001012 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001013 +705bonus pay for amazing work on #OSS 00010001013 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001014 +705bonus pay for amazing work on #OSS 00010001014 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001015 +705bonus pay for amazing work on #OSS 00010001015 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001016 +705bonus pay for amazing work on #OSS 00010001016 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001017 +705bonus pay for amazing work on #OSS 00010001017 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001018 +705bonus pay for amazing work on #OSS 00010001018 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001019 +705bonus pay for amazing work on #OSS 00010001019 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001020 +705bonus pay for amazing work on #OSS 00010001020 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001021 +705bonus pay for amazing work on #OSS 00010001021 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001022 +705bonus pay for amazing work on #OSS 00010001022 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001023 +705bonus pay for amazing work on #OSS 00010001023 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001024 +705bonus pay for amazing work on #OSS 00010001024 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001025 +705bonus pay for amazing work on #OSS 00010001025 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001026 +705bonus pay for amazing work on #OSS 00010001026 +62223138010481967038518 0000100000#kaiUzHemlLqDk#Ella Thomas 1121042880001027 +705bonus pay for amazing work on #OSS 00010001027 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Ella White 1121042880001028 +705bonus pay for amazing work on #OSS 00010001028 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001029 +705bonus pay for amazing work on #OSS 00010001029 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001030 +705bonus pay for amazing work on #OSS 00010001030 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001031 +705bonus pay for amazing work on #OSS 00010001031 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001032 +705bonus pay for amazing work on #OSS 00010001032 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001033 +705bonus pay for amazing work on #OSS 00010001033 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001034 +705bonus pay for amazing work on #OSS 00010001034 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001035 +705bonus pay for amazing work on #OSS 00010001035 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001036 +705bonus pay for amazing work on #OSS 00010001036 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001037 +705bonus pay for amazing work on #OSS 00010001037 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001038 +705bonus pay for amazing work on #OSS 00010001038 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001039 +705bonus pay for amazing work on #OSS 00010001039 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001040 +705bonus pay for amazing work on #OSS 00010001040 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001041 +705bonus pay for amazing work on #OSS 00010001041 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001042 +705bonus pay for amazing work on #OSS 00010001042 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001043 +705bonus pay for amazing work on #OSS 00010001043 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001044 +705bonus pay for amazing work on #OSS 00010001044 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001045 +705bonus pay for amazing work on #OSS 00010001045 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001046 +705bonus pay for amazing work on #OSS 00010001046 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001047 +705bonus pay for amazing work on #OSS 00010001047 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001048 +705bonus pay for amazing work on #OSS 00010001048 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001049 +705bonus pay for amazing work on #OSS 00010001049 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001050 +705bonus pay for amazing work on #OSS 00010001050 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001051 +705bonus pay for amazing work on #OSS 00010001051 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001052 +705bonus pay for amazing work on #OSS 00010001052 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001053 +705bonus pay for amazing work on #OSS 00010001053 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001054 +705bonus pay for amazing work on #OSS 00010001054 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001055 +705bonus pay for amazing work on #OSS 00010001055 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001056 +705bonus pay for amazing work on #OSS 00010001056 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001057 +705bonus pay for amazing work on #OSS 00010001057 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001058 +705bonus pay for amazing work on #OSS 00010001058 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001059 +705bonus pay for amazing work on #OSS 00010001059 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001060 +705bonus pay for amazing work on #OSS 00010001060 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001061 +705bonus pay for amazing work on #OSS 00010001061 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001062 +705bonus pay for amazing work on #OSS 00010001062 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001063 +705bonus pay for amazing work on #OSS 00010001063 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001064 +705bonus pay for amazing work on #OSS 00010001064 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001065 +705bonus pay for amazing work on #OSS 00010001065 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001066 +705bonus pay for amazing work on #OSS 00010001066 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001067 +705bonus pay for amazing work on #OSS 00010001067 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001068 +705bonus pay for amazing work on #OSS 00010001068 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001069 +705bonus pay for amazing work on #OSS 00010001069 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001070 +705bonus pay for amazing work on #OSS 00010001070 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001071 +705bonus pay for amazing work on #OSS 00010001071 +62223138010481967038518 0000100000#jV5YV4idFE0ew#Addison White 1121042880001072 +705bonus pay for amazing work on #OSS 00010001072 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Addison Garcia 1121042880001073 +705bonus pay for amazing work on #OSS 00010001073 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001074 +705bonus pay for amazing work on #OSS 00010001074 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001075 +705bonus pay for amazing work on #OSS 00010001075 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001076 +705bonus pay for amazing work on #OSS 00010001076 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001077 +705bonus pay for amazing work on #OSS 00010001077 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001078 +705bonus pay for amazing work on #OSS 00010001078 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001079 +705bonus pay for amazing work on #OSS 00010001079 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001080 +705bonus pay for amazing work on #OSS 00010001080 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001081 +705bonus pay for amazing work on #OSS 00010001081 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001082 +705bonus pay for amazing work on #OSS 00010001082 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001083 +705bonus pay for amazing work on #OSS 00010001083 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001084 +705bonus pay for amazing work on #OSS 00010001084 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001085 +705bonus pay for amazing work on #OSS 00010001085 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001086 +705bonus pay for amazing work on #OSS 00010001086 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001087 +705bonus pay for amazing work on #OSS 00010001087 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001088 +705bonus pay for amazing work on #OSS 00010001088 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001089 +705bonus pay for amazing work on #OSS 00010001089 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001090 +705bonus pay for amazing work on #OSS 00010001090 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001091 +705bonus pay for amazing work on #OSS 00010001091 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001092 +705bonus pay for amazing work on #OSS 00010001092 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001093 +705bonus pay for amazing work on #OSS 00010001093 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001094 +705bonus pay for amazing work on #OSS 00010001094 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001095 +705bonus pay for amazing work on #OSS 00010001095 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001096 +705bonus pay for amazing work on #OSS 00010001096 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001097 +705bonus pay for amazing work on #OSS 00010001097 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001098 +705bonus pay for amazing work on #OSS 00010001098 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001099 +705bonus pay for amazing work on #OSS 00010001099 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001100 +705bonus pay for amazing work on #OSS 00010001100 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001101 +705bonus pay for amazing work on #OSS 00010001101 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001102 +705bonus pay for amazing work on #OSS 00010001102 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001103 +705bonus pay for amazing work on #OSS 00010001103 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001104 +705bonus pay for amazing work on #OSS 00010001104 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001105 +705bonus pay for amazing work on #OSS 00010001105 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001106 +705bonus pay for amazing work on #OSS 00010001106 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001107 +705bonus pay for amazing work on #OSS 00010001107 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001108 +705bonus pay for amazing work on #OSS 00010001108 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001109 +705bonus pay for amazing work on #OSS 00010001109 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001110 +705bonus pay for amazing work on #OSS 00010001110 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001111 +705bonus pay for amazing work on #OSS 00010001111 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001112 +705bonus pay for amazing work on #OSS 00010001112 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001113 +705bonus pay for amazing work on #OSS 00010001113 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001114 +705bonus pay for amazing work on #OSS 00010001114 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001115 +705bonus pay for amazing work on #OSS 00010001115 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001116 +705bonus pay for amazing work on #OSS 00010001116 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001117 +705bonus pay for amazing work on #OSS 00010001117 +62223138010481967038518 0000100000#SaGqNIVH7tyeR#Sofia Garcia 1121042880001118 +705bonus pay for amazing work on #OSS 00010001118 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001119 +705bonus pay for amazing work on #OSS 00010001119 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001120 +705bonus pay for amazing work on #OSS 00010001120 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001121 +705bonus pay for amazing work on #OSS 00010001121 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001122 +705bonus pay for amazing work on #OSS 00010001122 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001123 +705bonus pay for amazing work on #OSS 00010001123 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001124 +705bonus pay for amazing work on #OSS 00010001124 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001125 +705bonus pay for amazing work on #OSS 00010001125 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001126 +705bonus pay for amazing work on #OSS 00010001126 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001127 +705bonus pay for amazing work on #OSS 00010001127 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001128 +705bonus pay for amazing work on #OSS 00010001128 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001129 +705bonus pay for amazing work on #OSS 00010001129 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001130 +705bonus pay for amazing work on #OSS 00010001130 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001131 +705bonus pay for amazing work on #OSS 00010001131 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001132 +705bonus pay for amazing work on #OSS 00010001132 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001133 +705bonus pay for amazing work on #OSS 00010001133 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001134 +705bonus pay for amazing work on #OSS 00010001134 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001135 +705bonus pay for amazing work on #OSS 00010001135 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001136 +705bonus pay for amazing work on #OSS 00010001136 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001137 +705bonus pay for amazing work on #OSS 00010001137 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001138 +705bonus pay for amazing work on #OSS 00010001138 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001139 +705bonus pay for amazing work on #OSS 00010001139 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001140 +705bonus pay for amazing work on #OSS 00010001140 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001141 +705bonus pay for amazing work on #OSS 00010001141 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001142 +705bonus pay for amazing work on #OSS 00010001142 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001143 +705bonus pay for amazing work on #OSS 00010001143 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001144 +705bonus pay for amazing work on #OSS 00010001144 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001145 +705bonus pay for amazing work on #OSS 00010001145 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001146 +705bonus pay for amazing work on #OSS 00010001146 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001147 +705bonus pay for amazing work on #OSS 00010001147 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001148 +705bonus pay for amazing work on #OSS 00010001148 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001149 +705bonus pay for amazing work on #OSS 00010001149 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001150 +705bonus pay for amazing work on #OSS 00010001150 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001151 +705bonus pay for amazing work on #OSS 00010001151 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001152 +705bonus pay for amazing work on #OSS 00010001152 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001153 +705bonus pay for amazing work on #OSS 00010001153 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001154 +705bonus pay for amazing work on #OSS 00010001154 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001155 +705bonus pay for amazing work on #OSS 00010001155 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001156 +705bonus pay for amazing work on #OSS 00010001156 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001157 +705bonus pay for amazing work on #OSS 00010001157 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001158 +705bonus pay for amazing work on #OSS 00010001158 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001159 +705bonus pay for amazing work on #OSS 00010001159 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001160 +705bonus pay for amazing work on #OSS 00010001160 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001161 +705bonus pay for amazing work on #OSS 00010001161 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001162 +705bonus pay for amazing work on #OSS 00010001162 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001163 +705bonus pay for amazing work on #OSS 00010001163 +62223138010481967038518 0000100000#cZEuVnhgitvJO#Lily Martin 1121042880001164 +705bonus pay for amazing work on #OSS 00010001164 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001165 +705bonus pay for amazing work on #OSS 00010001165 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001166 +705bonus pay for amazing work on #OSS 00010001166 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001167 +705bonus pay for amazing work on #OSS 00010001167 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001168 +705bonus pay for amazing work on #OSS 00010001168 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001169 +705bonus pay for amazing work on #OSS 00010001169 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001170 +705bonus pay for amazing work on #OSS 00010001170 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001171 +705bonus pay for amazing work on #OSS 00010001171 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001172 +705bonus pay for amazing work on #OSS 00010001172 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001173 +705bonus pay for amazing work on #OSS 00010001173 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001174 +705bonus pay for amazing work on #OSS 00010001174 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001175 +705bonus pay for amazing work on #OSS 00010001175 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001176 +705bonus pay for amazing work on #OSS 00010001176 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001177 +705bonus pay for amazing work on #OSS 00010001177 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001178 +705bonus pay for amazing work on #OSS 00010001178 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001179 +705bonus pay for amazing work on #OSS 00010001179 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001180 +705bonus pay for amazing work on #OSS 00010001180 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001181 +705bonus pay for amazing work on #OSS 00010001181 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001182 +705bonus pay for amazing work on #OSS 00010001182 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001183 +705bonus pay for amazing work on #OSS 00010001183 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001184 +705bonus pay for amazing work on #OSS 00010001184 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001185 +705bonus pay for amazing work on #OSS 00010001185 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001186 +705bonus pay for amazing work on #OSS 00010001186 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001187 +705bonus pay for amazing work on #OSS 00010001187 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001188 +705bonus pay for amazing work on #OSS 00010001188 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001189 +705bonus pay for amazing work on #OSS 00010001189 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001190 +705bonus pay for amazing work on #OSS 00010001190 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001191 +705bonus pay for amazing work on #OSS 00010001191 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001192 +705bonus pay for amazing work on #OSS 00010001192 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001193 +705bonus pay for amazing work on #OSS 00010001193 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001194 +705bonus pay for amazing work on #OSS 00010001194 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001195 +705bonus pay for amazing work on #OSS 00010001195 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001196 +705bonus pay for amazing work on #OSS 00010001196 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001197 +705bonus pay for amazing work on #OSS 00010001197 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001198 +705bonus pay for amazing work on #OSS 00010001198 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001199 +705bonus pay for amazing work on #OSS 00010001199 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001200 +705bonus pay for amazing work on #OSS 00010001200 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001201 +705bonus pay for amazing work on #OSS 00010001201 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001202 +705bonus pay for amazing work on #OSS 00010001202 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001203 +705bonus pay for amazing work on #OSS 00010001203 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001204 +705bonus pay for amazing work on #OSS 00010001204 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001205 +705bonus pay for amazing work on #OSS 00010001205 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001206 +705bonus pay for amazing work on #OSS 00010001206 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001207 +705bonus pay for amazing work on #OSS 00010001207 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001208 +705bonus pay for amazing work on #OSS 00010001208 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001209 +705bonus pay for amazing work on #OSS 00010001209 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001210 +705bonus pay for amazing work on #OSS 00010001210 +62223138010481967038518 0000100000#cC4Vn4Ifx1ADV#Ethan Williams 1121042880001211 +705bonus pay for amazing work on #OSS 00010001211 +62223138010481967038518 0000100000#3B78wXZgkf36m#Ethan Martin 1121042880001212 +705bonus pay for amazing work on #OSS 00010001212 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001213 +705bonus pay for amazing work on #OSS 00010001213 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001214 +705bonus pay for amazing work on #OSS 00010001214 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001215 +705bonus pay for amazing work on #OSS 00010001215 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001216 +705bonus pay for amazing work on #OSS 00010001216 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001217 +705bonus pay for amazing work on #OSS 00010001217 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001218 +705bonus pay for amazing work on #OSS 00010001218 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001219 +705bonus pay for amazing work on #OSS 00010001219 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001220 +705bonus pay for amazing work on #OSS 00010001220 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001221 +705bonus pay for amazing work on #OSS 00010001221 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001222 +705bonus pay for amazing work on #OSS 00010001222 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001223 +705bonus pay for amazing work on #OSS 00010001223 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001224 +705bonus pay for amazing work on #OSS 00010001224 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001225 +705bonus pay for amazing work on #OSS 00010001225 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001226 +705bonus pay for amazing work on #OSS 00010001226 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001227 +705bonus pay for amazing work on #OSS 00010001227 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001228 +705bonus pay for amazing work on #OSS 00010001228 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001229 +705bonus pay for amazing work on #OSS 00010001229 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001230 +705bonus pay for amazing work on #OSS 00010001230 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001231 +705bonus pay for amazing work on #OSS 00010001231 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001232 +705bonus pay for amazing work on #OSS 00010001232 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001233 +705bonus pay for amazing work on #OSS 00010001233 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001234 +705bonus pay for amazing work on #OSS 00010001234 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001235 +705bonus pay for amazing work on #OSS 00010001235 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001236 +705bonus pay for amazing work on #OSS 00010001236 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001237 +705bonus pay for amazing work on #OSS 00010001237 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001238 +705bonus pay for amazing work on #OSS 00010001238 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001239 +705bonus pay for amazing work on #OSS 00010001239 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001240 +705bonus pay for amazing work on #OSS 00010001240 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001241 +705bonus pay for amazing work on #OSS 00010001241 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001242 +705bonus pay for amazing work on #OSS 00010001242 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001243 +705bonus pay for amazing work on #OSS 00010001243 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001244 +705bonus pay for amazing work on #OSS 00010001244 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001245 +705bonus pay for amazing work on #OSS 00010001245 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001246 +705bonus pay for amazing work on #OSS 00010001246 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001247 +705bonus pay for amazing work on #OSS 00010001247 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001248 +705bonus pay for amazing work on #OSS 00010001248 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001249 +705bonus pay for amazing work on #OSS 00010001249 +62223138010481967038518 0000100000#3B78wXZgkf36m#Lily Martin 1121042880001250 +705bonus pay for amazing work on #OSS 00010001250 +82000025008922512500000000000000000125000000121042882 121042880000003 +5200Wells Fargo 121042882 PPDTrans. Des 180511 0121042880000004 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000001 +705bonus pay for amazing work on #OSS 00010000001 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000002 +705bonus pay for amazing work on #OSS 00010000002 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000003 +705bonus pay for amazing work on #OSS 00010000003 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000004 +705bonus pay for amazing work on #OSS 00010000004 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000005 +705bonus pay for amazing work on #OSS 00010000005 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000006 +705bonus pay for amazing work on #OSS 00010000006 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000007 +705bonus pay for amazing work on #OSS 00010000007 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000008 +705bonus pay for amazing work on #OSS 00010000008 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000009 +705bonus pay for amazing work on #OSS 00010000009 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000010 +705bonus pay for amazing work on #OSS 00010000010 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000011 +705bonus pay for amazing work on #OSS 00010000011 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000012 +705bonus pay for amazing work on #OSS 00010000012 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000013 +705bonus pay for amazing work on #OSS 00010000013 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000014 +705bonus pay for amazing work on #OSS 00010000014 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000015 +705bonus pay for amazing work on #OSS 00010000015 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000016 +705bonus pay for amazing work on #OSS 00010000016 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000017 +705bonus pay for amazing work on #OSS 00010000017 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000018 +705bonus pay for amazing work on #OSS 00010000018 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000019 +705bonus pay for amazing work on #OSS 00010000019 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000020 +705bonus pay for amazing work on #OSS 00010000020 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000021 +705bonus pay for amazing work on #OSS 00010000021 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000022 +705bonus pay for amazing work on #OSS 00010000022 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000023 +705bonus pay for amazing work on #OSS 00010000023 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000024 +705bonus pay for amazing work on #OSS 00010000024 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000025 +705bonus pay for amazing work on #OSS 00010000025 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000026 +705bonus pay for amazing work on #OSS 00010000026 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000027 +705bonus pay for amazing work on #OSS 00010000027 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000028 +705bonus pay for amazing work on #OSS 00010000028 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000029 +705bonus pay for amazing work on #OSS 00010000029 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000030 +705bonus pay for amazing work on #OSS 00010000030 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000031 +705bonus pay for amazing work on #OSS 00010000031 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000032 +705bonus pay for amazing work on #OSS 00010000032 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000033 +705bonus pay for amazing work on #OSS 00010000033 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000034 +705bonus pay for amazing work on #OSS 00010000034 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000035 +705bonus pay for amazing work on #OSS 00010000035 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000036 +705bonus pay for amazing work on #OSS 00010000036 +62223138010481967038518 0000100000#W12MwGwKDYYde#Elizabeth Taylor 1121042880000037 +705bonus pay for amazing work on #OSS 00010000037 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Elizabeth Robinson 1121042880000038 +705bonus pay for amazing work on #OSS 00010000038 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000039 +705bonus pay for amazing work on #OSS 00010000039 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000040 +705bonus pay for amazing work on #OSS 00010000040 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000041 +705bonus pay for amazing work on #OSS 00010000041 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000042 +705bonus pay for amazing work on #OSS 00010000042 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000043 +705bonus pay for amazing work on #OSS 00010000043 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000044 +705bonus pay for amazing work on #OSS 00010000044 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000045 +705bonus pay for amazing work on #OSS 00010000045 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000046 +705bonus pay for amazing work on #OSS 00010000046 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000047 +705bonus pay for amazing work on #OSS 00010000047 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000048 +705bonus pay for amazing work on #OSS 00010000048 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000049 +705bonus pay for amazing work on #OSS 00010000049 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000050 +705bonus pay for amazing work on #OSS 00010000050 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000051 +705bonus pay for amazing work on #OSS 00010000051 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000052 +705bonus pay for amazing work on #OSS 00010000052 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000053 +705bonus pay for amazing work on #OSS 00010000053 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000054 +705bonus pay for amazing work on #OSS 00010000054 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000055 +705bonus pay for amazing work on #OSS 00010000055 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000056 +705bonus pay for amazing work on #OSS 00010000056 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000057 +705bonus pay for amazing work on #OSS 00010000057 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000058 +705bonus pay for amazing work on #OSS 00010000058 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000059 +705bonus pay for amazing work on #OSS 00010000059 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000060 +705bonus pay for amazing work on #OSS 00010000060 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000061 +705bonus pay for amazing work on #OSS 00010000061 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000062 +705bonus pay for amazing work on #OSS 00010000062 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000063 +705bonus pay for amazing work on #OSS 00010000063 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000064 +705bonus pay for amazing work on #OSS 00010000064 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000065 +705bonus pay for amazing work on #OSS 00010000065 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000066 +705bonus pay for amazing work on #OSS 00010000066 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000067 +705bonus pay for amazing work on #OSS 00010000067 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000068 +705bonus pay for amazing work on #OSS 00010000068 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000069 +705bonus pay for amazing work on #OSS 00010000069 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000070 +705bonus pay for amazing work on #OSS 00010000070 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000071 +705bonus pay for amazing work on #OSS 00010000071 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000072 +705bonus pay for amazing work on #OSS 00010000072 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000073 +705bonus pay for amazing work on #OSS 00010000073 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000074 +705bonus pay for amazing work on #OSS 00010000074 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000075 +705bonus pay for amazing work on #OSS 00010000075 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000076 +705bonus pay for amazing work on #OSS 00010000076 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000077 +705bonus pay for amazing work on #OSS 00010000077 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000078 +705bonus pay for amazing work on #OSS 00010000078 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000079 +705bonus pay for amazing work on #OSS 00010000079 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000080 +705bonus pay for amazing work on #OSS 00010000080 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000081 +705bonus pay for amazing work on #OSS 00010000081 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000082 +705bonus pay for amazing work on #OSS 00010000082 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000083 +705bonus pay for amazing work on #OSS 00010000083 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000084 +705bonus pay for amazing work on #OSS 00010000084 +62223138010481967038518 0000100000#lWUA5eUgxQmCe#Zoey Robinson 1121042880000085 +705bonus pay for amazing work on #OSS 00010000085 +62223138010481967038518 0000100000#GOxlEZaHxO64c#Charlotte Martinez 1121042880000086 +705bonus pay for amazing work on #OSS 00010000086 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000087 +705bonus pay for amazing work on #OSS 00010000087 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000088 +705bonus pay for amazing work on #OSS 00010000088 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000089 +705bonus pay for amazing work on #OSS 00010000089 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000090 +705bonus pay for amazing work on #OSS 00010000090 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000091 +705bonus pay for amazing work on #OSS 00010000091 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000092 +705bonus pay for amazing work on #OSS 00010000092 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000093 +705bonus pay for amazing work on #OSS 00010000093 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000094 +705bonus pay for amazing work on #OSS 00010000094 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000095 +705bonus pay for amazing work on #OSS 00010000095 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000096 +705bonus pay for amazing work on #OSS 00010000096 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000097 +705bonus pay for amazing work on #OSS 00010000097 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000098 +705bonus pay for amazing work on #OSS 00010000098 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000099 +705bonus pay for amazing work on #OSS 00010000099 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000100 +705bonus pay for amazing work on #OSS 00010000100 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000101 +705bonus pay for amazing work on #OSS 00010000101 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000102 +705bonus pay for amazing work on #OSS 00010000102 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000103 +705bonus pay for amazing work on #OSS 00010000103 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000104 +705bonus pay for amazing work on #OSS 00010000104 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000105 +705bonus pay for amazing work on #OSS 00010000105 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000106 +705bonus pay for amazing work on #OSS 00010000106 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000107 +705bonus pay for amazing work on #OSS 00010000107 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000108 +705bonus pay for amazing work on #OSS 00010000108 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000109 +705bonus pay for amazing work on #OSS 00010000109 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000110 +705bonus pay for amazing work on #OSS 00010000110 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000111 +705bonus pay for amazing work on #OSS 00010000111 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000112 +705bonus pay for amazing work on #OSS 00010000112 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000113 +705bonus pay for amazing work on #OSS 00010000113 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000114 +705bonus pay for amazing work on #OSS 00010000114 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000115 +705bonus pay for amazing work on #OSS 00010000115 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000116 +705bonus pay for amazing work on #OSS 00010000116 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000117 +705bonus pay for amazing work on #OSS 00010000117 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000118 +705bonus pay for amazing work on #OSS 00010000118 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000119 +705bonus pay for amazing work on #OSS 00010000119 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000120 +705bonus pay for amazing work on #OSS 00010000120 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000121 +705bonus pay for amazing work on #OSS 00010000121 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000122 +705bonus pay for amazing work on #OSS 00010000122 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000123 +705bonus pay for amazing work on #OSS 00010000123 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000124 +705bonus pay for amazing work on #OSS 00010000124 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000125 +705bonus pay for amazing work on #OSS 00010000125 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000126 +705bonus pay for amazing work on #OSS 00010000126 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000127 +705bonus pay for amazing work on #OSS 00010000127 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000128 +705bonus pay for amazing work on #OSS 00010000128 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000129 +705bonus pay for amazing work on #OSS 00010000129 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000130 +705bonus pay for amazing work on #OSS 00010000130 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000131 +705bonus pay for amazing work on #OSS 00010000131 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000132 +705bonus pay for amazing work on #OSS 00010000132 +62223138010481967038518 0000100000#GOxlEZaHxO64c#David Martinez 1121042880000133 +705bonus pay for amazing work on #OSS 00010000133 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000134 +705bonus pay for amazing work on #OSS 00010000134 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000135 +705bonus pay for amazing work on #OSS 00010000135 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000136 +705bonus pay for amazing work on #OSS 00010000136 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000137 +705bonus pay for amazing work on #OSS 00010000137 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000138 +705bonus pay for amazing work on #OSS 00010000138 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000139 +705bonus pay for amazing work on #OSS 00010000139 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000140 +705bonus pay for amazing work on #OSS 00010000140 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000141 +705bonus pay for amazing work on #OSS 00010000141 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000142 +705bonus pay for amazing work on #OSS 00010000142 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000143 +705bonus pay for amazing work on #OSS 00010000143 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000144 +705bonus pay for amazing work on #OSS 00010000144 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000145 +705bonus pay for amazing work on #OSS 00010000145 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000146 +705bonus pay for amazing work on #OSS 00010000146 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000147 +705bonus pay for amazing work on #OSS 00010000147 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000148 +705bonus pay for amazing work on #OSS 00010000148 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000149 +705bonus pay for amazing work on #OSS 00010000149 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000150 +705bonus pay for amazing work on #OSS 00010000150 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000151 +705bonus pay for amazing work on #OSS 00010000151 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000152 +705bonus pay for amazing work on #OSS 00010000152 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000153 +705bonus pay for amazing work on #OSS 00010000153 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000154 +705bonus pay for amazing work on #OSS 00010000154 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000155 +705bonus pay for amazing work on #OSS 00010000155 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000156 +705bonus pay for amazing work on #OSS 00010000156 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000157 +705bonus pay for amazing work on #OSS 00010000157 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000158 +705bonus pay for amazing work on #OSS 00010000158 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000159 +705bonus pay for amazing work on #OSS 00010000159 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000160 +705bonus pay for amazing work on #OSS 00010000160 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000161 +705bonus pay for amazing work on #OSS 00010000161 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000162 +705bonus pay for amazing work on #OSS 00010000162 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000163 +705bonus pay for amazing work on #OSS 00010000163 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000164 +705bonus pay for amazing work on #OSS 00010000164 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000165 +705bonus pay for amazing work on #OSS 00010000165 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000166 +705bonus pay for amazing work on #OSS 00010000166 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000167 +705bonus pay for amazing work on #OSS 00010000167 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000168 +705bonus pay for amazing work on #OSS 00010000168 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000169 +705bonus pay for amazing work on #OSS 00010000169 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000170 +705bonus pay for amazing work on #OSS 00010000170 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000171 +705bonus pay for amazing work on #OSS 00010000171 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000172 +705bonus pay for amazing work on #OSS 00010000172 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000173 +705bonus pay for amazing work on #OSS 00010000173 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000174 +705bonus pay for amazing work on #OSS 00010000174 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000175 +705bonus pay for amazing work on #OSS 00010000175 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000176 +705bonus pay for amazing work on #OSS 00010000176 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000177 +705bonus pay for amazing work on #OSS 00010000177 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000178 +705bonus pay for amazing work on #OSS 00010000178 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000179 +705bonus pay for amazing work on #OSS 00010000179 +62223138010481967038518 0000100000#7RCLaiwWKDjQi#Mia Wilson 1121042880000180 +705bonus pay for amazing work on #OSS 00010000180 +62223138010481967038518 0000100000#2Me863oQpY2ul#Mia Robinson 1121042880000181 +705bonus pay for amazing work on #OSS 00010000181 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000182 +705bonus pay for amazing work on #OSS 00010000182 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000183 +705bonus pay for amazing work on #OSS 00010000183 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000184 +705bonus pay for amazing work on #OSS 00010000184 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000185 +705bonus pay for amazing work on #OSS 00010000185 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000186 +705bonus pay for amazing work on #OSS 00010000186 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000187 +705bonus pay for amazing work on #OSS 00010000187 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000188 +705bonus pay for amazing work on #OSS 00010000188 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000189 +705bonus pay for amazing work on #OSS 00010000189 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000190 +705bonus pay for amazing work on #OSS 00010000190 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000191 +705bonus pay for amazing work on #OSS 00010000191 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000192 +705bonus pay for amazing work on #OSS 00010000192 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000193 +705bonus pay for amazing work on #OSS 00010000193 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000194 +705bonus pay for amazing work on #OSS 00010000194 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000195 +705bonus pay for amazing work on #OSS 00010000195 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000196 +705bonus pay for amazing work on #OSS 00010000196 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000197 +705bonus pay for amazing work on #OSS 00010000197 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000198 +705bonus pay for amazing work on #OSS 00010000198 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000199 +705bonus pay for amazing work on #OSS 00010000199 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000200 +705bonus pay for amazing work on #OSS 00010000200 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000201 +705bonus pay for amazing work on #OSS 00010000201 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000202 +705bonus pay for amazing work on #OSS 00010000202 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000203 +705bonus pay for amazing work on #OSS 00010000203 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000204 +705bonus pay for amazing work on #OSS 00010000204 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000205 +705bonus pay for amazing work on #OSS 00010000205 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000206 +705bonus pay for amazing work on #OSS 00010000206 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000207 +705bonus pay for amazing work on #OSS 00010000207 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000208 +705bonus pay for amazing work on #OSS 00010000208 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000209 +705bonus pay for amazing work on #OSS 00010000209 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000210 +705bonus pay for amazing work on #OSS 00010000210 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000211 +705bonus pay for amazing work on #OSS 00010000211 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000212 +705bonus pay for amazing work on #OSS 00010000212 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000213 +705bonus pay for amazing work on #OSS 00010000213 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000214 +705bonus pay for amazing work on #OSS 00010000214 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000215 +705bonus pay for amazing work on #OSS 00010000215 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000216 +705bonus pay for amazing work on #OSS 00010000216 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000217 +705bonus pay for amazing work on #OSS 00010000217 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000218 +705bonus pay for amazing work on #OSS 00010000218 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000219 +705bonus pay for amazing work on #OSS 00010000219 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000220 +705bonus pay for amazing work on #OSS 00010000220 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000221 +705bonus pay for amazing work on #OSS 00010000221 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000222 +705bonus pay for amazing work on #OSS 00010000222 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000223 +705bonus pay for amazing work on #OSS 00010000223 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000224 +705bonus pay for amazing work on #OSS 00010000224 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000225 +705bonus pay for amazing work on #OSS 00010000225 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000226 +705bonus pay for amazing work on #OSS 00010000226 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000227 +705bonus pay for amazing work on #OSS 00010000227 +62223138010481967038518 0000100000#2Me863oQpY2ul#Zoey Robinson 1121042880000228 +705bonus pay for amazing work on #OSS 00010000228 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000229 +705bonus pay for amazing work on #OSS 00010000229 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000230 +705bonus pay for amazing work on #OSS 00010000230 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000231 +705bonus pay for amazing work on #OSS 00010000231 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000232 +705bonus pay for amazing work on #OSS 00010000232 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000233 +705bonus pay for amazing work on #OSS 00010000233 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000234 +705bonus pay for amazing work on #OSS 00010000234 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000235 +705bonus pay for amazing work on #OSS 00010000235 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000236 +705bonus pay for amazing work on #OSS 00010000236 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000237 +705bonus pay for amazing work on #OSS 00010000237 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000238 +705bonus pay for amazing work on #OSS 00010000238 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000239 +705bonus pay for amazing work on #OSS 00010000239 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000240 +705bonus pay for amazing work on #OSS 00010000240 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000241 +705bonus pay for amazing work on #OSS 00010000241 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000242 +705bonus pay for amazing work on #OSS 00010000242 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000243 +705bonus pay for amazing work on #OSS 00010000243 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000244 +705bonus pay for amazing work on #OSS 00010000244 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000245 +705bonus pay for amazing work on #OSS 00010000245 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000246 +705bonus pay for amazing work on #OSS 00010000246 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000247 +705bonus pay for amazing work on #OSS 00010000247 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000248 +705bonus pay for amazing work on #OSS 00010000248 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000249 +705bonus pay for amazing work on #OSS 00010000249 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000250 +705bonus pay for amazing work on #OSS 00010000250 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000251 +705bonus pay for amazing work on #OSS 00010000251 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000252 +705bonus pay for amazing work on #OSS 00010000252 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000253 +705bonus pay for amazing work on #OSS 00010000253 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000254 +705bonus pay for amazing work on #OSS 00010000254 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000255 +705bonus pay for amazing work on #OSS 00010000255 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000256 +705bonus pay for amazing work on #OSS 00010000256 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000257 +705bonus pay for amazing work on #OSS 00010000257 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000258 +705bonus pay for amazing work on #OSS 00010000258 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000259 +705bonus pay for amazing work on #OSS 00010000259 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000260 +705bonus pay for amazing work on #OSS 00010000260 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000261 +705bonus pay for amazing work on #OSS 00010000261 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000262 +705bonus pay for amazing work on #OSS 00010000262 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000263 +705bonus pay for amazing work on #OSS 00010000263 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000264 +705bonus pay for amazing work on #OSS 00010000264 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000265 +705bonus pay for amazing work on #OSS 00010000265 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000266 +705bonus pay for amazing work on #OSS 00010000266 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000267 +705bonus pay for amazing work on #OSS 00010000267 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000268 +705bonus pay for amazing work on #OSS 00010000268 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000269 +705bonus pay for amazing work on #OSS 00010000269 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000270 +705bonus pay for amazing work on #OSS 00010000270 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000271 +705bonus pay for amazing work on #OSS 00010000271 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000272 +705bonus pay for amazing work on #OSS 00010000272 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000273 +705bonus pay for amazing work on #OSS 00010000273 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000274 +705bonus pay for amazing work on #OSS 00010000274 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000275 +705bonus pay for amazing work on #OSS 00010000275 +62223138010481967038518 0000100000#zMFKJLdQL271e#Emma Johnson 1121042880000276 +705bonus pay for amazing work on #OSS 00010000276 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000277 +705bonus pay for amazing work on #OSS 00010000277 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000278 +705bonus pay for amazing work on #OSS 00010000278 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000279 +705bonus pay for amazing work on #OSS 00010000279 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000280 +705bonus pay for amazing work on #OSS 00010000280 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000281 +705bonus pay for amazing work on #OSS 00010000281 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000282 +705bonus pay for amazing work on #OSS 00010000282 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000283 +705bonus pay for amazing work on #OSS 00010000283 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000284 +705bonus pay for amazing work on #OSS 00010000284 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000285 +705bonus pay for amazing work on #OSS 00010000285 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000286 +705bonus pay for amazing work on #OSS 00010000286 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000287 +705bonus pay for amazing work on #OSS 00010000287 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000288 +705bonus pay for amazing work on #OSS 00010000288 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000289 +705bonus pay for amazing work on #OSS 00010000289 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000290 +705bonus pay for amazing work on #OSS 00010000290 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000291 +705bonus pay for amazing work on #OSS 00010000291 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000292 +705bonus pay for amazing work on #OSS 00010000292 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000293 +705bonus pay for amazing work on #OSS 00010000293 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000294 +705bonus pay for amazing work on #OSS 00010000294 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000295 +705bonus pay for amazing work on #OSS 00010000295 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000296 +705bonus pay for amazing work on #OSS 00010000296 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000297 +705bonus pay for amazing work on #OSS 00010000297 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000298 +705bonus pay for amazing work on #OSS 00010000298 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000299 +705bonus pay for amazing work on #OSS 00010000299 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000300 +705bonus pay for amazing work on #OSS 00010000300 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000301 +705bonus pay for amazing work on #OSS 00010000301 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000302 +705bonus pay for amazing work on #OSS 00010000302 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000303 +705bonus pay for amazing work on #OSS 00010000303 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000304 +705bonus pay for amazing work on #OSS 00010000304 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000305 +705bonus pay for amazing work on #OSS 00010000305 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000306 +705bonus pay for amazing work on #OSS 00010000306 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000307 +705bonus pay for amazing work on #OSS 00010000307 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000308 +705bonus pay for amazing work on #OSS 00010000308 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000309 +705bonus pay for amazing work on #OSS 00010000309 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000310 +705bonus pay for amazing work on #OSS 00010000310 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000311 +705bonus pay for amazing work on #OSS 00010000311 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000312 +705bonus pay for amazing work on #OSS 00010000312 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000313 +705bonus pay for amazing work on #OSS 00010000313 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000314 +705bonus pay for amazing work on #OSS 00010000314 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000315 +705bonus pay for amazing work on #OSS 00010000315 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000316 +705bonus pay for amazing work on #OSS 00010000316 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000317 +705bonus pay for amazing work on #OSS 00010000317 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000318 +705bonus pay for amazing work on #OSS 00010000318 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000319 +705bonus pay for amazing work on #OSS 00010000319 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000320 +705bonus pay for amazing work on #OSS 00010000320 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000321 +705bonus pay for amazing work on #OSS 00010000321 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000322 +705bonus pay for amazing work on #OSS 00010000322 +62223138010481967038518 0000100000#Xtl8scHijS8Dd#Zoey Robinson 1121042880000323 +705bonus pay for amazing work on #OSS 00010000323 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Zoey Williams 1121042880000324 +705bonus pay for amazing work on #OSS 00010000324 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000325 +705bonus pay for amazing work on #OSS 00010000325 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000326 +705bonus pay for amazing work on #OSS 00010000326 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000327 +705bonus pay for amazing work on #OSS 00010000327 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000328 +705bonus pay for amazing work on #OSS 00010000328 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000329 +705bonus pay for amazing work on #OSS 00010000329 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000330 +705bonus pay for amazing work on #OSS 00010000330 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000331 +705bonus pay for amazing work on #OSS 00010000331 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000332 +705bonus pay for amazing work on #OSS 00010000332 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000333 +705bonus pay for amazing work on #OSS 00010000333 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000334 +705bonus pay for amazing work on #OSS 00010000334 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000335 +705bonus pay for amazing work on #OSS 00010000335 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000336 +705bonus pay for amazing work on #OSS 00010000336 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000337 +705bonus pay for amazing work on #OSS 00010000337 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000338 +705bonus pay for amazing work on #OSS 00010000338 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000339 +705bonus pay for amazing work on #OSS 00010000339 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000340 +705bonus pay for amazing work on #OSS 00010000340 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000341 +705bonus pay for amazing work on #OSS 00010000341 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000342 +705bonus pay for amazing work on #OSS 00010000342 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000343 +705bonus pay for amazing work on #OSS 00010000343 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000344 +705bonus pay for amazing work on #OSS 00010000344 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000345 +705bonus pay for amazing work on #OSS 00010000345 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000346 +705bonus pay for amazing work on #OSS 00010000346 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000347 +705bonus pay for amazing work on #OSS 00010000347 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000348 +705bonus pay for amazing work on #OSS 00010000348 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000349 +705bonus pay for amazing work on #OSS 00010000349 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000350 +705bonus pay for amazing work on #OSS 00010000350 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000351 +705bonus pay for amazing work on #OSS 00010000351 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000352 +705bonus pay for amazing work on #OSS 00010000352 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000353 +705bonus pay for amazing work on #OSS 00010000353 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000354 +705bonus pay for amazing work on #OSS 00010000354 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000355 +705bonus pay for amazing work on #OSS 00010000355 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000356 +705bonus pay for amazing work on #OSS 00010000356 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000357 +705bonus pay for amazing work on #OSS 00010000357 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000358 +705bonus pay for amazing work on #OSS 00010000358 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000359 +705bonus pay for amazing work on #OSS 00010000359 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000360 +705bonus pay for amazing work on #OSS 00010000360 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000361 +705bonus pay for amazing work on #OSS 00010000361 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000362 +705bonus pay for amazing work on #OSS 00010000362 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000363 +705bonus pay for amazing work on #OSS 00010000363 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000364 +705bonus pay for amazing work on #OSS 00010000364 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000365 +705bonus pay for amazing work on #OSS 00010000365 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000366 +705bonus pay for amazing work on #OSS 00010000366 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000367 +705bonus pay for amazing work on #OSS 00010000367 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000368 +705bonus pay for amazing work on #OSS 00010000368 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000369 +705bonus pay for amazing work on #OSS 00010000369 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000370 +705bonus pay for amazing work on #OSS 00010000370 +62223138010481967038518 0000100000#ncLT3DyhZtsIm#Ethan Williams 1121042880000371 +705bonus pay for amazing work on #OSS 00010000371 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000372 +705bonus pay for amazing work on #OSS 00010000372 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000373 +705bonus pay for amazing work on #OSS 00010000373 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000374 +705bonus pay for amazing work on #OSS 00010000374 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000375 +705bonus pay for amazing work on #OSS 00010000375 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000376 +705bonus pay for amazing work on #OSS 00010000376 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000377 +705bonus pay for amazing work on #OSS 00010000377 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000378 +705bonus pay for amazing work on #OSS 00010000378 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000379 +705bonus pay for amazing work on #OSS 00010000379 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000380 +705bonus pay for amazing work on #OSS 00010000380 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000381 +705bonus pay for amazing work on #OSS 00010000381 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000382 +705bonus pay for amazing work on #OSS 00010000382 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000383 +705bonus pay for amazing work on #OSS 00010000383 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000384 +705bonus pay for amazing work on #OSS 00010000384 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000385 +705bonus pay for amazing work on #OSS 00010000385 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000386 +705bonus pay for amazing work on #OSS 00010000386 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000387 +705bonus pay for amazing work on #OSS 00010000387 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000388 +705bonus pay for amazing work on #OSS 00010000388 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000389 +705bonus pay for amazing work on #OSS 00010000389 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000390 +705bonus pay for amazing work on #OSS 00010000390 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000391 +705bonus pay for amazing work on #OSS 00010000391 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000392 +705bonus pay for amazing work on #OSS 00010000392 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000393 +705bonus pay for amazing work on #OSS 00010000393 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000394 +705bonus pay for amazing work on #OSS 00010000394 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000395 +705bonus pay for amazing work on #OSS 00010000395 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000396 +705bonus pay for amazing work on #OSS 00010000396 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000397 +705bonus pay for amazing work on #OSS 00010000397 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000398 +705bonus pay for amazing work on #OSS 00010000398 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000399 +705bonus pay for amazing work on #OSS 00010000399 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000400 +705bonus pay for amazing work on #OSS 00010000400 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000401 +705bonus pay for amazing work on #OSS 00010000401 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000402 +705bonus pay for amazing work on #OSS 00010000402 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000403 +705bonus pay for amazing work on #OSS 00010000403 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000404 +705bonus pay for amazing work on #OSS 00010000404 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000405 +705bonus pay for amazing work on #OSS 00010000405 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000406 +705bonus pay for amazing work on #OSS 00010000406 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000407 +705bonus pay for amazing work on #OSS 00010000407 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000408 +705bonus pay for amazing work on #OSS 00010000408 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000409 +705bonus pay for amazing work on #OSS 00010000409 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000410 +705bonus pay for amazing work on #OSS 00010000410 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000411 +705bonus pay for amazing work on #OSS 00010000411 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000412 +705bonus pay for amazing work on #OSS 00010000412 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000413 +705bonus pay for amazing work on #OSS 00010000413 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000414 +705bonus pay for amazing work on #OSS 00010000414 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000415 +705bonus pay for amazing work on #OSS 00010000415 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000416 +705bonus pay for amazing work on #OSS 00010000416 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000417 +705bonus pay for amazing work on #OSS 00010000417 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000418 +705bonus pay for amazing work on #OSS 00010000418 +62223138010481967038518 0000100000#GX7NcgJ905vCC#Mia Wilson 1121042880000419 +705bonus pay for amazing work on #OSS 00010000419 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000420 +705bonus pay for amazing work on #OSS 00010000420 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000421 +705bonus pay for amazing work on #OSS 00010000421 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000422 +705bonus pay for amazing work on #OSS 00010000422 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000423 +705bonus pay for amazing work on #OSS 00010000423 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000424 +705bonus pay for amazing work on #OSS 00010000424 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000425 +705bonus pay for amazing work on #OSS 00010000425 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000426 +705bonus pay for amazing work on #OSS 00010000426 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000427 +705bonus pay for amazing work on #OSS 00010000427 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000428 +705bonus pay for amazing work on #OSS 00010000428 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000429 +705bonus pay for amazing work on #OSS 00010000429 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000430 +705bonus pay for amazing work on #OSS 00010000430 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000431 +705bonus pay for amazing work on #OSS 00010000431 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000432 +705bonus pay for amazing work on #OSS 00010000432 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000433 +705bonus pay for amazing work on #OSS 00010000433 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000434 +705bonus pay for amazing work on #OSS 00010000434 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000435 +705bonus pay for amazing work on #OSS 00010000435 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000436 +705bonus pay for amazing work on #OSS 00010000436 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000437 +705bonus pay for amazing work on #OSS 00010000437 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000438 +705bonus pay for amazing work on #OSS 00010000438 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000439 +705bonus pay for amazing work on #OSS 00010000439 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000440 +705bonus pay for amazing work on #OSS 00010000440 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000441 +705bonus pay for amazing work on #OSS 00010000441 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000442 +705bonus pay for amazing work on #OSS 00010000442 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000443 +705bonus pay for amazing work on #OSS 00010000443 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000444 +705bonus pay for amazing work on #OSS 00010000444 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000445 +705bonus pay for amazing work on #OSS 00010000445 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000446 +705bonus pay for amazing work on #OSS 00010000446 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000447 +705bonus pay for amazing work on #OSS 00010000447 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000448 +705bonus pay for amazing work on #OSS 00010000448 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000449 +705bonus pay for amazing work on #OSS 00010000449 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000450 +705bonus pay for amazing work on #OSS 00010000450 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000451 +705bonus pay for amazing work on #OSS 00010000451 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000452 +705bonus pay for amazing work on #OSS 00010000452 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000453 +705bonus pay for amazing work on #OSS 00010000453 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000454 +705bonus pay for amazing work on #OSS 00010000454 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000455 +705bonus pay for amazing work on #OSS 00010000455 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000456 +705bonus pay for amazing work on #OSS 00010000456 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000457 +705bonus pay for amazing work on #OSS 00010000457 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000458 +705bonus pay for amazing work on #OSS 00010000458 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000459 +705bonus pay for amazing work on #OSS 00010000459 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000460 +705bonus pay for amazing work on #OSS 00010000460 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000461 +705bonus pay for amazing work on #OSS 00010000461 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000462 +705bonus pay for amazing work on #OSS 00010000462 +62223138010481967038518 0000100000#JhJSepp0trPSL#Zoey Robinson 1121042880000463 +705bonus pay for amazing work on #OSS 00010000463 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Zoey Thompson 1121042880000464 +705bonus pay for amazing work on #OSS 00010000464 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000465 +705bonus pay for amazing work on #OSS 00010000465 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000466 +705bonus pay for amazing work on #OSS 00010000466 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000467 +705bonus pay for amazing work on #OSS 00010000467 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000468 +705bonus pay for amazing work on #OSS 00010000468 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000469 +705bonus pay for amazing work on #OSS 00010000469 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000470 +705bonus pay for amazing work on #OSS 00010000470 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000471 +705bonus pay for amazing work on #OSS 00010000471 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000472 +705bonus pay for amazing work on #OSS 00010000472 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000473 +705bonus pay for amazing work on #OSS 00010000473 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000474 +705bonus pay for amazing work on #OSS 00010000474 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000475 +705bonus pay for amazing work on #OSS 00010000475 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000476 +705bonus pay for amazing work on #OSS 00010000476 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000477 +705bonus pay for amazing work on #OSS 00010000477 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000478 +705bonus pay for amazing work on #OSS 00010000478 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000479 +705bonus pay for amazing work on #OSS 00010000479 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000480 +705bonus pay for amazing work on #OSS 00010000480 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000481 +705bonus pay for amazing work on #OSS 00010000481 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000482 +705bonus pay for amazing work on #OSS 00010000482 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000483 +705bonus pay for amazing work on #OSS 00010000483 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000484 +705bonus pay for amazing work on #OSS 00010000484 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000485 +705bonus pay for amazing work on #OSS 00010000485 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000486 +705bonus pay for amazing work on #OSS 00010000486 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000487 +705bonus pay for amazing work on #OSS 00010000487 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000488 +705bonus pay for amazing work on #OSS 00010000488 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000489 +705bonus pay for amazing work on #OSS 00010000489 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000490 +705bonus pay for amazing work on #OSS 00010000490 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000491 +705bonus pay for amazing work on #OSS 00010000491 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000492 +705bonus pay for amazing work on #OSS 00010000492 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000493 +705bonus pay for amazing work on #OSS 00010000493 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000494 +705bonus pay for amazing work on #OSS 00010000494 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000495 +705bonus pay for amazing work on #OSS 00010000495 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000496 +705bonus pay for amazing work on #OSS 00010000496 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000497 +705bonus pay for amazing work on #OSS 00010000497 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000498 +705bonus pay for amazing work on #OSS 00010000498 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000499 +705bonus pay for amazing work on #OSS 00010000499 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000500 +705bonus pay for amazing work on #OSS 00010000500 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000501 +705bonus pay for amazing work on #OSS 00010000501 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000502 +705bonus pay for amazing work on #OSS 00010000502 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000503 +705bonus pay for amazing work on #OSS 00010000503 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000504 +705bonus pay for amazing work on #OSS 00010000504 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000505 +705bonus pay for amazing work on #OSS 00010000505 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000506 +705bonus pay for amazing work on #OSS 00010000506 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000507 +705bonus pay for amazing work on #OSS 00010000507 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000508 +705bonus pay for amazing work on #OSS 00010000508 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000509 +705bonus pay for amazing work on #OSS 00010000509 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000510 +705bonus pay for amazing work on #OSS 00010000510 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000511 +705bonus pay for amazing work on #OSS 00010000511 +62223138010481967038518 0000100000#dibaxDXu8AkHK#Joshua Thompson 1121042880000512 +705bonus pay for amazing work on #OSS 00010000512 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Joshua Thomas 1121042880000513 +705bonus pay for amazing work on #OSS 00010000513 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000514 +705bonus pay for amazing work on #OSS 00010000514 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000515 +705bonus pay for amazing work on #OSS 00010000515 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000516 +705bonus pay for amazing work on #OSS 00010000516 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000517 +705bonus pay for amazing work on #OSS 00010000517 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000518 +705bonus pay for amazing work on #OSS 00010000518 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000519 +705bonus pay for amazing work on #OSS 00010000519 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000520 +705bonus pay for amazing work on #OSS 00010000520 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000521 +705bonus pay for amazing work on #OSS 00010000521 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000522 +705bonus pay for amazing work on #OSS 00010000522 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000523 +705bonus pay for amazing work on #OSS 00010000523 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000524 +705bonus pay for amazing work on #OSS 00010000524 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000525 +705bonus pay for amazing work on #OSS 00010000525 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000526 +705bonus pay for amazing work on #OSS 00010000526 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000527 +705bonus pay for amazing work on #OSS 00010000527 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000528 +705bonus pay for amazing work on #OSS 00010000528 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000529 +705bonus pay for amazing work on #OSS 00010000529 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000530 +705bonus pay for amazing work on #OSS 00010000530 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000531 +705bonus pay for amazing work on #OSS 00010000531 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000532 +705bonus pay for amazing work on #OSS 00010000532 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000533 +705bonus pay for amazing work on #OSS 00010000533 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000534 +705bonus pay for amazing work on #OSS 00010000534 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000535 +705bonus pay for amazing work on #OSS 00010000535 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000536 +705bonus pay for amazing work on #OSS 00010000536 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000537 +705bonus pay for amazing work on #OSS 00010000537 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000538 +705bonus pay for amazing work on #OSS 00010000538 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000539 +705bonus pay for amazing work on #OSS 00010000539 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000540 +705bonus pay for amazing work on #OSS 00010000540 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000541 +705bonus pay for amazing work on #OSS 00010000541 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000542 +705bonus pay for amazing work on #OSS 00010000542 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000543 +705bonus pay for amazing work on #OSS 00010000543 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000544 +705bonus pay for amazing work on #OSS 00010000544 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000545 +705bonus pay for amazing work on #OSS 00010000545 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000546 +705bonus pay for amazing work on #OSS 00010000546 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000547 +705bonus pay for amazing work on #OSS 00010000547 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000548 +705bonus pay for amazing work on #OSS 00010000548 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000549 +705bonus pay for amazing work on #OSS 00010000549 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000550 +705bonus pay for amazing work on #OSS 00010000550 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000551 +705bonus pay for amazing work on #OSS 00010000551 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000552 +705bonus pay for amazing work on #OSS 00010000552 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000553 +705bonus pay for amazing work on #OSS 00010000553 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000554 +705bonus pay for amazing work on #OSS 00010000554 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000555 +705bonus pay for amazing work on #OSS 00010000555 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000556 +705bonus pay for amazing work on #OSS 00010000556 +62223138010481967038518 0000100000#AeqfY3v9oYj84#Ella Thomas 1121042880000557 +705bonus pay for amazing work on #OSS 00010000557 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Ella Thompson 1121042880000558 +705bonus pay for amazing work on #OSS 00010000558 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000559 +705bonus pay for amazing work on #OSS 00010000559 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000560 +705bonus pay for amazing work on #OSS 00010000560 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000561 +705bonus pay for amazing work on #OSS 00010000561 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000562 +705bonus pay for amazing work on #OSS 00010000562 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000563 +705bonus pay for amazing work on #OSS 00010000563 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000564 +705bonus pay for amazing work on #OSS 00010000564 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000565 +705bonus pay for amazing work on #OSS 00010000565 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000566 +705bonus pay for amazing work on #OSS 00010000566 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000567 +705bonus pay for amazing work on #OSS 00010000567 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000568 +705bonus pay for amazing work on #OSS 00010000568 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000569 +705bonus pay for amazing work on #OSS 00010000569 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000570 +705bonus pay for amazing work on #OSS 00010000570 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000571 +705bonus pay for amazing work on #OSS 00010000571 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000572 +705bonus pay for amazing work on #OSS 00010000572 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000573 +705bonus pay for amazing work on #OSS 00010000573 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000574 +705bonus pay for amazing work on #OSS 00010000574 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000575 +705bonus pay for amazing work on #OSS 00010000575 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000576 +705bonus pay for amazing work on #OSS 00010000576 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000577 +705bonus pay for amazing work on #OSS 00010000577 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000578 +705bonus pay for amazing work on #OSS 00010000578 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000579 +705bonus pay for amazing work on #OSS 00010000579 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000580 +705bonus pay for amazing work on #OSS 00010000580 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000581 +705bonus pay for amazing work on #OSS 00010000581 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000582 +705bonus pay for amazing work on #OSS 00010000582 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000583 +705bonus pay for amazing work on #OSS 00010000583 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000584 +705bonus pay for amazing work on #OSS 00010000584 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000585 +705bonus pay for amazing work on #OSS 00010000585 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000586 +705bonus pay for amazing work on #OSS 00010000586 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000587 +705bonus pay for amazing work on #OSS 00010000587 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000588 +705bonus pay for amazing work on #OSS 00010000588 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000589 +705bonus pay for amazing work on #OSS 00010000589 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000590 +705bonus pay for amazing work on #OSS 00010000590 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000591 +705bonus pay for amazing work on #OSS 00010000591 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000592 +705bonus pay for amazing work on #OSS 00010000592 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000593 +705bonus pay for amazing work on #OSS 00010000593 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000594 +705bonus pay for amazing work on #OSS 00010000594 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000595 +705bonus pay for amazing work on #OSS 00010000595 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000596 +705bonus pay for amazing work on #OSS 00010000596 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000597 +705bonus pay for amazing work on #OSS 00010000597 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000598 +705bonus pay for amazing work on #OSS 00010000598 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000599 +705bonus pay for amazing work on #OSS 00010000599 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000600 +705bonus pay for amazing work on #OSS 00010000600 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000601 +705bonus pay for amazing work on #OSS 00010000601 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000602 +705bonus pay for amazing work on #OSS 00010000602 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000603 +705bonus pay for amazing work on #OSS 00010000603 +62223138010481967038518 0000100000#sMsMFiAD3y8R8#Joshua Thompson 1121042880000604 +705bonus pay for amazing work on #OSS 00010000604 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Joshua Smith 1121042880000605 +705bonus pay for amazing work on #OSS 00010000605 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000606 +705bonus pay for amazing work on #OSS 00010000606 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000607 +705bonus pay for amazing work on #OSS 00010000607 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000608 +705bonus pay for amazing work on #OSS 00010000608 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000609 +705bonus pay for amazing work on #OSS 00010000609 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000610 +705bonus pay for amazing work on #OSS 00010000610 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000611 +705bonus pay for amazing work on #OSS 00010000611 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000612 +705bonus pay for amazing work on #OSS 00010000612 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000613 +705bonus pay for amazing work on #OSS 00010000613 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000614 +705bonus pay for amazing work on #OSS 00010000614 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000615 +705bonus pay for amazing work on #OSS 00010000615 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000616 +705bonus pay for amazing work on #OSS 00010000616 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000617 +705bonus pay for amazing work on #OSS 00010000617 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000618 +705bonus pay for amazing work on #OSS 00010000618 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000619 +705bonus pay for amazing work on #OSS 00010000619 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000620 +705bonus pay for amazing work on #OSS 00010000620 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000621 +705bonus pay for amazing work on #OSS 00010000621 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000622 +705bonus pay for amazing work on #OSS 00010000622 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000623 +705bonus pay for amazing work on #OSS 00010000623 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000624 +705bonus pay for amazing work on #OSS 00010000624 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000625 +705bonus pay for amazing work on #OSS 00010000625 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000626 +705bonus pay for amazing work on #OSS 00010000626 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000627 +705bonus pay for amazing work on #OSS 00010000627 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000628 +705bonus pay for amazing work on #OSS 00010000628 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000629 +705bonus pay for amazing work on #OSS 00010000629 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000630 +705bonus pay for amazing work on #OSS 00010000630 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000631 +705bonus pay for amazing work on #OSS 00010000631 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000632 +705bonus pay for amazing work on #OSS 00010000632 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000633 +705bonus pay for amazing work on #OSS 00010000633 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000634 +705bonus pay for amazing work on #OSS 00010000634 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000635 +705bonus pay for amazing work on #OSS 00010000635 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000636 +705bonus pay for amazing work on #OSS 00010000636 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000637 +705bonus pay for amazing work on #OSS 00010000637 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000638 +705bonus pay for amazing work on #OSS 00010000638 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000639 +705bonus pay for amazing work on #OSS 00010000639 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000640 +705bonus pay for amazing work on #OSS 00010000640 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000641 +705bonus pay for amazing work on #OSS 00010000641 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000642 +705bonus pay for amazing work on #OSS 00010000642 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000643 +705bonus pay for amazing work on #OSS 00010000643 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000644 +705bonus pay for amazing work on #OSS 00010000644 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000645 +705bonus pay for amazing work on #OSS 00010000645 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000646 +705bonus pay for amazing work on #OSS 00010000646 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000647 +705bonus pay for amazing work on #OSS 00010000647 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000648 +705bonus pay for amazing work on #OSS 00010000648 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000649 +705bonus pay for amazing work on #OSS 00010000649 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000650 +705bonus pay for amazing work on #OSS 00010000650 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000651 +705bonus pay for amazing work on #OSS 00010000651 +62223138010481967038518 0000100000#MNpzIT6VjEXXo#Jacob Smith 1121042880000652 +705bonus pay for amazing work on #OSS 00010000652 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Joseph Robinson 1121042880000653 +705bonus pay for amazing work on #OSS 00010000653 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000654 +705bonus pay for amazing work on #OSS 00010000654 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000655 +705bonus pay for amazing work on #OSS 00010000655 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000656 +705bonus pay for amazing work on #OSS 00010000656 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000657 +705bonus pay for amazing work on #OSS 00010000657 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000658 +705bonus pay for amazing work on #OSS 00010000658 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000659 +705bonus pay for amazing work on #OSS 00010000659 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000660 +705bonus pay for amazing work on #OSS 00010000660 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000661 +705bonus pay for amazing work on #OSS 00010000661 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000662 +705bonus pay for amazing work on #OSS 00010000662 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000663 +705bonus pay for amazing work on #OSS 00010000663 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000664 +705bonus pay for amazing work on #OSS 00010000664 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000665 +705bonus pay for amazing work on #OSS 00010000665 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000666 +705bonus pay for amazing work on #OSS 00010000666 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000667 +705bonus pay for amazing work on #OSS 00010000667 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000668 +705bonus pay for amazing work on #OSS 00010000668 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000669 +705bonus pay for amazing work on #OSS 00010000669 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000670 +705bonus pay for amazing work on #OSS 00010000670 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000671 +705bonus pay for amazing work on #OSS 00010000671 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000672 +705bonus pay for amazing work on #OSS 00010000672 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000673 +705bonus pay for amazing work on #OSS 00010000673 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000674 +705bonus pay for amazing work on #OSS 00010000674 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000675 +705bonus pay for amazing work on #OSS 00010000675 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000676 +705bonus pay for amazing work on #OSS 00010000676 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000677 +705bonus pay for amazing work on #OSS 00010000677 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000678 +705bonus pay for amazing work on #OSS 00010000678 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000679 +705bonus pay for amazing work on #OSS 00010000679 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000680 +705bonus pay for amazing work on #OSS 00010000680 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000681 +705bonus pay for amazing work on #OSS 00010000681 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000682 +705bonus pay for amazing work on #OSS 00010000682 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000683 +705bonus pay for amazing work on #OSS 00010000683 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000684 +705bonus pay for amazing work on #OSS 00010000684 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000685 +705bonus pay for amazing work on #OSS 00010000685 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000686 +705bonus pay for amazing work on #OSS 00010000686 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000687 +705bonus pay for amazing work on #OSS 00010000687 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000688 +705bonus pay for amazing work on #OSS 00010000688 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000689 +705bonus pay for amazing work on #OSS 00010000689 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000690 +705bonus pay for amazing work on #OSS 00010000690 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000691 +705bonus pay for amazing work on #OSS 00010000691 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000692 +705bonus pay for amazing work on #OSS 00010000692 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000693 +705bonus pay for amazing work on #OSS 00010000693 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000694 +705bonus pay for amazing work on #OSS 00010000694 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000695 +705bonus pay for amazing work on #OSS 00010000695 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000696 +705bonus pay for amazing work on #OSS 00010000696 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000697 +705bonus pay for amazing work on #OSS 00010000697 +62223138010481967038518 0000100000#rtfeGh2Meqpzb#Zoey Robinson 1121042880000698 +705bonus pay for amazing work on #OSS 00010000698 +62223138010481967038518 0000100000#M6oY0AzJgimFq#Zoey Brown 1121042880000699 +705bonus pay for amazing work on #OSS 00010000699 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000700 +705bonus pay for amazing work on #OSS 00010000700 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000701 +705bonus pay for amazing work on #OSS 00010000701 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000702 +705bonus pay for amazing work on #OSS 00010000702 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000703 +705bonus pay for amazing work on #OSS 00010000703 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000704 +705bonus pay for amazing work on #OSS 00010000704 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000705 +705bonus pay for amazing work on #OSS 00010000705 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000706 +705bonus pay for amazing work on #OSS 00010000706 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000707 +705bonus pay for amazing work on #OSS 00010000707 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000708 +705bonus pay for amazing work on #OSS 00010000708 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000709 +705bonus pay for amazing work on #OSS 00010000709 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000710 +705bonus pay for amazing work on #OSS 00010000710 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000711 +705bonus pay for amazing work on #OSS 00010000711 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000712 +705bonus pay for amazing work on #OSS 00010000712 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000713 +705bonus pay for amazing work on #OSS 00010000713 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000714 +705bonus pay for amazing work on #OSS 00010000714 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000715 +705bonus pay for amazing work on #OSS 00010000715 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000716 +705bonus pay for amazing work on #OSS 00010000716 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000717 +705bonus pay for amazing work on #OSS 00010000717 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000718 +705bonus pay for amazing work on #OSS 00010000718 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000719 +705bonus pay for amazing work on #OSS 00010000719 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000720 +705bonus pay for amazing work on #OSS 00010000720 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000721 +705bonus pay for amazing work on #OSS 00010000721 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000722 +705bonus pay for amazing work on #OSS 00010000722 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000723 +705bonus pay for amazing work on #OSS 00010000723 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000724 +705bonus pay for amazing work on #OSS 00010000724 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000725 +705bonus pay for amazing work on #OSS 00010000725 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000726 +705bonus pay for amazing work on #OSS 00010000726 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000727 +705bonus pay for amazing work on #OSS 00010000727 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000728 +705bonus pay for amazing work on #OSS 00010000728 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000729 +705bonus pay for amazing work on #OSS 00010000729 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000730 +705bonus pay for amazing work on #OSS 00010000730 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000731 +705bonus pay for amazing work on #OSS 00010000731 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000732 +705bonus pay for amazing work on #OSS 00010000732 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000733 +705bonus pay for amazing work on #OSS 00010000733 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000734 +705bonus pay for amazing work on #OSS 00010000734 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000735 +705bonus pay for amazing work on #OSS 00010000735 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000736 +705bonus pay for amazing work on #OSS 00010000736 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000737 +705bonus pay for amazing work on #OSS 00010000737 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000738 +705bonus pay for amazing work on #OSS 00010000738 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000739 +705bonus pay for amazing work on #OSS 00010000739 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000740 +705bonus pay for amazing work on #OSS 00010000740 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000741 +705bonus pay for amazing work on #OSS 00010000741 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000742 +705bonus pay for amazing work on #OSS 00010000742 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000743 +705bonus pay for amazing work on #OSS 00010000743 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000744 +705bonus pay for amazing work on #OSS 00010000744 +62223138010481967038518 0000100000#M6oY0AzJgimFq#William Brown 1121042880000745 +705bonus pay for amazing work on #OSS 00010000745 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#William Martinez 1121042880000746 +705bonus pay for amazing work on #OSS 00010000746 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000747 +705bonus pay for amazing work on #OSS 00010000747 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000748 +705bonus pay for amazing work on #OSS 00010000748 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000749 +705bonus pay for amazing work on #OSS 00010000749 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000750 +705bonus pay for amazing work on #OSS 00010000750 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000751 +705bonus pay for amazing work on #OSS 00010000751 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000752 +705bonus pay for amazing work on #OSS 00010000752 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000753 +705bonus pay for amazing work on #OSS 00010000753 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000754 +705bonus pay for amazing work on #OSS 00010000754 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000755 +705bonus pay for amazing work on #OSS 00010000755 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000756 +705bonus pay for amazing work on #OSS 00010000756 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000757 +705bonus pay for amazing work on #OSS 00010000757 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000758 +705bonus pay for amazing work on #OSS 00010000758 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000759 +705bonus pay for amazing work on #OSS 00010000759 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000760 +705bonus pay for amazing work on #OSS 00010000760 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000761 +705bonus pay for amazing work on #OSS 00010000761 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000762 +705bonus pay for amazing work on #OSS 00010000762 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000763 +705bonus pay for amazing work on #OSS 00010000763 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000764 +705bonus pay for amazing work on #OSS 00010000764 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000765 +705bonus pay for amazing work on #OSS 00010000765 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000766 +705bonus pay for amazing work on #OSS 00010000766 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000767 +705bonus pay for amazing work on #OSS 00010000767 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000768 +705bonus pay for amazing work on #OSS 00010000768 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000769 +705bonus pay for amazing work on #OSS 00010000769 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000770 +705bonus pay for amazing work on #OSS 00010000770 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000771 +705bonus pay for amazing work on #OSS 00010000771 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000772 +705bonus pay for amazing work on #OSS 00010000772 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000773 +705bonus pay for amazing work on #OSS 00010000773 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000774 +705bonus pay for amazing work on #OSS 00010000774 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000775 +705bonus pay for amazing work on #OSS 00010000775 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000776 +705bonus pay for amazing work on #OSS 00010000776 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000777 +705bonus pay for amazing work on #OSS 00010000777 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000778 +705bonus pay for amazing work on #OSS 00010000778 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000779 +705bonus pay for amazing work on #OSS 00010000779 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000780 +705bonus pay for amazing work on #OSS 00010000780 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000781 +705bonus pay for amazing work on #OSS 00010000781 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000782 +705bonus pay for amazing work on #OSS 00010000782 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000783 +705bonus pay for amazing work on #OSS 00010000783 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000784 +705bonus pay for amazing work on #OSS 00010000784 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000785 +705bonus pay for amazing work on #OSS 00010000785 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000786 +705bonus pay for amazing work on #OSS 00010000786 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000787 +705bonus pay for amazing work on #OSS 00010000787 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000788 +705bonus pay for amazing work on #OSS 00010000788 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000789 +705bonus pay for amazing work on #OSS 00010000789 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000790 +705bonus pay for amazing work on #OSS 00010000790 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000791 +705bonus pay for amazing work on #OSS 00010000791 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000792 +705bonus pay for amazing work on #OSS 00010000792 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000793 +705bonus pay for amazing work on #OSS 00010000793 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000794 +705bonus pay for amazing work on #OSS 00010000794 +62223138010481967038518 0000100000#MIOzQcrU8ZrBN#David Martinez 1121042880000795 +705bonus pay for amazing work on #OSS 00010000795 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000796 +705bonus pay for amazing work on #OSS 00010000796 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000797 +705bonus pay for amazing work on #OSS 00010000797 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000798 +705bonus pay for amazing work on #OSS 00010000798 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000799 +705bonus pay for amazing work on #OSS 00010000799 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000800 +705bonus pay for amazing work on #OSS 00010000800 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000801 +705bonus pay for amazing work on #OSS 00010000801 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000802 +705bonus pay for amazing work on #OSS 00010000802 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000803 +705bonus pay for amazing work on #OSS 00010000803 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000804 +705bonus pay for amazing work on #OSS 00010000804 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000805 +705bonus pay for amazing work on #OSS 00010000805 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000806 +705bonus pay for amazing work on #OSS 00010000806 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000807 +705bonus pay for amazing work on #OSS 00010000807 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000808 +705bonus pay for amazing work on #OSS 00010000808 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000809 +705bonus pay for amazing work on #OSS 00010000809 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000810 +705bonus pay for amazing work on #OSS 00010000810 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000811 +705bonus pay for amazing work on #OSS 00010000811 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000812 +705bonus pay for amazing work on #OSS 00010000812 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000813 +705bonus pay for amazing work on #OSS 00010000813 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000814 +705bonus pay for amazing work on #OSS 00010000814 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000815 +705bonus pay for amazing work on #OSS 00010000815 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000816 +705bonus pay for amazing work on #OSS 00010000816 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000817 +705bonus pay for amazing work on #OSS 00010000817 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000818 +705bonus pay for amazing work on #OSS 00010000818 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000819 +705bonus pay for amazing work on #OSS 00010000819 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000820 +705bonus pay for amazing work on #OSS 00010000820 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000821 +705bonus pay for amazing work on #OSS 00010000821 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000822 +705bonus pay for amazing work on #OSS 00010000822 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000823 +705bonus pay for amazing work on #OSS 00010000823 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000824 +705bonus pay for amazing work on #OSS 00010000824 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000825 +705bonus pay for amazing work on #OSS 00010000825 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000826 +705bonus pay for amazing work on #OSS 00010000826 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000827 +705bonus pay for amazing work on #OSS 00010000827 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000828 +705bonus pay for amazing work on #OSS 00010000828 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000829 +705bonus pay for amazing work on #OSS 00010000829 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000830 +705bonus pay for amazing work on #OSS 00010000830 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000831 +705bonus pay for amazing work on #OSS 00010000831 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000832 +705bonus pay for amazing work on #OSS 00010000832 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000833 +705bonus pay for amazing work on #OSS 00010000833 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000834 +705bonus pay for amazing work on #OSS 00010000834 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000835 +705bonus pay for amazing work on #OSS 00010000835 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000836 +705bonus pay for amazing work on #OSS 00010000836 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000837 +705bonus pay for amazing work on #OSS 00010000837 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000838 +705bonus pay for amazing work on #OSS 00010000838 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000839 +705bonus pay for amazing work on #OSS 00010000839 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000840 +705bonus pay for amazing work on #OSS 00010000840 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000841 +705bonus pay for amazing work on #OSS 00010000841 +62223138010481967038518 0000100000#I4huaz9y2qdn9#Sofia Garcia 1121042880000842 +705bonus pay for amazing work on #OSS 00010000842 +62223138010481967038518 0000100000#mNX82cp57avlG#Natalie Thompson 1121042880000843 +705bonus pay for amazing work on #OSS 00010000843 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000844 +705bonus pay for amazing work on #OSS 00010000844 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000845 +705bonus pay for amazing work on #OSS 00010000845 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000846 +705bonus pay for amazing work on #OSS 00010000846 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000847 +705bonus pay for amazing work on #OSS 00010000847 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000848 +705bonus pay for amazing work on #OSS 00010000848 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000849 +705bonus pay for amazing work on #OSS 00010000849 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000850 +705bonus pay for amazing work on #OSS 00010000850 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000851 +705bonus pay for amazing work on #OSS 00010000851 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000852 +705bonus pay for amazing work on #OSS 00010000852 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000853 +705bonus pay for amazing work on #OSS 00010000853 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000854 +705bonus pay for amazing work on #OSS 00010000854 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000855 +705bonus pay for amazing work on #OSS 00010000855 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000856 +705bonus pay for amazing work on #OSS 00010000856 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000857 +705bonus pay for amazing work on #OSS 00010000857 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000858 +705bonus pay for amazing work on #OSS 00010000858 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000859 +705bonus pay for amazing work on #OSS 00010000859 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000860 +705bonus pay for amazing work on #OSS 00010000860 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000861 +705bonus pay for amazing work on #OSS 00010000861 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000862 +705bonus pay for amazing work on #OSS 00010000862 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000863 +705bonus pay for amazing work on #OSS 00010000863 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000864 +705bonus pay for amazing work on #OSS 00010000864 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000865 +705bonus pay for amazing work on #OSS 00010000865 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000866 +705bonus pay for amazing work on #OSS 00010000866 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000867 +705bonus pay for amazing work on #OSS 00010000867 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000868 +705bonus pay for amazing work on #OSS 00010000868 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000869 +705bonus pay for amazing work on #OSS 00010000869 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000870 +705bonus pay for amazing work on #OSS 00010000870 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000871 +705bonus pay for amazing work on #OSS 00010000871 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000872 +705bonus pay for amazing work on #OSS 00010000872 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000873 +705bonus pay for amazing work on #OSS 00010000873 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000874 +705bonus pay for amazing work on #OSS 00010000874 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000875 +705bonus pay for amazing work on #OSS 00010000875 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000876 +705bonus pay for amazing work on #OSS 00010000876 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000877 +705bonus pay for amazing work on #OSS 00010000877 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000878 +705bonus pay for amazing work on #OSS 00010000878 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000879 +705bonus pay for amazing work on #OSS 00010000879 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000880 +705bonus pay for amazing work on #OSS 00010000880 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000881 +705bonus pay for amazing work on #OSS 00010000881 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000882 +705bonus pay for amazing work on #OSS 00010000882 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000883 +705bonus pay for amazing work on #OSS 00010000883 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000884 +705bonus pay for amazing work on #OSS 00010000884 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000885 +705bonus pay for amazing work on #OSS 00010000885 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000886 +705bonus pay for amazing work on #OSS 00010000886 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000887 +705bonus pay for amazing work on #OSS 00010000887 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000888 +705bonus pay for amazing work on #OSS 00010000888 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000889 +705bonus pay for amazing work on #OSS 00010000889 +62223138010481967038518 0000100000#mNX82cp57avlG#Joshua Thompson 1121042880000890 +705bonus pay for amazing work on #OSS 00010000890 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000891 +705bonus pay for amazing work on #OSS 00010000891 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000892 +705bonus pay for amazing work on #OSS 00010000892 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000893 +705bonus pay for amazing work on #OSS 00010000893 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000894 +705bonus pay for amazing work on #OSS 00010000894 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000895 +705bonus pay for amazing work on #OSS 00010000895 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000896 +705bonus pay for amazing work on #OSS 00010000896 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000897 +705bonus pay for amazing work on #OSS 00010000897 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000898 +705bonus pay for amazing work on #OSS 00010000898 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000899 +705bonus pay for amazing work on #OSS 00010000899 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000900 +705bonus pay for amazing work on #OSS 00010000900 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000901 +705bonus pay for amazing work on #OSS 00010000901 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000902 +705bonus pay for amazing work on #OSS 00010000902 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000903 +705bonus pay for amazing work on #OSS 00010000903 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000904 +705bonus pay for amazing work on #OSS 00010000904 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000905 +705bonus pay for amazing work on #OSS 00010000905 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000906 +705bonus pay for amazing work on #OSS 00010000906 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000907 +705bonus pay for amazing work on #OSS 00010000907 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000908 +705bonus pay for amazing work on #OSS 00010000908 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000909 +705bonus pay for amazing work on #OSS 00010000909 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000910 +705bonus pay for amazing work on #OSS 00010000910 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000911 +705bonus pay for amazing work on #OSS 00010000911 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000912 +705bonus pay for amazing work on #OSS 00010000912 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000913 +705bonus pay for amazing work on #OSS 00010000913 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000914 +705bonus pay for amazing work on #OSS 00010000914 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000915 +705bonus pay for amazing work on #OSS 00010000915 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000916 +705bonus pay for amazing work on #OSS 00010000916 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000917 +705bonus pay for amazing work on #OSS 00010000917 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000918 +705bonus pay for amazing work on #OSS 00010000918 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000919 +705bonus pay for amazing work on #OSS 00010000919 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000920 +705bonus pay for amazing work on #OSS 00010000920 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000921 +705bonus pay for amazing work on #OSS 00010000921 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000922 +705bonus pay for amazing work on #OSS 00010000922 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000923 +705bonus pay for amazing work on #OSS 00010000923 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000924 +705bonus pay for amazing work on #OSS 00010000924 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000925 +705bonus pay for amazing work on #OSS 00010000925 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000926 +705bonus pay for amazing work on #OSS 00010000926 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000927 +705bonus pay for amazing work on #OSS 00010000927 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000928 +705bonus pay for amazing work on #OSS 00010000928 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000929 +705bonus pay for amazing work on #OSS 00010000929 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000930 +705bonus pay for amazing work on #OSS 00010000930 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000931 +705bonus pay for amazing work on #OSS 00010000931 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000932 +705bonus pay for amazing work on #OSS 00010000932 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000933 +705bonus pay for amazing work on #OSS 00010000933 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000934 +705bonus pay for amazing work on #OSS 00010000934 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000935 +705bonus pay for amazing work on #OSS 00010000935 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000936 +705bonus pay for amazing work on #OSS 00010000936 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000937 +705bonus pay for amazing work on #OSS 00010000937 +62223138010481967038518 0000100000#FIqV7bx0XyTQs#Elijah Jackson 1121042880000938 +705bonus pay for amazing work on #OSS 00010000938 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000939 +705bonus pay for amazing work on #OSS 00010000939 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000940 +705bonus pay for amazing work on #OSS 00010000940 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000941 +705bonus pay for amazing work on #OSS 00010000941 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000942 +705bonus pay for amazing work on #OSS 00010000942 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000943 +705bonus pay for amazing work on #OSS 00010000943 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000944 +705bonus pay for amazing work on #OSS 00010000944 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000945 +705bonus pay for amazing work on #OSS 00010000945 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000946 +705bonus pay for amazing work on #OSS 00010000946 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000947 +705bonus pay for amazing work on #OSS 00010000947 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000948 +705bonus pay for amazing work on #OSS 00010000948 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000949 +705bonus pay for amazing work on #OSS 00010000949 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000950 +705bonus pay for amazing work on #OSS 00010000950 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000951 +705bonus pay for amazing work on #OSS 00010000951 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000952 +705bonus pay for amazing work on #OSS 00010000952 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000953 +705bonus pay for amazing work on #OSS 00010000953 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000954 +705bonus pay for amazing work on #OSS 00010000954 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000955 +705bonus pay for amazing work on #OSS 00010000955 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000956 +705bonus pay for amazing work on #OSS 00010000956 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000957 +705bonus pay for amazing work on #OSS 00010000957 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000958 +705bonus pay for amazing work on #OSS 00010000958 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000959 +705bonus pay for amazing work on #OSS 00010000959 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000960 +705bonus pay for amazing work on #OSS 00010000960 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000961 +705bonus pay for amazing work on #OSS 00010000961 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000962 +705bonus pay for amazing work on #OSS 00010000962 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000963 +705bonus pay for amazing work on #OSS 00010000963 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000964 +705bonus pay for amazing work on #OSS 00010000964 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000965 +705bonus pay for amazing work on #OSS 00010000965 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000966 +705bonus pay for amazing work on #OSS 00010000966 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000967 +705bonus pay for amazing work on #OSS 00010000967 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000968 +705bonus pay for amazing work on #OSS 00010000968 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000969 +705bonus pay for amazing work on #OSS 00010000969 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000970 +705bonus pay for amazing work on #OSS 00010000970 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000971 +705bonus pay for amazing work on #OSS 00010000971 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000972 +705bonus pay for amazing work on #OSS 00010000972 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000973 +705bonus pay for amazing work on #OSS 00010000973 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000974 +705bonus pay for amazing work on #OSS 00010000974 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000975 +705bonus pay for amazing work on #OSS 00010000975 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000976 +705bonus pay for amazing work on #OSS 00010000976 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000977 +705bonus pay for amazing work on #OSS 00010000977 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000978 +705bonus pay for amazing work on #OSS 00010000978 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000979 +705bonus pay for amazing work on #OSS 00010000979 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000980 +705bonus pay for amazing work on #OSS 00010000980 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000981 +705bonus pay for amazing work on #OSS 00010000981 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000982 +705bonus pay for amazing work on #OSS 00010000982 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000983 +705bonus pay for amazing work on #OSS 00010000983 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000984 +705bonus pay for amazing work on #OSS 00010000984 +62223138010481967038518 0000100000#SAqaC8dCnXqUW#Jacob Smith 1121042880000985 +705bonus pay for amazing work on #OSS 00010000985 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Jacob Robinson 1121042880000986 +705bonus pay for amazing work on #OSS 00010000986 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880000987 +705bonus pay for amazing work on #OSS 00010000987 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880000988 +705bonus pay for amazing work on #OSS 00010000988 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880000989 +705bonus pay for amazing work on #OSS 00010000989 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880000990 +705bonus pay for amazing work on #OSS 00010000990 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880000991 +705bonus pay for amazing work on #OSS 00010000991 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880000992 +705bonus pay for amazing work on #OSS 00010000992 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880000993 +705bonus pay for amazing work on #OSS 00010000993 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880000994 +705bonus pay for amazing work on #OSS 00010000994 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880000995 +705bonus pay for amazing work on #OSS 00010000995 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880000996 +705bonus pay for amazing work on #OSS 00010000996 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880000997 +705bonus pay for amazing work on #OSS 00010000997 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880000998 +705bonus pay for amazing work on #OSS 00010000998 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880000999 +705bonus pay for amazing work on #OSS 00010000999 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001000 +705bonus pay for amazing work on #OSS 00010001000 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001001 +705bonus pay for amazing work on #OSS 00010001001 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001002 +705bonus pay for amazing work on #OSS 00010001002 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001003 +705bonus pay for amazing work on #OSS 00010001003 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001004 +705bonus pay for amazing work on #OSS 00010001004 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001005 +705bonus pay for amazing work on #OSS 00010001005 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001006 +705bonus pay for amazing work on #OSS 00010001006 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001007 +705bonus pay for amazing work on #OSS 00010001007 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001008 +705bonus pay for amazing work on #OSS 00010001008 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001009 +705bonus pay for amazing work on #OSS 00010001009 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001010 +705bonus pay for amazing work on #OSS 00010001010 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001011 +705bonus pay for amazing work on #OSS 00010001011 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001012 +705bonus pay for amazing work on #OSS 00010001012 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001013 +705bonus pay for amazing work on #OSS 00010001013 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001014 +705bonus pay for amazing work on #OSS 00010001014 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001015 +705bonus pay for amazing work on #OSS 00010001015 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001016 +705bonus pay for amazing work on #OSS 00010001016 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001017 +705bonus pay for amazing work on #OSS 00010001017 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001018 +705bonus pay for amazing work on #OSS 00010001018 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001019 +705bonus pay for amazing work on #OSS 00010001019 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001020 +705bonus pay for amazing work on #OSS 00010001020 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001021 +705bonus pay for amazing work on #OSS 00010001021 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001022 +705bonus pay for amazing work on #OSS 00010001022 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001023 +705bonus pay for amazing work on #OSS 00010001023 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001024 +705bonus pay for amazing work on #OSS 00010001024 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001025 +705bonus pay for amazing work on #OSS 00010001025 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001026 +705bonus pay for amazing work on #OSS 00010001026 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001027 +705bonus pay for amazing work on #OSS 00010001027 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001028 +705bonus pay for amazing work on #OSS 00010001028 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001029 +705bonus pay for amazing work on #OSS 00010001029 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001030 +705bonus pay for amazing work on #OSS 00010001030 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001031 +705bonus pay for amazing work on #OSS 00010001031 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001032 +705bonus pay for amazing work on #OSS 00010001032 +62223138010481967038518 0000100000#lQnpnGFqAPDUD#Zoey Robinson 1121042880001033 +705bonus pay for amazing work on #OSS 00010001033 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001034 +705bonus pay for amazing work on #OSS 00010001034 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001035 +705bonus pay for amazing work on #OSS 00010001035 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001036 +705bonus pay for amazing work on #OSS 00010001036 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001037 +705bonus pay for amazing work on #OSS 00010001037 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001038 +705bonus pay for amazing work on #OSS 00010001038 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001039 +705bonus pay for amazing work on #OSS 00010001039 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001040 +705bonus pay for amazing work on #OSS 00010001040 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001041 +705bonus pay for amazing work on #OSS 00010001041 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001042 +705bonus pay for amazing work on #OSS 00010001042 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001043 +705bonus pay for amazing work on #OSS 00010001043 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001044 +705bonus pay for amazing work on #OSS 00010001044 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001045 +705bonus pay for amazing work on #OSS 00010001045 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001046 +705bonus pay for amazing work on #OSS 00010001046 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001047 +705bonus pay for amazing work on #OSS 00010001047 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001048 +705bonus pay for amazing work on #OSS 00010001048 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001049 +705bonus pay for amazing work on #OSS 00010001049 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001050 +705bonus pay for amazing work on #OSS 00010001050 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001051 +705bonus pay for amazing work on #OSS 00010001051 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001052 +705bonus pay for amazing work on #OSS 00010001052 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001053 +705bonus pay for amazing work on #OSS 00010001053 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001054 +705bonus pay for amazing work on #OSS 00010001054 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001055 +705bonus pay for amazing work on #OSS 00010001055 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001056 +705bonus pay for amazing work on #OSS 00010001056 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001057 +705bonus pay for amazing work on #OSS 00010001057 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001058 +705bonus pay for amazing work on #OSS 00010001058 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001059 +705bonus pay for amazing work on #OSS 00010001059 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001060 +705bonus pay for amazing work on #OSS 00010001060 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001061 +705bonus pay for amazing work on #OSS 00010001061 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001062 +705bonus pay for amazing work on #OSS 00010001062 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001063 +705bonus pay for amazing work on #OSS 00010001063 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001064 +705bonus pay for amazing work on #OSS 00010001064 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001065 +705bonus pay for amazing work on #OSS 00010001065 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001066 +705bonus pay for amazing work on #OSS 00010001066 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001067 +705bonus pay for amazing work on #OSS 00010001067 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001068 +705bonus pay for amazing work on #OSS 00010001068 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001069 +705bonus pay for amazing work on #OSS 00010001069 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001070 +705bonus pay for amazing work on #OSS 00010001070 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001071 +705bonus pay for amazing work on #OSS 00010001071 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001072 +705bonus pay for amazing work on #OSS 00010001072 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001073 +705bonus pay for amazing work on #OSS 00010001073 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001074 +705bonus pay for amazing work on #OSS 00010001074 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001075 +705bonus pay for amazing work on #OSS 00010001075 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001076 +705bonus pay for amazing work on #OSS 00010001076 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001077 +705bonus pay for amazing work on #OSS 00010001077 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001078 +705bonus pay for amazing work on #OSS 00010001078 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001079 +705bonus pay for amazing work on #OSS 00010001079 +62223138010481967038518 0000100000#9h32axwUf3RCZ#Emma Johnson 1121042880001080 +705bonus pay for amazing work on #OSS 00010001080 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#Ava Brown 1121042880001081 +705bonus pay for amazing work on #OSS 00010001081 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001082 +705bonus pay for amazing work on #OSS 00010001082 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001083 +705bonus pay for amazing work on #OSS 00010001083 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001084 +705bonus pay for amazing work on #OSS 00010001084 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001085 +705bonus pay for amazing work on #OSS 00010001085 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001086 +705bonus pay for amazing work on #OSS 00010001086 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001087 +705bonus pay for amazing work on #OSS 00010001087 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001088 +705bonus pay for amazing work on #OSS 00010001088 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001089 +705bonus pay for amazing work on #OSS 00010001089 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001090 +705bonus pay for amazing work on #OSS 00010001090 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001091 +705bonus pay for amazing work on #OSS 00010001091 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001092 +705bonus pay for amazing work on #OSS 00010001092 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001093 +705bonus pay for amazing work on #OSS 00010001093 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001094 +705bonus pay for amazing work on #OSS 00010001094 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001095 +705bonus pay for amazing work on #OSS 00010001095 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001096 +705bonus pay for amazing work on #OSS 00010001096 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001097 +705bonus pay for amazing work on #OSS 00010001097 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001098 +705bonus pay for amazing work on #OSS 00010001098 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001099 +705bonus pay for amazing work on #OSS 00010001099 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001100 +705bonus pay for amazing work on #OSS 00010001100 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001101 +705bonus pay for amazing work on #OSS 00010001101 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001102 +705bonus pay for amazing work on #OSS 00010001102 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001103 +705bonus pay for amazing work on #OSS 00010001103 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001104 +705bonus pay for amazing work on #OSS 00010001104 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001105 +705bonus pay for amazing work on #OSS 00010001105 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001106 +705bonus pay for amazing work on #OSS 00010001106 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001107 +705bonus pay for amazing work on #OSS 00010001107 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001108 +705bonus pay for amazing work on #OSS 00010001108 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001109 +705bonus pay for amazing work on #OSS 00010001109 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001110 +705bonus pay for amazing work on #OSS 00010001110 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001111 +705bonus pay for amazing work on #OSS 00010001111 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001112 +705bonus pay for amazing work on #OSS 00010001112 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001113 +705bonus pay for amazing work on #OSS 00010001113 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001114 +705bonus pay for amazing work on #OSS 00010001114 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001115 +705bonus pay for amazing work on #OSS 00010001115 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001116 +705bonus pay for amazing work on #OSS 00010001116 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001117 +705bonus pay for amazing work on #OSS 00010001117 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001118 +705bonus pay for amazing work on #OSS 00010001118 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001119 +705bonus pay for amazing work on #OSS 00010001119 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001120 +705bonus pay for amazing work on #OSS 00010001120 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001121 +705bonus pay for amazing work on #OSS 00010001121 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001122 +705bonus pay for amazing work on #OSS 00010001122 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001123 +705bonus pay for amazing work on #OSS 00010001123 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001124 +705bonus pay for amazing work on #OSS 00010001124 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001125 +705bonus pay for amazing work on #OSS 00010001125 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001126 +705bonus pay for amazing work on #OSS 00010001126 +62223138010481967038518 0000100000#3ldvwYFeFjwNI#William Brown 1121042880001127 +705bonus pay for amazing work on #OSS 00010001127 +62223138010481967038518 0000100000#rbQebs4WIqZFc#William Thomas 1121042880001128 +705bonus pay for amazing work on #OSS 00010001128 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001129 +705bonus pay for amazing work on #OSS 00010001129 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001130 +705bonus pay for amazing work on #OSS 00010001130 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001131 +705bonus pay for amazing work on #OSS 00010001131 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001132 +705bonus pay for amazing work on #OSS 00010001132 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001133 +705bonus pay for amazing work on #OSS 00010001133 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001134 +705bonus pay for amazing work on #OSS 00010001134 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001135 +705bonus pay for amazing work on #OSS 00010001135 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001136 +705bonus pay for amazing work on #OSS 00010001136 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001137 +705bonus pay for amazing work on #OSS 00010001137 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001138 +705bonus pay for amazing work on #OSS 00010001138 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001139 +705bonus pay for amazing work on #OSS 00010001139 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001140 +705bonus pay for amazing work on #OSS 00010001140 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001141 +705bonus pay for amazing work on #OSS 00010001141 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001142 +705bonus pay for amazing work on #OSS 00010001142 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001143 +705bonus pay for amazing work on #OSS 00010001143 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001144 +705bonus pay for amazing work on #OSS 00010001144 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001145 +705bonus pay for amazing work on #OSS 00010001145 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001146 +705bonus pay for amazing work on #OSS 00010001146 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001147 +705bonus pay for amazing work on #OSS 00010001147 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001148 +705bonus pay for amazing work on #OSS 00010001148 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001149 +705bonus pay for amazing work on #OSS 00010001149 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001150 +705bonus pay for amazing work on #OSS 00010001150 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001151 +705bonus pay for amazing work on #OSS 00010001151 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001152 +705bonus pay for amazing work on #OSS 00010001152 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001153 +705bonus pay for amazing work on #OSS 00010001153 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001154 +705bonus pay for amazing work on #OSS 00010001154 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001155 +705bonus pay for amazing work on #OSS 00010001155 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001156 +705bonus pay for amazing work on #OSS 00010001156 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001157 +705bonus pay for amazing work on #OSS 00010001157 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001158 +705bonus pay for amazing work on #OSS 00010001158 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001159 +705bonus pay for amazing work on #OSS 00010001159 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001160 +705bonus pay for amazing work on #OSS 00010001160 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001161 +705bonus pay for amazing work on #OSS 00010001161 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001162 +705bonus pay for amazing work on #OSS 00010001162 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001163 +705bonus pay for amazing work on #OSS 00010001163 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001164 +705bonus pay for amazing work on #OSS 00010001164 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001165 +705bonus pay for amazing work on #OSS 00010001165 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001166 +705bonus pay for amazing work on #OSS 00010001166 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001167 +705bonus pay for amazing work on #OSS 00010001167 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001168 +705bonus pay for amazing work on #OSS 00010001168 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001169 +705bonus pay for amazing work on #OSS 00010001169 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001170 +705bonus pay for amazing work on #OSS 00010001170 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001171 +705bonus pay for amazing work on #OSS 00010001171 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001172 +705bonus pay for amazing work on #OSS 00010001172 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001173 +705bonus pay for amazing work on #OSS 00010001173 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001174 +705bonus pay for amazing work on #OSS 00010001174 +62223138010481967038518 0000100000#rbQebs4WIqZFc#Ella Thomas 1121042880001175 +705bonus pay for amazing work on #OSS 00010001175 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001176 +705bonus pay for amazing work on #OSS 00010001176 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001177 +705bonus pay for amazing work on #OSS 00010001177 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001178 +705bonus pay for amazing work on #OSS 00010001178 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001179 +705bonus pay for amazing work on #OSS 00010001179 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001180 +705bonus pay for amazing work on #OSS 00010001180 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001181 +705bonus pay for amazing work on #OSS 00010001181 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001182 +705bonus pay for amazing work on #OSS 00010001182 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001183 +705bonus pay for amazing work on #OSS 00010001183 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001184 +705bonus pay for amazing work on #OSS 00010001184 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001185 +705bonus pay for amazing work on #OSS 00010001185 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001186 +705bonus pay for amazing work on #OSS 00010001186 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001187 +705bonus pay for amazing work on #OSS 00010001187 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001188 +705bonus pay for amazing work on #OSS 00010001188 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001189 +705bonus pay for amazing work on #OSS 00010001189 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001190 +705bonus pay for amazing work on #OSS 00010001190 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001191 +705bonus pay for amazing work on #OSS 00010001191 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001192 +705bonus pay for amazing work on #OSS 00010001192 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001193 +705bonus pay for amazing work on #OSS 00010001193 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001194 +705bonus pay for amazing work on #OSS 00010001194 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001195 +705bonus pay for amazing work on #OSS 00010001195 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001196 +705bonus pay for amazing work on #OSS 00010001196 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001197 +705bonus pay for amazing work on #OSS 00010001197 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001198 +705bonus pay for amazing work on #OSS 00010001198 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001199 +705bonus pay for amazing work on #OSS 00010001199 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001200 +705bonus pay for amazing work on #OSS 00010001200 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001201 +705bonus pay for amazing work on #OSS 00010001201 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001202 +705bonus pay for amazing work on #OSS 00010001202 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001203 +705bonus pay for amazing work on #OSS 00010001203 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001204 +705bonus pay for amazing work on #OSS 00010001204 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001205 +705bonus pay for amazing work on #OSS 00010001205 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001206 +705bonus pay for amazing work on #OSS 00010001206 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001207 +705bonus pay for amazing work on #OSS 00010001207 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001208 +705bonus pay for amazing work on #OSS 00010001208 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001209 +705bonus pay for amazing work on #OSS 00010001209 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001210 +705bonus pay for amazing work on #OSS 00010001210 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001211 +705bonus pay for amazing work on #OSS 00010001211 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001212 +705bonus pay for amazing work on #OSS 00010001212 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001213 +705bonus pay for amazing work on #OSS 00010001213 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001214 +705bonus pay for amazing work on #OSS 00010001214 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001215 +705bonus pay for amazing work on #OSS 00010001215 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001216 +705bonus pay for amazing work on #OSS 00010001216 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001217 +705bonus pay for amazing work on #OSS 00010001217 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001218 +705bonus pay for amazing work on #OSS 00010001218 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001219 +705bonus pay for amazing work on #OSS 00010001219 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001220 +705bonus pay for amazing work on #OSS 00010001220 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001221 +705bonus pay for amazing work on #OSS 00010001221 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001222 +705bonus pay for amazing work on #OSS 00010001222 +62223138010481967038518 0000100000#8xo7U8d55wAdj#Mia Wilson 1121042880001223 +705bonus pay for amazing work on #OSS 00010001223 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001224 +705bonus pay for amazing work on #OSS 00010001224 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001225 +705bonus pay for amazing work on #OSS 00010001225 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001226 +705bonus pay for amazing work on #OSS 00010001226 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001227 +705bonus pay for amazing work on #OSS 00010001227 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001228 +705bonus pay for amazing work on #OSS 00010001228 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001229 +705bonus pay for amazing work on #OSS 00010001229 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001230 +705bonus pay for amazing work on #OSS 00010001230 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001231 +705bonus pay for amazing work on #OSS 00010001231 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001232 +705bonus pay for amazing work on #OSS 00010001232 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001233 +705bonus pay for amazing work on #OSS 00010001233 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001234 +705bonus pay for amazing work on #OSS 00010001234 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001235 +705bonus pay for amazing work on #OSS 00010001235 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001236 +705bonus pay for amazing work on #OSS 00010001236 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001237 +705bonus pay for amazing work on #OSS 00010001237 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001238 +705bonus pay for amazing work on #OSS 00010001238 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001239 +705bonus pay for amazing work on #OSS 00010001239 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001240 +705bonus pay for amazing work on #OSS 00010001240 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001241 +705bonus pay for amazing work on #OSS 00010001241 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001242 +705bonus pay for amazing work on #OSS 00010001242 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001243 +705bonus pay for amazing work on #OSS 00010001243 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001244 +705bonus pay for amazing work on #OSS 00010001244 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001245 +705bonus pay for amazing work on #OSS 00010001245 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001246 +705bonus pay for amazing work on #OSS 00010001246 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001247 +705bonus pay for amazing work on #OSS 00010001247 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001248 +705bonus pay for amazing work on #OSS 00010001248 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001249 +705bonus pay for amazing work on #OSS 00010001249 +62223138010481967038518 0000100000#wuDASrtkr9R1U#William Brown 1121042880001250 +705bonus pay for amazing work on #OSS 00010001250 +82000025008922512500000000000000000125000000121042882 121042880000004 +9000004001001000100005690050000000000000000000500000000 diff --git a/cmd/writeACH/202001172118.ach b/cmd/writeACH/202001172118.ach new file mode 100644 index 000000000..a451ecd6f --- /dev/null +++ b/cmd/writeACH/202001172118.ach @@ -0,0 +1,10010 @@ +101 231380104 1210428822001171418A094101Citadel Wells Fargo +5200Wells Fargo 121042882 PPDTrans. Des 200118 1121042880000001 +622231380104032812267171545110000000000#477301545636# Aubrey Anderson 1121042880000001 +705Shriekerdew bonus pay for amazing work on #OSS 00010000001 +622231380104338814313314487880000000000#132522337527# Mia Taylor 1121042880000002 +705Spritefuschia bonus pay for amazing work on #OSS 00010000002 +622231380104211052814810278880000000000#084775043065# Emma Martinez 1121042880000003 +705Koalacitrine bonus pay for amazing work on #OSS 00010000003 +622231380104277860824861066330000000000#332863025108# Olivia Jones 1121042880000004 +705Horsesour bonus pay for amazing work on #OSS 00010000004 +622231380104317667724020104350000000000#486120124355# Lily Williams 1121042880000005 +705Pipersunset bonus pay for amazing work on #OSS 00010000005 +622231380104047664204642367580000000000#763745066301# Mason Smith 1121042880000006 +705Fancierrapid bonus pay for amazing work on #OSS 00010000006 +622231380104855346843716005760000000000#255707605155# Olivia Harris 1121042880000007 +705Knifeslime bonus pay for amazing work on #OSS 00010000007 +622231380104580142355872244530000000000#166260206075# Benjamin Garcia 1121042880000008 +705Chopperdeep bonus pay for amazing work on #OSS 00010000008 +622231380104757176036772108230000000000#525420712615# Joseph Davis 1121042880000009 +705Shakerpuzzle bonus pay for amazing work on #OSS 00010000009 +622231380104611367102770303670000000000#854870601767# Ethan Williams 1121042880000010 +705Facelove bonus pay for amazing work on #OSS 00010000010 +622231380104016240356128267380000000000#337817857405# David Brown 1121042880000011 +705Killerstar bonus pay for amazing work on #OSS 00010000011 +622231380104343664743680132400000000000#608561220086# Ella Johnson 1121042880000012 +705Fairyrift bonus pay for amazing work on #OSS 00010000012 +622231380104805153026308585850000000000#412058825764# Daniel Harris 1121042880000013 +705Donkeyprickle bonus pay for amazing work on #OSS 00010000013 +622231380104156828345740452220000000000#705884646276# Sofia Garcia 1121042880000014 +705Viperribbon bonus pay for amazing work on #OSS 00010000014 +622231380104808872574552407180000000000#810752033826# Jacob Davis 1121042880000015 +705Rippernotch bonus pay for amazing work on #OSS 00010000015 +622231380104742070828755623800000000000#014533122444# Mason Garcia 1121042880000016 +705Reaperpebble bonus pay for amazing work on #OSS 00010000016 +622231380104777381316552810510000000000#452422348534# Zoey Brown 1121042880000017 +705Tailbig bonus pay for amazing work on #OSS 00010000017 +622231380104705730037107357780000000000#335874203530# Charlotte Miller 1121042880000018 +705Friendsugar bonus pay for amazing work on #OSS 00010000018 +622231380104501488160471122410000000000#741200210200# Emily White 1121042880000019 +705Lighterlaser bonus pay for amazing work on #OSS 00010000019 +622231380104668555605535512110000000000#356835706162# Jacob Brown 1121042880000020 +705Dragonlie bonus pay for amazing work on #OSS 00010000020 +622231380104056527325747124710000000000#642811183712# Lily Thomas 1121042880000021 +705Edgeplatinum bonus pay for amazing work on #OSS 00010000021 +622231380104770722087103505780000000000#072051300553# Joseph Wilson 1121042880000022 +705Stallionmisty bonus pay for amazing work on #OSS 00010000022 +622231380104328074105834101210000000000#715348446480# Mason Martinez 1121042880000023 +705Leadersprout bonus pay for amazing work on #OSS 00010000023 +622231380104676227018811710580000000000#644206571227# William Williams 1121042880000024 +705Snapseason bonus pay for amazing work on #OSS 00010000024 +622231380104847174867774560020000000000#085188678838# Mia Moore 1121042880000025 +705Roarcliff bonus pay for amazing work on #OSS 00010000025 +622231380104202148431211230780000000000#104851155311# Sofia Miller 1121042880000026 +705Chillerfast bonus pay for amazing work on #OSS 00010000026 +622231380104331888483366023210000000000#554641765453# Addison White 1121042880000027 +705Flyfield bonus pay for amazing work on #OSS 00010000027 +622231380104825343442210563470000000000#258701755522# James Wilson 1121042880000028 +705Pawlight bonus pay for amazing work on #OSS 00010000028 +622231380104285083678328534740000000000#800310663011# Joseph Robinson 1121042880000029 +705Cockatoooil bonus pay for amazing work on #OSS 00010000029 +622231380104175857745700513070000000000#273405272385# Ella Martinez 1121042880000030 +705Stalliongrave bonus pay for amazing work on #OSS 00010000030 +622231380104222730137654465670000000000#226688734714# Charlotte Anderson 1121042880000031 +705Hoofsquare bonus pay for amazing work on #OSS 00010000031 +622231380104843378803040702770000000000#774130114687# William Thomas 1121042880000032 +705Dutchessforest bonus pay for amazing work on #OSS 00010000032 +622231380104885830758606852720000000000#213636780770# Elizabeth Jackson 1121042880000033 +705Kangarooquartz bonus pay for amazing work on #OSS 00010000033 +622231380104738736870257373200000000000#644537436443# Sophia Johnson 1121042880000034 +705Howlerchatter bonus pay for amazing work on #OSS 00010000034 +622231380104300266060322023650000000000#552686460717# Emily Moore 1121042880000035 +705Whiprust bonus pay for amazing work on #OSS 00010000035 +622231380104365607837122856600000000000#068646780744# James Moore 1121042880000036 +705Beakpie bonus pay for amazing work on #OSS 00010000036 +622231380104415865150320020530000000000#327073628830# Lily Johnson 1121042880000037 +705Forgersilk bonus pay for amazing work on #OSS 00010000037 +622231380104580868537502688480000000000#424652524223# Ava White 1121042880000038 +705Spritearrow bonus pay for amazing work on #OSS 00010000038 +622231380104785575561028005370000000000#643735385827# Zoey Williams 1121042880000039 +705Sentryred bonus pay for amazing work on #OSS 00010000039 +622231380104066383786623835170000000000#804755643454# Ethan Johnson 1121042880000040 +705Bitefir bonus pay for amazing work on #OSS 00010000040 +622231380104413776571212460460000000000#726360516410# Elizabeth Williams 1121042880000041 +705Crestwind bonus pay for amazing work on #OSS 00010000041 +622231380104700230620443138070000000000#870434564503# Ethan Jackson 1121042880000042 +705Seerfair bonus pay for amazing work on #OSS 00010000042 +622231380104482385670358455040000000000#540510218451# Aiden Anderson 1121042880000043 +705Stingfluff bonus pay for amazing work on #OSS 00010000043 +622231380104647138786203416680000000000#223315630113# Avery Taylor 1121042880000044 +705Shiftpsychadelic bonus pay for amazing work on #OSS 00010000044 +622231380104860770308143237080000000000#884256365321# Avery Jackson 1121042880000045 +705Followermint bonus pay for amazing work on #OSS 00010000045 +622231380104733722106058863650000000000#703257712253# Emma Johnson 1121042880000046 +705Speartwilight bonus pay for amazing work on #OSS 00010000046 +622231380104241664326660564060000000000#378866358362# David Garcia 1121042880000047 +705Wanderermagenta bonus pay for amazing work on #OSS 00010000047 +622231380104887665725852164800000000000#702225025855# Michael Johnson 1121042880000048 +705Ridermint bonus pay for amazing work on #OSS 00010000048 +622231380104245764862704631650000000000#021863731337# Sophia Martin 1121042880000049 +705Burnchain bonus pay for amazing work on #OSS 00010000049 +622231380104038816754116645200000000000#583033823302# Elizabeth Jackson 1121042880000050 +705Ratjelly bonus pay for amazing work on #OSS 00010000050 +622231380104427448201462367160000000000#132023050133# Sofia White 1121042880000051 +705Terrierfrost bonus pay for amazing work on #OSS 00010000051 +622231380104305666034685580440000000000#333752673515# Ella Jackson 1121042880000052 +705Kingflannel bonus pay for amazing work on #OSS 00010000052 +622231380104877244882083316730000000000#807373348803# Avery Davis 1121042880000053 +705Scaledenim bonus pay for amazing work on #OSS 00010000053 +622231380104355270763357118850000000000#780517128186# Zoey Williams 1121042880000054 +705Toucanmango bonus pay for amazing work on #OSS 00010000054 +622231380104815274206644147830000000000#026157515484# Elijah White 1121042880000055 +705Speakerwhip bonus pay for amazing work on #OSS 00010000055 +622231380104463811470561301120000000000#074338448712# Elizabeth Wilson 1121042880000056 +705Handcitrine bonus pay for amazing work on #OSS 00010000056 +622231380104846562384675873140000000000#664501172650# Mason Davis 1121042880000057 +705Museebony bonus pay for amazing work on #OSS 00010000057 +622231380104668475886128830320000000000#026011006402# Daniel Miller 1121042880000058 +705Ravenbloom bonus pay for amazing work on #OSS 00010000058 +622231380104133200224887617480000000000#213210584345# Avery Smith 1121042880000059 +705Thumbfish bonus pay for amazing work on #OSS 00010000059 +622231380104715804566661310630000000000#612463051482# Emma White 1121042880000060 +705Knaveveil bonus pay for amazing work on #OSS 00010000060 +622231380104427362440485228360000000000#104688175340# Avery White 1121042880000061 +705Riderfossil bonus pay for amazing work on #OSS 00010000061 +622231380104077373100541555080000000000#606462823183# Michael Smith 1121042880000062 +705Stormsilver bonus pay for amazing work on #OSS 00010000062 +622231380104847030280781775310000000000#201247075634# Ella Davis 1121042880000063 +705Spearriver bonus pay for amazing work on #OSS 00010000063 +622231380104643507542038464170000000000#008668844537# Mason Thomas 1121042880000064 +705Carpetsplash bonus pay for amazing work on #OSS 00010000064 +622231380104403234461620868110000000000#075425455432# Jayden Wilson 1121042880000065 +705Slothsilent bonus pay for amazing work on #OSS 00010000065 +622231380104805768443647862630000000000#526216760706# Ethan Garcia 1121042880000066 +705Scribepale bonus pay for amazing work on #OSS 00010000066 +622231380104076318284622184600000000000#341265882018# Natalie Moore 1121042880000067 +705Scardesert bonus pay for amazing work on #OSS 00010000067 +622231380104335316816133623830000000000#570518515057# Ethan White 1121042880000068 +705Otterrampant bonus pay for amazing work on #OSS 00010000068 +622231380104173128760284603500000000000#131102461161# Avery White 1121042880000069 +705Oxjuniper bonus pay for amazing work on #OSS 00010000069 +622231380104257488618400157510000000000#723301026163# David Anderson 1121042880000070 +705Fairybranch bonus pay for amazing work on #OSS 00010000070 +622231380104405650642718563340000000000#358603878087# Ava Brown 1121042880000071 +705Princestar bonus pay for amazing work on #OSS 00010000071 +622231380104473501113630827800000000000#881864147660# Jacob Thompson 1121042880000072 +705Unicornrift bonus pay for amazing work on #OSS 00010000072 +622231380104142878837655658680000000000#080464785303# Alexander Anderson 1121042880000073 +705Devourerstream bonus pay for amazing work on #OSS 00010000073 +622231380104847647257713723800000000000#662355757068# Jayden Thomas 1121042880000074 +705Trackfern bonus pay for amazing work on #OSS 00010000074 +622231380104000858825257236180000000000#541708143558# Zoey Brown 1121042880000075 +705Swoopfree bonus pay for amazing work on #OSS 00010000075 +622231380104752615318711074460000000000#660433141630# Alexander Wilson 1121042880000076 +705Slavewood bonus pay for amazing work on #OSS 00010000076 +622231380104854878525851853200000000000#050846173418# Sophia Johnson 1121042880000077 +705Edgedog bonus pay for amazing work on #OSS 00010000077 +622231380104560141002047360650000000000#843231473062# Emily White 1121042880000078 +705Ravencotton bonus pay for amazing work on #OSS 00010000078 +622231380104752316768376214130000000000#038227465124# Aiden Anderson 1121042880000079 +705Nosecrocus bonus pay for amazing work on #OSS 00010000079 +622231380104820747176738277260000000000#225133853008# Mason Moore 1121042880000080 +705Curtainlapis bonus pay for amazing work on #OSS 00010000080 +622231380104435042825463762270000000000#470845286541# Olivia Johnson 1121042880000081 +705Lordcliff bonus pay for amazing work on #OSS 00010000081 +622231380104142375264047060170000000000#454120250378# Ella Garcia 1121042880000082 +705Divealmond bonus pay for amazing work on #OSS 00010000082 +622231380104724685848443042320000000000#384121775164# Zoey Davis 1121042880000083 +705Tigershadow bonus pay for amazing work on #OSS 00010000083 +622231380104365467374224237760000000000#048084222752# Emma Johnson 1121042880000084 +705Pantherdenim bonus pay for amazing work on #OSS 00010000084 +622231380104863715355255530080000000000#553024515634# Emma Thompson 1121042880000085 +705Footcurly bonus pay for amazing work on #OSS 00010000085 +622231380104643277634664758530000000000#640382852837# Emma Robinson 1121042880000086 +705Razorash bonus pay for amazing work on #OSS 00010000086 +622231380104773387000582566660000000000#410187050565# Madison Martinez 1121042880000087 +705Iguanarogue bonus pay for amazing work on #OSS 00010000087 +622231380104032828287057410660000000000#556315808128# Ella Robinson 1121042880000088 +705Dolphintabby bonus pay for amazing work on #OSS 00010000088 +622231380104180650417752015550000000000#848507480520# Ella Miller 1121042880000089 +705Flasheralder bonus pay for amazing work on #OSS 00010000089 +622231380104772423680170080830000000000#604387565485# David Martinez 1121042880000090 +705Panthershell bonus pay for amazing work on #OSS 00010000090 +622231380104237873318882601530000000000#318360665122# Noah Jones 1121042880000091 +705Skinnernight bonus pay for amazing work on #OSS 00010000091 +622231380104447218335388455210000000000#553227504427# Joshua Robinson 1121042880000092 +705Swooporchid bonus pay for amazing work on #OSS 00010000092 +622231380104406560634546051170000000000#544631448571# Matthew Taylor 1121042880000093 +705Razorcopper bonus pay for amazing work on #OSS 00010000093 +622231380104200175630436705380000000000#452011886286# Sofia Jackson 1121042880000094 +705Spritepewter bonus pay for amazing work on #OSS 00010000094 +622231380104677537558083401670000000000#377380867224# Joshua Davis 1121042880000095 +705Ninjacandle bonus pay for amazing work on #OSS 00010000095 +622231380104327410876233045410000000000#432815580551# Abigail Wilson 1121042880000096 +705Wolfslash bonus pay for amazing work on #OSS 00010000096 +622231380104248616400657543700000000000#108615816115# Benjamin Miller 1121042880000097 +705Neckbronze bonus pay for amazing work on #OSS 00010000097 +622231380104028054634647687880000000000#077306324778# Chloe Brown 1121042880000098 +705Hidejuniper bonus pay for amazing work on #OSS 00010000098 +622231380104838431538034844220000000000#477820157718# Ella White 1121042880000099 +705Killerscratch bonus pay for amazing work on #OSS 00010000099 +622231380104781416240563057740000000000#254202842643# Daniel Anderson 1121042880000100 +705Scribemoor bonus pay for amazing work on #OSS 00010000100 +622231380104616666277140873420000000000#422731650330# Mia Moore 1121042880000101 +705Wolfcoral bonus pay for amazing work on #OSS 00010000101 +622231380104815787688070740580000000000#246828858657# Charlotte Anderson 1121042880000102 +705Pantherflicker bonus pay for amazing work on #OSS 00010000102 +622231380104556511414663454670000000000#548280434021# Elizabeth Wilson 1121042880000103 +705Snappermalachite bonus pay for amazing work on #OSS 00010000103 +622231380104111812417867512110000000000#183132428628# Ella Thomas 1121042880000104 +705Slavenickel bonus pay for amazing work on #OSS 00010000104 +622231380104721884762818418150000000000#571437057644# Aubrey Brown 1121042880000105 +705Tonguecrocus bonus pay for amazing work on #OSS 00010000105 +622231380104813071670483025830000000000#142166444118# Joseph Moore 1121042880000106 +705Huggerjasper bonus pay for amazing work on #OSS 00010000106 +622231380104302451547866675310000000000#672501111665# Michael Martinez 1121042880000107 +705Paladinbrave bonus pay for amazing work on #OSS 00010000107 +622231380104262358674463406740000000000#241353874054# Ella Martinez 1121042880000108 +705Tailgrave bonus pay for amazing work on #OSS 00010000108 +622231380104330862706515023210000000000#885428610213# Matthew Wilson 1121042880000109 +705Mustangstump bonus pay for amazing work on #OSS 00010000109 +622231380104635553428671612640000000000#418173201824# Joseph Garcia 1121042880000110 +705Friendkeen bonus pay for amazing work on #OSS 00010000110 +622231380104352766535458257610000000000#177752667332# Andrew Anderson 1121042880000111 +705Diverquill bonus pay for amazing work on #OSS 00010000111 +622231380104862050267480636370000000000#003740858511# Ella Johnson 1121042880000112 +705Gamblerglen bonus pay for amazing work on #OSS 00010000112 +622231380104043488261474823460000000000#700641673188# Ella Martinez 1121042880000113 +705Hyenavolcano bonus pay for amazing work on #OSS 00010000113 +622231380104473250856505133210000000000#325327872465# Daniel Harris 1121042880000114 +705Grinstone bonus pay for amazing work on #OSS 00010000114 +622231380104880201317746331410000000000#356753584554# Aiden Miller 1121042880000115 +705Musepuzzle bonus pay for amazing work on #OSS 00010000115 +622231380104880362686358863750000000000#415300648720# Ella Smith 1121042880000116 +705Knifecold bonus pay for amazing work on #OSS 00010000116 +622231380104732455365304015070000000000#540876461503# Emily Taylor 1121042880000117 +705Chopperspeckle bonus pay for amazing work on #OSS 00010000117 +622231380104157856717037061480000000000#662778131575# Abigail Robinson 1121042880000118 +705Hairnavy bonus pay for amazing work on #OSS 00010000118 +622231380104273278163542306580000000000#415546488253# Ella Moore 1121042880000119 +705Eaternimble bonus pay for amazing work on #OSS 00010000119 +622231380104535042056653807240000000000#575285857847# David Harris 1121042880000120 +705Dragonrift bonus pay for amazing work on #OSS 00010000120 +622231380104520555518076073310000000000#172478587852# Mason Anderson 1121042880000121 +705Saverneon bonus pay for amazing work on #OSS 00010000121 +622231380104586063584526562740000000000#488858618266# William Jackson 1121042880000122 +705Ridercloud bonus pay for amazing work on #OSS 00010000122 +622231380104563234426058767840000000000#625817818702# Olivia Taylor 1121042880000123 +705Geckoquick bonus pay for amazing work on #OSS 00010000123 +622231380104703120886172450630000000000#313500367383# Emily Davis 1121042880000124 +705Mousedune bonus pay for amazing work on #OSS 00010000124 +622231380104848271812543213080000000000#055173644727# Andrew Robinson 1121042880000125 +705Tonguepeach bonus pay for amazing work on #OSS 00010000125 +622231380104774036048470674730000000000#481500021868# Daniel Martin 1121042880000126 +705Riderobsidian bonus pay for amazing work on #OSS 00010000126 +622231380104646478411707453300000000000#588402806880# Mia Wilson 1121042880000127 +705Hyenakiwi bonus pay for amazing work on #OSS 00010000127 +622231380104608070662380013800000000000#260177172520# Andrew Brown 1121042880000128 +705Glazersponge bonus pay for amazing work on #OSS 00010000128 +622231380104227433551086316340000000000#663566164100# Madison Thomas 1121042880000129 +705Ninjaripple bonus pay for amazing work on #OSS 00010000129 +622231380104677132531243737700000000000#818564847070# Elizabeth Jackson 1121042880000130 +705Razorpuzzle bonus pay for amazing work on #OSS 00010000130 +622231380104731223056020188840000000000#700345333402# Benjamin Martinez 1121042880000131 +705Monkeyforest bonus pay for amazing work on #OSS 00010000131 +622231380104572422155302575330000000000#748054376413# Mia Harris 1121042880000132 +705Unicornbitter bonus pay for amazing work on #OSS 00010000132 +622231380104140015662861100500000000000#505023528417# Emma Robinson 1121042880000133 +705Minnowiridescent bonus pay for amazing work on #OSS 00010000133 +622231380104143820757667276340000000000#128842365240# Jacob Harris 1121042880000134 +705Chesttitanium bonus pay for amazing work on #OSS 00010000134 +622231380104170051034070727340000000000#714743368874# Avery Anderson 1121042880000135 +705Snarlballistic bonus pay for amazing work on #OSS 00010000135 +622231380104521125265757261380000000000#631360706380# Olivia Martinez 1121042880000136 +705Friendlie bonus pay for amazing work on #OSS 00010000136 +622231380104450707434158270810000000000#346821015738# James Garcia 1121042880000137 +705Toeplatinum bonus pay for amazing work on #OSS 00010000137 +622231380104452537388385355460000000000#178806683665# Benjamin Jackson 1121042880000138 +705Chopperlunar bonus pay for amazing work on #OSS 00010000138 +622231380104103581227226738550000000000#575857171307# Joseph Thomas 1121042880000139 +705Knightsulpher bonus pay for amazing work on #OSS 00010000139 +622231380104754382242761014780000000000#382144728403# Olivia Miller 1121042880000140 +705Lanternshine bonus pay for amazing work on #OSS 00010000140 +622231380104602416804446216570000000000#366115825618# Jacob Harris 1121042880000141 +705Rovereast bonus pay for amazing work on #OSS 00010000141 +622231380104520583621372303470000000000#865061285571# Sophia Thompson 1121042880000142 +705Singerobsidian bonus pay for amazing work on #OSS 00010000142 +622231380104836546250635085620000000000#121157610300# Noah Williams 1121042880000143 +705Shiftmotley bonus pay for amazing work on #OSS 00010000143 +622231380104216207517564714450000000000#775552314282# David Harris 1121042880000144 +705Scribewhite bonus pay for amazing work on #OSS 00010000144 +622231380104761808516051567680000000000#343335754260# Jayden Robinson 1121042880000145 +705Ribplume bonus pay for amazing work on #OSS 00010000145 +622231380104723816156524172380000000000#552432072044# Mia Williams 1121042880000146 +705Ottereast bonus pay for amazing work on #OSS 00010000146 +622231380104726161240473160320000000000#135618500552# Joseph Jones 1121042880000147 +705Sentrypool bonus pay for amazing work on #OSS 00010000147 +622231380104173862325188816730000000000#455316313133# Benjamin Jones 1121042880000148 +705Lionlizard bonus pay for amazing work on #OSS 00010000148 +622231380104888582303223730370000000000#733678333853# Natalie Wilson 1121042880000149 +705Marefoil bonus pay for amazing work on #OSS 00010000149 +622231380104140786684367488420000000000#407104383413# Alexander Anderson 1121042880000150 +705Pumasprout bonus pay for amazing work on #OSS 00010000150 +622231380104416232318657884860000000000#378308216608# Andrew Brown 1121042880000151 +705Witchebony bonus pay for amazing work on #OSS 00010000151 +622231380104467262241407308570000000000#124782618117# Sophia Robinson 1121042880000152 +705Neckpeppermint bonus pay for amazing work on #OSS 00010000152 +622231380104211260848262780270000000000#016870473410# Anthony Harris 1121042880000153 +705Pigpineapple bonus pay for amazing work on #OSS 00010000153 +622231380104017512571344674460000000000#160305376061# Jacob Thomas 1121042880000154 +705Razornavy bonus pay for amazing work on #OSS 00010000154 +622231380104236623826187521600000000000#205700735071# Natalie Martin 1121042880000155 +705Lionlace bonus pay for amazing work on #OSS 00010000155 +622231380104038077628584047000000000000#503763521000# Lily Johnson 1121042880000156 +705Sentryequinox bonus pay for amazing work on #OSS 00010000156 +622231380104303557252647471200000000000#771617255176# Madison Anderson 1121042880000157 +705Seekerebony bonus pay for amazing work on #OSS 00010000157 +622231380104003425752278614550000000000#534064178816# Joseph Martin 1121042880000158 +705Scorpionstitch bonus pay for amazing work on #OSS 00010000158 +622231380104223813567186278520000000000#611350542681# Jacob Harris 1121042880000159 +705Mistressmaze bonus pay for amazing work on #OSS 00010000159 +622231380104311245888022387280000000000#753510466733# David Robinson 1121042880000160 +705Ducksulpher bonus pay for amazing work on #OSS 00010000160 +622231380104010857580114316770000000000#405653867005# Aubrey Thompson 1121042880000161 +705Roverstellar bonus pay for amazing work on #OSS 00010000161 +622231380104201405771778006810000000000#720743238576# Isabella Martin 1121042880000162 +705Stagsilent bonus pay for amazing work on #OSS 00010000162 +622231380104188556646726263260000000000#607870415575# Joseph Moore 1121042880000163 +705Guardianripple bonus pay for amazing work on #OSS 00010000163 +622231380104222833647207121530000000000#683315642456# David Brown 1121042880000164 +705Thumbjelly bonus pay for amazing work on #OSS 00010000164 +622231380104318632156553441880000000000#545358861566# Michael Moore 1121042880000165 +705Masterjewel bonus pay for amazing work on #OSS 00010000165 +622231380104882240037334336010000000000#161402776722# Madison Robinson 1121042880000166 +705Crystalscythe bonus pay for amazing work on #OSS 00010000166 +622231380104208863718768152100000000000#322787366717# Alexander Jones 1121042880000167 +705Sagelunar bonus pay for amazing work on #OSS 00010000167 +622231380104523843463528024210000000000#131026778602# David Thompson 1121042880000168 +705Lightningboulder bonus pay for amazing work on #OSS 00010000168 +622231380104702037280222836120000000000#356778374181# Charlotte White 1121042880000169 +705Bellfreckle bonus pay for amazing work on #OSS 00010000169 +622231380104613443762772541800000000000#031806310281# William Martinez 1121042880000170 +705Takernickel bonus pay for amazing work on #OSS 00010000170 +622231380104306671627342643520000000000#664844684085# William Davis 1121042880000171 +705Warriortitanium bonus pay for amazing work on #OSS 00010000171 +622231380104711634032388153630000000000#058736723374# James Robinson 1121042880000172 +705Boltmeadow bonus pay for amazing work on #OSS 00010000172 +622231380104643738567144675680000000000#283688243536# Isabella Thomas 1121042880000173 +705Soarercandle bonus pay for amazing work on #OSS 00010000173 +622231380104513128564251888820000000000#582144586663# David Garcia 1121042880000174 +705Moosemeadow bonus pay for amazing work on #OSS 00010000174 +622231380104451220014444308230000000000#536125365214# Michael Davis 1121042880000175 +705Princestump bonus pay for amazing work on #OSS 00010000175 +622231380104374788531765411870000000000#852532734317# William Harris 1121042880000176 +705Scourgegeode bonus pay for amazing work on #OSS 00010000176 +622231380104708504345214113440000000000#152212026846# Abigail Johnson 1121042880000177 +705Tailbald bonus pay for amazing work on #OSS 00010000177 +622231380104466448232677422760000000000#353626737536# Isabella Thomas 1121042880000178 +705Koalaplaid bonus pay for amazing work on #OSS 00010000178 +622231380104205314765176321150000000000#520386811180# Emma Miller 1121042880000179 +705Weedfoam bonus pay for amazing work on #OSS 00010000179 +622231380104181286733277726170000000000#560053126670# Elizabeth Taylor 1121042880000180 +705Kangarooflax bonus pay for amazing work on #OSS 00010000180 +622231380104737120022302746610000000000#503430308837# Joshua Robinson 1121042880000181 +705Speakerspark bonus pay for amazing work on #OSS 00010000181 +622231380104213826501402546040000000000#576156514234# Isabella Smith 1121042880000182 +705Pumafrill bonus pay for amazing work on #OSS 00010000182 +622231380104121663586283331210000000000#713365557732# Elijah Garcia 1121042880000183 +705Healerlapis bonus pay for amazing work on #OSS 00010000183 +622231380104267002110541403650000000000#700715153453# Madison Thompson 1121042880000184 +705Antlerorange bonus pay for amazing work on #OSS 00010000184 +622231380104502885507642710650000000000#143263348376# Mason Anderson 1121042880000185 +705Chattercyan bonus pay for amazing work on #OSS 00010000185 +622231380104467802000873423770000000000#008537661577# Olivia Miller 1121042880000186 +705Skinnersolstice bonus pay for amazing work on #OSS 00010000186 +622231380104432866416106435880000000000#553606246275# Matthew Smith 1121042880000187 +705Slothshell bonus pay for amazing work on #OSS 00010000187 +622231380104224122631246127180000000000#530780605840# Jacob Martin 1121042880000188 +705Dartquark bonus pay for amazing work on #OSS 00010000188 +622231380104022724453785680500000000000#818311442760# Ava Wilson 1121042880000189 +705Musepepper bonus pay for amazing work on #OSS 00010000189 +622231380104007432334372718240000000000#111880623334# David Davis 1121042880000190 +705Scourgewarp bonus pay for amazing work on #OSS 00010000190 +622231380104255422560327451810000000000#128778478075# Emily Miller 1121042880000191 +705Pegasusholly bonus pay for amazing work on #OSS 00010000191 +622231380104444262800874128260000000000#435575451581# Benjamin White 1121042880000192 +705Seedshort bonus pay for amazing work on #OSS 00010000192 +622231380104306621264424046480000000000#851341810333# Chloe White 1121042880000193 +705Facesand bonus pay for amazing work on #OSS 00010000193 +622231380104017641381278220620000000000#322350506511# Jacob Wilson 1121042880000194 +705Carptar bonus pay for amazing work on #OSS 00010000194 +622231380104828453707354512610000000000#712087064466# Aiden Thompson 1121042880000195 +705Cloakdust bonus pay for amazing work on #OSS 00010000195 +622231380104640714775264557580000000000#026442316857# Alexander Wilson 1121042880000196 +705Kickerroot bonus pay for amazing work on #OSS 00010000196 +622231380104287525862646473220000000000#187800113433# Abigail Brown 1121042880000197 +705Gamblersapphire bonus pay for amazing work on #OSS 00010000197 +622231380104145356385651111080000000000#372126513288# James Robinson 1121042880000198 +705Throatstellar bonus pay for amazing work on #OSS 00010000198 +622231380104650147576352020680000000000#203143411840# David Smith 1121042880000199 +705Songwhip bonus pay for amazing work on #OSS 00010000199 +622231380104517266055667747100000000000#532705831215# Jacob Smith 1121042880000200 +705Knifecyan bonus pay for amazing work on #OSS 00010000200 +622231380104506181024470465880000000000#501566308372# Sophia Thompson 1121042880000201 +705Batstellar bonus pay for amazing work on #OSS 00010000201 +622231380104177141522270333120000000000#336573580721# William Davis 1121042880000202 +705Kickermagenta bonus pay for amazing work on #OSS 00010000202 +622231380104818050833486318720000000000#245183407085# Lily Moore 1121042880000203 +705Bisongranite bonus pay for amazing work on #OSS 00010000203 +622231380104240721626143051180000000000#370785170707# Olivia White 1121042880000204 +705Oriolewheat bonus pay for amazing work on #OSS 00010000204 +622231380104564641721316240070000000000#223045655614# Liam Johnson 1121042880000205 +705Killerquark bonus pay for amazing work on #OSS 00010000205 +622231380104322756004363772140000000000#735641680515# Alexander Moore 1121042880000206 +705Spearpolar bonus pay for amazing work on #OSS 00010000206 +622231380104813724800028853700000000000#144013178711# Emma Moore 1121042880000207 +705Capcake bonus pay for amazing work on #OSS 00010000207 +622231380104233346670471413850000000000#708616641340# Jayden Wilson 1121042880000208 +705Scarlime bonus pay for amazing work on #OSS 00010000208 +622231380104180168460881583620000000000#214362382201# Isabella Martin 1121042880000209 +705Arrowfreckle bonus pay for amazing work on #OSS 00010000209 +622231380104241854825184262650000000000#181610408060# Noah Jackson 1121042880000210 +705Thorndeep bonus pay for amazing work on #OSS 00010000210 +622231380104744682836551166430000000000#257678430148# Noah Brown 1121042880000211 +705Spikeebony bonus pay for amazing work on #OSS 00010000211 +622231380104562778570020415210000000000#534033050511# Avery Smith 1121042880000212 +705Howlerring bonus pay for amazing work on #OSS 00010000212 +622231380104346142556053475640000000000#567133748704# Natalie Garcia 1121042880000213 +705Slayerpattern bonus pay for amazing work on #OSS 00010000213 +622231380104733861108006321670000000000#156301230324# Matthew Moore 1121042880000214 +705Ladyshade bonus pay for amazing work on #OSS 00010000214 +622231380104413737404688157620000000000#773774518262# Benjamin Thomas 1121042880000215 +705Scalesleet bonus pay for amazing work on #OSS 00010000215 +622231380104500337250738883610000000000#223780473433# Anthony Martin 1121042880000216 +705Dogpurple bonus pay for amazing work on #OSS 00010000216 +622231380104227001305134661610000000000#871548786556# Emily Davis 1121042880000217 +705Dolphinfree bonus pay for amazing work on #OSS 00010000217 +622231380104842050265531888600000000000#864545464676# Sofia Harris 1121042880000218 +705Thornbronze bonus pay for amazing work on #OSS 00010000218 +622231380104385885467070184460000000000#156565801203# Avery Miller 1121042880000219 +705Scowlchecker bonus pay for amazing work on #OSS 00010000219 +622231380104080122064366832550000000000#781133214584# Matthew Moore 1121042880000220 +705Ravenebony bonus pay for amazing work on #OSS 00010000220 +622231380104783840051623576260000000000#727560844415# Chloe Miller 1121042880000221 +705Bugwood bonus pay for amazing work on #OSS 00010000221 +622231380104556240147416003530000000000#701024500214# Isabella Taylor 1121042880000222 +705Terrierjet bonus pay for amazing work on #OSS 00010000222 +622231380104114244563572143450000000000#262414850072# Mia Jones 1121042880000223 +705Elkivory bonus pay for amazing work on #OSS 00010000223 +622231380104203814188636364580000000000#488804038558# Isabella Garcia 1121042880000224 +705Razorchip bonus pay for amazing work on #OSS 00010000224 +622231380104173067236558120540000000000#885415428408# Chloe Taylor 1121042880000225 +705Drifteralpine bonus pay for amazing work on #OSS 00010000225 +622231380104424847645616678840000000000#077275584852# Charlotte Garcia 1121042880000226 +705Pythonhate bonus pay for amazing work on #OSS 00010000226 +622231380104751630513711558340000000000#651804715581# David Johnson 1121042880000227 +705Shiftmuck bonus pay for amazing work on #OSS 00010000227 +622231380104624136176646687210000000000#853424372686# Matthew Miller 1121042880000228 +705Crystaldark bonus pay for amazing work on #OSS 00010000228 +622231380104131142423368662050000000000#048561524414# Benjamin Martinez 1121042880000229 +705Biterdust bonus pay for amazing work on #OSS 00010000229 +622231380104776232126766583700000000000#514215168342# Ethan Martinez 1121042880000230 +705Stingroad bonus pay for amazing work on #OSS 00010000230 +622231380104573344630480727050000000000#251073483687# James Miller 1121042880000231 +705Raptorwax bonus pay for amazing work on #OSS 00010000231 +622231380104325207134540774800000000000#081377471208# David Thomas 1121042880000232 +705Questerswamp bonus pay for amazing work on #OSS 00010000232 +622231380104377648576611034160000000000#304618855474# Noah Robinson 1121042880000233 +705Boltrune bonus pay for amazing work on #OSS 00010000233 +622231380104887027165768081420000000000#374830618735# Charlotte Robinson 1121042880000234 +705Followerdiamond bonus pay for amazing work on #OSS 00010000234 +622231380104148662162860456480000000000#565727316641# Emily Thompson 1121042880000235 +705Chanterround bonus pay for amazing work on #OSS 00010000235 +622231380104566710402878242270000000000#080324653657# Michael Thompson 1121042880000236 +705Rayflax bonus pay for amazing work on #OSS 00010000236 +622231380104244042467880206450000000000#183688712366# Mason Thompson 1121042880000237 +705Witchcyan bonus pay for amazing work on #OSS 00010000237 +622231380104581136680257728500000000000#728668758532# Isabella Davis 1121042880000238 +705Sharkwind bonus pay for amazing work on #OSS 00010000238 +622231380104162478613550301040000000000#600012565582# Elijah Wilson 1121042880000239 +705Shriekerlily bonus pay for amazing work on #OSS 00010000239 +622231380104442170001374784530000000000#243301821212# Alexander White 1121042880000240 +705Jesterpaper bonus pay for amazing work on #OSS 00010000240 +622231380104571063846020461560000000000#164237633651# James Robinson 1121042880000241 +705Carpetflax bonus pay for amazing work on #OSS 00010000241 +622231380104335710041456135540000000000#040458522837# Chloe Robinson 1121042880000242 +705Seedautumn bonus pay for amazing work on #OSS 00010000242 +622231380104044287625557782150000000000#205377187351# Olivia Thompson 1121042880000243 +705Gamblernimble bonus pay for amazing work on #OSS 00010000243 +622231380104337417544855575360000000000#838754548324# Natalie Thomas 1121042880000244 +705Cloudtrail bonus pay for amazing work on #OSS 00010000244 +622231380104055060665860852370000000000#428433474406# James Anderson 1121042880000245 +705Hornetchatter bonus pay for amazing work on #OSS 00010000245 +622231380104726002057163645230000000000#654026632171# Joseph Thompson 1121042880000246 +705Collarmetal bonus pay for amazing work on #OSS 00010000246 +622231380104347125208268753880000000000#115445213132# Ella Thompson 1121042880000247 +705Snaprazor bonus pay for amazing work on #OSS 00010000247 +622231380104774404387835145820000000000#036715308427# Sophia Thompson 1121042880000248 +705Crownfast bonus pay for amazing work on #OSS 00010000248 +622231380104474708704804243640000000000#806308154057# Ella Anderson 1121042880000249 +705Divephase bonus pay for amazing work on #OSS 00010000249 +622231380104508066286253042210000000000#346403520444# Abigail Johnson 1121042880000250 +705Bowbone bonus pay for amazing work on #OSS 00010000250 +622231380104862502118577204240000000000#333068111402# Jayden Miller 1121042880000251 +705Antelopetree bonus pay for amazing work on #OSS 00010000251 +622231380104350458120502327330000000000#181462726253# Chloe Williams 1121042880000252 +705Divernotch bonus pay for amazing work on #OSS 00010000252 +622231380104203587644861850280000000000#513466344770# Emily Anderson 1121042880000253 +705Scorpiontyphoon bonus pay for amazing work on #OSS 00010000253 +622231380104476286585235403820000000000#343567758287# Madison Martin 1121042880000254 +705Flyhorn bonus pay for amazing work on #OSS 00010000254 +622231380104532135464402650630000000000#730877271212# Olivia Wilson 1121042880000255 +705Spearsilent bonus pay for amazing work on #OSS 00010000255 +622231380104674068223683074700000000000#884627136464# Aiden Harris 1121042880000256 +705Chopperbead bonus pay for amazing work on #OSS 00010000256 +622231380104534755477882063860000000000#173682176001# Elijah Williams 1121042880000257 +705Graspperidot bonus pay for amazing work on #OSS 00010000257 +622231380104677470158714305520000000000#487404162825# David Thomas 1121042880000258 +705Wingbone bonus pay for amazing work on #OSS 00010000258 +622231380104338772336041027550000000000#546882025165# Anthony Robinson 1121042880000259 +705Elkwarp bonus pay for amazing work on #OSS 00010000259 +622231380104810711706251454240000000000#842711546855# Joseph Brown 1121042880000260 +705Keeperjade bonus pay for amazing work on #OSS 00010000260 +622231380104043831310852753870000000000#336516151420# Aiden Thomas 1121042880000261 +705Footblossom bonus pay for amazing work on #OSS 00010000261 +622231380104784125800365275710000000000#446812276533# Emily Martinez 1121042880000262 +705Boarglass bonus pay for amazing work on #OSS 00010000262 +622231380104032100746412777880000000000#481018147288# Emily Martin 1121042880000263 +705Warriormalachite bonus pay for amazing work on #OSS 00010000263 +622231380104106474165548814770000000000#670655706515# Madison Anderson 1121042880000264 +705Grinslash bonus pay for amazing work on #OSS 00010000264 +622231380104215805713732372130000000000#382532257318# Sophia Thomas 1121042880000265 +705Pawbevel bonus pay for amazing work on #OSS 00010000265 +622231380104574424552183008080000000000#314216157780# Liam Harris 1121042880000266 +705Pixiesolar bonus pay for amazing work on #OSS 00010000266 +622231380104268231168800571320000000000#457253345556# Jacob Miller 1121042880000267 +705Antcosmic bonus pay for amazing work on #OSS 00010000267 +622231380104337141884714720140000000000#220264400868# Avery Martinez 1121042880000268 +705Pumastone bonus pay for amazing work on #OSS 00010000268 +622231380104775855245285433640000000000#012754133056# Mason Taylor 1121042880000269 +705Scarerred bonus pay for amazing work on #OSS 00010000269 +622231380104363548041780162120000000000#743473203414# Noah Wilson 1121042880000270 +705Bellyglen bonus pay for amazing work on #OSS 00010000270 +622231380104543226308261665520000000000#848657202130# Ethan Davis 1121042880000271 +705Raccoonsand bonus pay for amazing work on #OSS 00010000271 +622231380104203733832048308280000000000#664177387705# Mason Martin 1121042880000272 +705Deathperidot bonus pay for amazing work on #OSS 00010000272 +622231380104150644026731781870000000000#337762142574# Michael Harris 1121042880000273 +705Bunnyglitter bonus pay for amazing work on #OSS 00010000273 +622231380104626507647607325020000000000#811008616653# Sophia Smith 1121042880000274 +705Heronpie bonus pay for amazing work on #OSS 00010000274 +622231380104114088284476757160000000000#767688412286# Ethan Martin 1121042880000275 +705Coyoteheather bonus pay for amazing work on #OSS 00010000275 +622231380104151542625681471670000000000#752266177844# Charlotte Miller 1121042880000276 +705Leglight bonus pay for amazing work on #OSS 00010000276 +622231380104738418446400251600000000000#708873017734# Matthew Johnson 1121042880000277 +705Chargerapricot bonus pay for amazing work on #OSS 00010000277 +622231380104267251560524632010000000000#248183163580# Mia Thompson 1121042880000278 +705Boaprism bonus pay for amazing work on #OSS 00010000278 +622231380104028226221424733380000000000#255772668216# Anthony Miller 1121042880000279 +705Beakgarnet bonus pay for amazing work on #OSS 00010000279 +622231380104371422153306400070000000000#310256141324# Andrew Davis 1121042880000280 +705Stonecloud bonus pay for amazing work on #OSS 00010000280 +622231380104265146535134842410000000000#633746161067# Andrew Davis 1121042880000281 +705Touchmud bonus pay for amazing work on #OSS 00010000281 +622231380104371011226113608300000000000#263534511141# Matthew Thompson 1121042880000282 +705Jawrhinestone bonus pay for amazing work on #OSS 00010000282 +622231380104325565574676702150000000000#200303070874# William Martin 1121042880000283 +705Bugsatin bonus pay for amazing work on #OSS 00010000283 +622231380104567154442158628760000000000#530741067130# Isabella Taylor 1121042880000284 +705Speakertree bonus pay for amazing work on #OSS 00010000284 +622231380104800108853600103530000000000#013482483703# Emma Miller 1121042880000285 +705Shirtvolcano bonus pay for amazing work on #OSS 00010000285 +622231380104516271868084565580000000000#715666606622# Avery Johnson 1121042880000286 +705Stalkercrack bonus pay for amazing work on #OSS 00010000286 +622231380104201456242681202270000000000#842560128307# Zoey Jackson 1121042880000287 +705Whalemint bonus pay for amazing work on #OSS 00010000287 +622231380104483284208668725000000000000#580758380530# Matthew Garcia 1121042880000288 +705Thieffern bonus pay for amazing work on #OSS 00010000288 +622231380104616141113384564100000000000#530801810076# Olivia Wilson 1121042880000289 +705Piperstone bonus pay for amazing work on #OSS 00010000289 +622231380104610866505348422170000000000#340781116203# Joshua Garcia 1121042880000290 +705Paladinwood bonus pay for amazing work on #OSS 00010000290 +622231380104817404143310363120000000000#520111618781# Avery Anderson 1121042880000291 +705Throatiris bonus pay for amazing work on #OSS 00010000291 +622231380104574422816662732330000000000#831028122260# Sophia Martinez 1121042880000292 +705Skinnerluck bonus pay for amazing work on #OSS 00010000292 +622231380104787822658416384730000000000#881673262555# Madison Thompson 1121042880000293 +705Bisonshort bonus pay for amazing work on #OSS 00010000293 +622231380104432635674515752610000000000#306424778078# Natalie Thompson 1121042880000294 +705Playerwind bonus pay for amazing work on #OSS 00010000294 +622231380104224083268466387000000000000#320002855685# Mia Jackson 1121042880000295 +705Skullberyl bonus pay for amazing work on #OSS 00010000295 +622231380104631635330315566670000000000#347132831007# Ava Brown 1121042880000296 +705Catfuschia bonus pay for amazing work on #OSS 00010000296 +622231380104310883512075416510000000000#514878562447# Lily Jackson 1121042880000297 +705Shoulderdisco bonus pay for amazing work on #OSS 00010000297 +622231380104540160300121404440000000000#762156317482# Aiden Taylor 1121042880000298 +705Guardianmoor bonus pay for amazing work on #OSS 00010000298 +622231380104326801122720772200000000000#205232365123# Elizabeth Wilson 1121042880000299 +705Musedeep bonus pay for amazing work on #OSS 00010000299 +622231380104088100451117076830000000000#386312785538# Charlotte Martinez 1121042880000300 +705Legendriver bonus pay for amazing work on #OSS 00010000300 +622231380104282053638822815570000000000#311268502858# Anthony White 1121042880000301 +705Fairybold bonus pay for amazing work on #OSS 00010000301 +622231380104574335627776133450000000000#278213821178# Jacob Harris 1121042880000302 +705Fishplume bonus pay for amazing work on #OSS 00010000302 +622231380104167160688357075110000000000#186105672235# Joseph Thomas 1121042880000303 +705Seekercherry bonus pay for amazing work on #OSS 00010000303 +622231380104108630343040016200000000000#211575631357# Madison Miller 1121042880000304 +705Dropspangle bonus pay for amazing work on #OSS 00010000304 +622231380104121745822788717530000000000#227567032621# Andrew Anderson 1121042880000305 +705Witcharrow bonus pay for amazing work on #OSS 00010000305 +622231380104555106433535176350000000000#350126078887# Daniel Harris 1121042880000306 +705Jesterprickle bonus pay for amazing work on #OSS 00010000306 +622231380104641145441530881120000000000#882402052867# Lily Martinez 1121042880000307 +705Dutchessrattle bonus pay for amazing work on #OSS 00010000307 +622231380104231272745722828720000000000#861486365813# Noah Taylor 1121042880000308 +705Butterflyivy bonus pay for amazing work on #OSS 00010000308 +622231380104751551714241023350000000000#736362207646# Daniel Robinson 1121042880000309 +705Sharkalpine bonus pay for amazing work on #OSS 00010000309 +622231380104612873766215186880000000000#077851402822# Zoey Martin 1121042880000310 +705Butterflyscarlet bonus pay for amazing work on #OSS 00010000310 +622231380104572023062778462780000000000#364378160723# Noah Jones 1121042880000311 +705Kickercrack bonus pay for amazing work on #OSS 00010000311 +622231380104512660538055670250000000000#404835752430# Sofia Taylor 1121042880000312 +705Fanggreat bonus pay for amazing work on #OSS 00010000312 +622231380104875542760215430210000000000#523262033752# James Williams 1121042880000313 +705Bootpeppermint bonus pay for amazing work on #OSS 00010000313 +622231380104484541655573864810000000000#347500312637# Daniel Thomas 1121042880000314 +705Houndspring bonus pay for amazing work on #OSS 00010000314 +622231380104243086818603672110000000000#043446724345# Mia White 1121042880000315 +705Baneorchid bonus pay for amazing work on #OSS 00010000315 +622231380104017260245168706740000000000#741107330488# Elijah Davis 1121042880000316 +705Toucandeep bonus pay for amazing work on #OSS 00010000316 +622231380104180471252331022530000000000#417488348133# Elizabeth Johnson 1121042880000317 +705Walkerhazel bonus pay for amazing work on #OSS 00010000317 +622231380104261483087352018340000000000#567424688176# Alexander Johnson 1121042880000318 +705Turnerhickory bonus pay for amazing work on #OSS 00010000318 +622231380104257151047572612170000000000#517684204425# Charlotte Taylor 1121042880000319 +705Snagglefootwest bonus pay for amazing work on #OSS 00010000319 +622231380104215761745014132460000000000#203538204143# Chloe Thomas 1121042880000320 +705Crowdaisy bonus pay for amazing work on #OSS 00010000320 +622231380104156774142035005640000000000#517322472650# Joshua Harris 1121042880000321 +705Snakedawn bonus pay for amazing work on #OSS 00010000321 +622231380104518766526384181580000000000#241131114561# Abigail Williams 1121042880000322 +705Spurhelix bonus pay for amazing work on #OSS 00010000322 +622231380104636481221858025470000000000#660582110247# Mia Jackson 1121042880000323 +705Playerbrown bonus pay for amazing work on #OSS 00010000323 +622231380104286361852132231620000000000#178008141351# Elijah Miller 1121042880000324 +705Stagwood bonus pay for amazing work on #OSS 00010000324 +622231380104581060831530753630000000000#350411320172# Addison Johnson 1121042880000325 +705Scorpionalpine bonus pay for amazing work on #OSS 00010000325 +622231380104533844258888565870000000000#781174188340# Benjamin Thompson 1121042880000326 +705Shifttree bonus pay for amazing work on #OSS 00010000326 +622231380104530606047267087540000000000#086678557411# Matthew Smith 1121042880000327 +705Wolverinepuzzle bonus pay for amazing work on #OSS 00010000327 +622231380104740246864632456750000000000#373701515850# William Taylor 1121042880000328 +705Knavesprinkle bonus pay for amazing work on #OSS 00010000328 +622231380104607143630756030650000000000#644716353705# Natalie Jackson 1121042880000329 +705Sentrysolstice bonus pay for amazing work on #OSS 00010000329 +622231380104842752147043351440000000000#567165480175# Anthony Jackson 1121042880000330 +705Buffalocomet bonus pay for amazing work on #OSS 00010000330 +622231380104643275464421607780000000000#012862387474# Noah Davis 1121042880000331 +705Boarperidot bonus pay for amazing work on #OSS 00010000331 +622231380104117884366800447820000000000#580844625106# Mia White 1121042880000332 +705Foxmad bonus pay for amazing work on #OSS 00010000332 +622231380104665471804715352700000000000#060578511774# Olivia Moore 1121042880000333 +705Cougarred bonus pay for amazing work on #OSS 00010000333 +622231380104000461261470156600000000000#348873853852# Matthew White 1121042880000334 +705Shieldband bonus pay for amazing work on #OSS 00010000334 +622231380104758278701413077570000000000#848285430660# Sophia Williams 1121042880000335 +705Stingergreen bonus pay for amazing work on #OSS 00010000335 +622231380104040762006678551130000000000#510787775676# Liam White 1121042880000336 +705Fisherscratch bonus pay for amazing work on #OSS 00010000336 +622231380104641255855426618350000000000#428035660578# Emma Jackson 1121042880000337 +705Flywater bonus pay for amazing work on #OSS 00010000337 +622231380104844683735481243720000000000#560838672803# Noah White 1121042880000338 +705Legscharm bonus pay for amazing work on #OSS 00010000338 +622231380104226723527446741800000000000#187480828632# Emma Martinez 1121042880000339 +705Hyenadaffodil bonus pay for amazing work on #OSS 00010000339 +622231380104720530062640262430000000000#774522547331# Joshua Miller 1121042880000340 +705Knightforest bonus pay for amazing work on #OSS 00010000340 +622231380104700767627201534510000000000#018645544282# Natalie Harris 1121042880000341 +705Braidshell bonus pay for amazing work on #OSS 00010000341 +622231380104700027387162271680000000000#505514347444# Isabella Moore 1121042880000342 +705Backnight bonus pay for amazing work on #OSS 00010000342 +622231380104173606732315036040000000000#606664776303# Noah Robinson 1121042880000343 +705Sentryflame bonus pay for amazing work on #OSS 00010000343 +622231380104746865257731021020000000000#157210577814# Alexander Martinez 1121042880000344 +705Birdbig bonus pay for amazing work on #OSS 00010000344 +622231380104037834265577158630000000000#318213215321# Natalie Thomas 1121042880000345 +705Chopperhate bonus pay for amazing work on #OSS 00010000345 +622231380104105554660848733850000000000#611623538108# Sofia Garcia 1121042880000346 +705Songgrove bonus pay for amazing work on #OSS 00010000346 +622231380104013275753603131060000000000#822328313835# Mia Brown 1121042880000347 +705Butterflyfair bonus pay for amazing work on #OSS 00010000347 +622231380104874505625238863240000000000#224202481023# Aiden White 1121042880000348 +705Hawkbrass bonus pay for amazing work on #OSS 00010000348 +622231380104734788855535378620000000000#571504340377# Joshua Robinson 1121042880000349 +705Biterclear bonus pay for amazing work on #OSS 00010000349 +622231380104684783003610660470000000000#577854725050# Zoey Wilson 1121042880000350 +705Watcheralpine bonus pay for amazing work on #OSS 00010000350 +622231380104070171464245364170000000000#634037514858# Lily Garcia 1121042880000351 +705Shiftnorth bonus pay for amazing work on #OSS 00010000351 +622231380104086411623486545570000000000#802400716530# William Moore 1121042880000352 +705Flameabalone bonus pay for amazing work on #OSS 00010000352 +622231380104741212605845355470000000000#731112304117# Ava Martin 1121042880000353 +705Apeiris bonus pay for amazing work on #OSS 00010000353 +622231380104550234830006481220000000000#607803254786# Matthew Robinson 1121042880000354 +705Mothagate bonus pay for amazing work on #OSS 00010000354 +622231380104158314285424186360000000000#504046626426# Elijah Davis 1121042880000355 +705Gargoylebrook bonus pay for amazing work on #OSS 00010000355 +622231380104673018086520717240000000000#410661631871# Natalie Martinez 1121042880000356 +705Bowpaint bonus pay for amazing work on #OSS 00010000356 +622231380104638215052283248210000000000#183643814007# Aiden Martin 1121042880000357 +705Gorillajasper bonus pay for amazing work on #OSS 00010000357 +622231380104025056118334082400000000000#353024804252# Mason Wilson 1121042880000358 +705Fairytundra bonus pay for amazing work on #OSS 00010000358 +622231380104813326080452304730000000000#376040656757# Elizabeth Martin 1121042880000359 +705Bisontin bonus pay for amazing work on #OSS 00010000359 +622231380104142225273035374020000000000#707567268277# Abigail Wilson 1121042880000360 +705Riderchatter bonus pay for amazing work on #OSS 00010000360 +622231380104870818053114867440000000000#451040612802# Jacob Taylor 1121042880000361 +705Hornetdandelion bonus pay for amazing work on #OSS 00010000361 +622231380104558743054742440850000000000#723453804137# Elizabeth Thomas 1121042880000362 +705Snakegravel bonus pay for amazing work on #OSS 00010000362 +622231380104814171641508132860000000000#281180581111# Charlotte Smith 1121042880000363 +705Ripperhollow bonus pay for amazing work on #OSS 00010000363 +622231380104507557145844640820000000000#102224881704# Alexander Williams 1121042880000364 +705Minnowzircon bonus pay for amazing work on #OSS 00010000364 +622231380104876135803460477830000000000#307462288162# David Taylor 1121042880000365 +705Witchiron bonus pay for amazing work on #OSS 00010000365 +622231380104371382844364564530000000000#677006570386# Ella Jones 1121042880000366 +705Shriekervine bonus pay for amazing work on #OSS 00010000366 +622231380104838415004730468260000000000#884538503413# Ella Martinez 1121042880000367 +705Yakglaze bonus pay for amazing work on #OSS 00010000367 +622231380104077428805264086870000000000#460563557815# Liam Brown 1121042880000368 +705Stingerred bonus pay for amazing work on #OSS 00010000368 +622231380104483556208542061650000000000#567201652344# Benjamin Taylor 1121042880000369 +705Speardour bonus pay for amazing work on #OSS 00010000369 +622231380104101074685772062080000000000#472007153341# Noah Miller 1121042880000370 +705Legendtree bonus pay for amazing work on #OSS 00010000370 +622231380104268508366322276630000000000#408137848676# Sofia Davis 1121042880000371 +705Knaveagate bonus pay for amazing work on #OSS 00010000371 +622231380104133705008860227770000000000#344018537488# Alexander Garcia 1121042880000372 +705Slicerapple bonus pay for amazing work on #OSS 00010000372 +622231380104637510110541625680000000000#657124674265# Olivia Williams 1121042880000373 +705Raptorwool bonus pay for amazing work on #OSS 00010000373 +622231380104824061163530301220000000000#563658562330# William Johnson 1121042880000374 +705Swordchecker bonus pay for amazing work on #OSS 00010000374 +622231380104851224778504453270000000000#043702287643# Zoey Garcia 1121042880000375 +705Spiderwax bonus pay for amazing work on #OSS 00010000375 +622231380104350015384310442280000000000#072235880571# Elijah Thomas 1121042880000376 +705Neckiron bonus pay for amazing work on #OSS 00010000376 +622231380104163472774210245460000000000#617653587611# Natalie Moore 1121042880000377 +705Legendaquamarine bonus pay for amazing work on #OSS 00010000377 +622231380104665132012743858280000000000#565372736652# Alexander Smith 1121042880000378 +705Salmonviolet bonus pay for amazing work on #OSS 00010000378 +622231380104742268803776843670000000000#727243701316# William Jones 1121042880000379 +705Hidepepper bonus pay for amazing work on #OSS 00010000379 +622231380104758408325666685610000000000#232312120124# Ella Taylor 1121042880000380 +705Whipdew bonus pay for amazing work on #OSS 00010000380 +622231380104276751111672770080000000000#620881023267# Emily White 1121042880000381 +705Lizardquiver bonus pay for amazing work on #OSS 00010000381 +622231380104600046405037042240000000000#710844165404# Daniel Jones 1121042880000382 +705Jayspark bonus pay for amazing work on #OSS 00010000382 +622231380104546336760611213840000000000#266082635822# Noah Davis 1121042880000383 +705Tailjade bonus pay for amazing work on #OSS 00010000383 +622231380104771622625230527070000000000#402156104177# William White 1121042880000384 +705Batgeode bonus pay for amazing work on #OSS 00010000384 +622231380104232551067233555770000000000#106222506612# Chloe Moore 1121042880000385 +705Stagpattern bonus pay for amazing work on #OSS 00010000385 +622231380104618418733582072500000000000#578021215877# Mason Smith 1121042880000386 +705Shirtstitch bonus pay for amazing work on #OSS 00010000386 +622231380104151382156103152220000000000#101216421636# Abigail Taylor 1121042880000387 +705Princeplume bonus pay for amazing work on #OSS 00010000387 +622231380104656023850775602450000000000#432257753061# Mason Davis 1121042880000388 +705Catcherpsychadelic bonus pay for amazing work on #OSS 00010000388 +622231380104307370265557636250000000000#633382748187# Matthew Robinson 1121042880000389 +705Scarergreat bonus pay for amazing work on #OSS 00010000389 +622231380104136744643543432010000000000#738582288135# Lily Taylor 1121042880000390 +705Tradercoal bonus pay for amazing work on #OSS 00010000390 +622231380104155138237800204230000000000#654561176256# Joseph Thompson 1121042880000391 +705Carpetfossil bonus pay for amazing work on #OSS 00010000391 +622231380104450067603441700520000000000#635172810766# Elijah Jones 1121042880000392 +705Snarlregal bonus pay for amazing work on #OSS 00010000392 +622231380104413067244020575530000000000#553823416074# Emma Wilson 1121042880000393 +705Sparrowperidot bonus pay for amazing work on #OSS 00010000393 +622231380104667201662346454150000000000#862572643687# Jayden Robinson 1121042880000394 +705Ferretfluff bonus pay for amazing work on #OSS 00010000394 +622231380104436318213727406320000000000#644803540677# William Garcia 1121042880000395 +705Dukeriver bonus pay for amazing work on #OSS 00010000395 +622231380104323617118604155680000000000#243844458313# Charlotte Taylor 1121042880000396 +705Snapshore bonus pay for amazing work on #OSS 00010000396 +622231380104861818243701212070000000000#468557706408# Madison Davis 1121042880000397 +705Wingfreckle bonus pay for amazing work on #OSS 00010000397 +622231380104126811868068867320000000000#463726202780# Aiden White 1121042880000398 +705Shouldersulpher bonus pay for amazing work on #OSS 00010000398 +622231380104668501848658688370000000000#824235225744# Michael Johnson 1121042880000399 +705Armlegend bonus pay for amazing work on #OSS 00010000399 +622231380104272264284680158600000000000#272064602056# Emily Taylor 1121042880000400 +705Neckbubble bonus pay for amazing work on #OSS 00010000400 +622231380104712437025180652630000000000#301503387714# Matthew Jones 1121042880000401 +705Swallowsapphire bonus pay for amazing work on #OSS 00010000401 +622231380104038662633845617080000000000#324733132071# Isabella Robinson 1121042880000402 +705Wizardswift bonus pay for amazing work on #OSS 00010000402 +622231380104070535253638322030000000000#466778746881# Andrew Thompson 1121042880000403 +705Rayprong bonus pay for amazing work on #OSS 00010000403 +622231380104608841147414878320000000000#430064875456# Elijah Smith 1121042880000404 +705Ridgedestiny bonus pay for amazing work on #OSS 00010000404 +622231380104155063616170313580000000000#025323648774# Madison White 1121042880000405 +705Unicornbitter bonus pay for amazing work on #OSS 00010000405 +622231380104445251407888554430000000000#847626170733# Anthony Jones 1121042880000406 +705Songskitter bonus pay for amazing work on #OSS 00010000406 +622231380104726174133534026840000000000#138345263045# David Harris 1121042880000407 +705Wizardpear bonus pay for amazing work on #OSS 00010000407 +622231380104604887228530340000000000000#311784062245# Ethan Jones 1121042880000408 +705Spurfree bonus pay for amazing work on #OSS 00010000408 +622231380104510655008182050180000000000#675404865841# Emma Jones 1121042880000409 +705Scribesilent bonus pay for amazing work on #OSS 00010000409 +622231380104715116380837015570000000000#854160018852# Emily Jones 1121042880000410 +705Wizardpower bonus pay for amazing work on #OSS 00010000410 +622231380104108632880873257440000000000#162680146483# Jayden Miller 1121042880000411 +705Frillmorning bonus pay for amazing work on #OSS 00010000411 +622231380104032767662646107270000000000#013472071036# Zoey Thomas 1121042880000412 +705Griffinbrook bonus pay for amazing work on #OSS 00010000412 +622231380104474265807000687070000000000#717525452554# Addison Harris 1121042880000413 +705Goosenickel bonus pay for amazing work on #OSS 00010000413 +622231380104341718586178014300000000000#848270342356# Elijah White 1121042880000414 +705Paladinvivid bonus pay for amazing work on #OSS 00010000414 +622231380104110514007656216860000000000#766052272851# Mia Jackson 1121042880000415 +705Bellyfork bonus pay for amazing work on #OSS 00010000415 +622231380104177248071815651110000000000#627234413883# Jayden White 1121042880000416 +705Geckohazel bonus pay for amazing work on #OSS 00010000416 +622231380104414110784527506210000000000#602470457083# Chloe Jackson 1121042880000417 +705Iguanasaber bonus pay for amazing work on #OSS 00010000417 +622231380104166873132667315800000000000#436017417772# David Anderson 1121042880000418 +705Vulturesoft bonus pay for amazing work on #OSS 00010000418 +622231380104600173867067876110000000000#521763663442# Mia Miller 1121042880000419 +705Spidergravel bonus pay for amazing work on #OSS 00010000419 +622231380104815244844531448820000000000#536447232785# Natalie Garcia 1121042880000420 +705Gamblergiant bonus pay for amazing work on #OSS 00010000420 +622231380104261553247573683750000000000#875428403625# Sophia Williams 1121042880000421 +705Raptorbutter bonus pay for amazing work on #OSS 00010000421 +622231380104447676323266376760000000000#577015583708# Lily Harris 1121042880000422 +705Hornglass bonus pay for amazing work on #OSS 00010000422 +622231380104026866621127868130000000000#567361081783# Addison Harris 1121042880000423 +705Daggerspectrum bonus pay for amazing work on #OSS 00010000423 +622231380104435853063528712020000000000#575582507680# Aubrey Martinez 1121042880000424 +705Scarertime bonus pay for amazing work on #OSS 00010000424 +622231380104026417185027273010000000000#245863863302# Charlotte Taylor 1121042880000425 +705Dogcyber bonus pay for amazing work on #OSS 00010000425 +622231380104517865248620517850000000000#487304773572# Elizabeth White 1121042880000426 +705Spearmalachite bonus pay for amazing work on #OSS 00010000426 +622231380104740446063415713850000000000#844124488424# Lily Garcia 1121042880000427 +705Cockatoowalnut bonus pay for amazing work on #OSS 00010000427 +622231380104215206477307213110000000000#631278773560# Jayden Robinson 1121042880000428 +705Frightgreen bonus pay for amazing work on #OSS 00010000428 +622231380104403271507270057480000000000#754664675138# Jayden Garcia 1121042880000429 +705Ringerchip bonus pay for amazing work on #OSS 00010000429 +622231380104366648214318530450000000000#715821627627# Charlotte Smith 1121042880000430 +705Salmonrelic bonus pay for amazing work on #OSS 00010000430 +622231380104882271858166782880000000000#173745240178# Mia Jones 1121042880000431 +705Apenickel bonus pay for amazing work on #OSS 00010000431 +622231380104307322286046067740000000000#402246147238# James Robinson 1121042880000432 +705Divermeteor bonus pay for amazing work on #OSS 00010000432 +622231380104828436681407546370000000000#328051632408# Lily Johnson 1121042880000433 +705Lordfoam bonus pay for amazing work on #OSS 00010000433 +622231380104732137307468633750000000000#548057763332# Zoey Smith 1121042880000434 +705Thumbnotch bonus pay for amazing work on #OSS 00010000434 +622231380104275858315710045330000000000#073531785483# Emily White 1121042880000435 +705Cougarruby bonus pay for amazing work on #OSS 00010000435 +622231380104216074548627042600000000000#416137122785# Ava Davis 1121042880000436 +705Ogrequiver bonus pay for amazing work on #OSS 00010000436 +622231380104772106538168388400000000000#360551722136# Michael Wilson 1121042880000437 +705Gamblerdirt bonus pay for amazing work on #OSS 00010000437 +622231380104863635458846766230000000000#672082212225# Sofia Davis 1121042880000438 +705Terrierpeat bonus pay for amazing work on #OSS 00010000438 +622231380104327705745862026840000000000#610413661384# Matthew Harris 1121042880000439 +705Gorillatreasure bonus pay for amazing work on #OSS 00010000439 +622231380104485477215138053340000000000#461553061488# Matthew Moore 1121042880000440 +705Unicorngreen bonus pay for amazing work on #OSS 00010000440 +622231380104581672322185088880000000000#470241850614# Daniel Martinez 1121042880000441 +705Sparrowsalt bonus pay for amazing work on #OSS 00010000441 +622231380104473772061386811830000000000#056003326487# Sofia Brown 1121042880000442 +705Princewave bonus pay for amazing work on #OSS 00010000442 +622231380104047521240038446100000000000#336073765110# Aiden Thomas 1121042880000443 +705Twisterrift bonus pay for amazing work on #OSS 00010000443 +622231380104274406601810001220000000000#170885882888# Elijah Robinson 1121042880000444 +705Palmjewel bonus pay for amazing work on #OSS 00010000444 +622231380104235704356166060010000000000#523611418072# Ava Moore 1121042880000445 +705Chillerpetal bonus pay for amazing work on #OSS 00010000445 +622231380104161272841825175660000000000#536451141764# Mason Jackson 1121042880000446 +705Wingvivid bonus pay for amazing work on #OSS 00010000446 +622231380104843061103705854840000000000#037332868353# Michael Moore 1121042880000447 +705Pigquasar bonus pay for amazing work on #OSS 00010000447 +622231380104335267226424073850000000000#230842880246# Andrew Jackson 1121042880000448 +705Shriekerwarp bonus pay for amazing work on #OSS 00010000448 +622231380104023267224870422510000000000#522544371605# Sophia Garcia 1121042880000449 +705Scarerivy bonus pay for amazing work on #OSS 00010000449 +622231380104302607530227865620000000000#448332400116# Michael Taylor 1121042880000450 +705Piratecliff bonus pay for amazing work on #OSS 00010000450 +622231380104843810006172784530000000000#628207100466# Elizabeth Miller 1121042880000451 +705Ladyflax bonus pay for amazing work on #OSS 00010000451 +622231380104157472003413154410000000000#545502771754# Joshua Moore 1121042880000452 +705Songdust bonus pay for amazing work on #OSS 00010000452 +622231380104263546364520046410000000000#322706067724# Abigail Harris 1121042880000453 +705Snapperpeat bonus pay for amazing work on #OSS 00010000453 +622231380104121812681306122550000000000#480814522621# Mia Johnson 1121042880000454 +705Antblaze bonus pay for amazing work on #OSS 00010000454 +622231380104280387288245334320000000000#354703512655# Madison Thomas 1121042880000455 +705Storkflint bonus pay for amazing work on #OSS 00010000455 +622231380104533123025766541260000000000#377820782060# Emma Smith 1121042880000456 +705Razorbranch bonus pay for amazing work on #OSS 00010000456 +622231380104117680868478473010000000000#213034344750# David Davis 1121042880000457 +705Hornrose bonus pay for amazing work on #OSS 00010000457 +622231380104065048742613604380000000000#303252322111# Daniel Thompson 1121042880000458 +705Apepink bonus pay for amazing work on #OSS 00010000458 +622231380104738414331103272720000000000#706557785060# Anthony White 1121042880000459 +705Legsautumn bonus pay for amazing work on #OSS 00010000459 +622231380104332525387268564260000000000#880162431564# Madison Anderson 1121042880000460 +705Slicerrift bonus pay for amazing work on #OSS 00010000460 +622231380104160225440211387050000000000#676203600287# Anthony Taylor 1121042880000461 +705Mindchocolate bonus pay for amazing work on #OSS 00010000461 +622231380104704063046170320720000000000#845650847534# Aubrey Brown 1121042880000462 +705Eatercoconut bonus pay for amazing work on #OSS 00010000462 +622231380104517750652126330640000000000#356125737317# Sofia Martinez 1121042880000463 +705Unicornveil bonus pay for amazing work on #OSS 00010000463 +622231380104270270833254628460000000000#454615751200# Daniel White 1121042880000464 +705Facemalachite bonus pay for amazing work on #OSS 00010000464 +622231380104744784228587207230000000000#758403224247# Matthew Martin 1121042880000465 +705Toeflower bonus pay for amazing work on #OSS 00010000465 +622231380104205748080355660710000000000#521012657422# Isabella Robinson 1121042880000466 +705Slicerwind bonus pay for amazing work on #OSS 00010000466 +622231380104651146757236151430000000000#444252523426# Emma Garcia 1121042880000467 +705Princessthorn bonus pay for amazing work on #OSS 00010000467 +622231380104367332168440671620000000000#302281277172# Madison Moore 1121042880000468 +705Whimseysolar bonus pay for amazing work on #OSS 00010000468 +622231380104144555564861122150000000000#035683826077# Sophia Johnson 1121042880000469 +705Thornnoon bonus pay for amazing work on #OSS 00010000469 +622231380104433676060017716220000000000#765360024474# Anthony Jackson 1121042880000470 +705Saverquilt bonus pay for amazing work on #OSS 00010000470 +622231380104888844655666588460000000000#344061241845# Anthony Thompson 1121042880000471 +705Whipluck bonus pay for amazing work on #OSS 00010000471 +622231380104165184178713650820000000000#678005835203# Matthew Miller 1121042880000472 +705Touchfrill bonus pay for amazing work on #OSS 00010000472 +622231380104628587018856382730000000000#015607781573# Sophia Davis 1121042880000473 +705Deathfeather bonus pay for amazing work on #OSS 00010000473 +622231380104670102811313480720000000000#024721484137# Alexander Garcia 1121042880000474 +705Thiefclover bonus pay for amazing work on #OSS 00010000474 +622231380104204368182842587360000000000#713530624116# Addison Thompson 1121042880000475 +705Legendthorn bonus pay for amazing work on #OSS 00010000475 +622231380104834674457588638210000000000#302051445733# Jayden Jones 1121042880000476 +705Chillsunrise bonus pay for amazing work on #OSS 00010000476 +622231380104562862547086164360000000000#557007264336# Joseph Wilson 1121042880000477 +705Keeperdandelion bonus pay for amazing work on #OSS 00010000477 +622231380104660481806122202860000000000#357244246548# Ella Martinez 1121042880000478 +705Raptorequinox bonus pay for amazing work on #OSS 00010000478 +622231380104548042476358847300000000000#525572130632# Alexander Miller 1121042880000479 +705Geckosprinkle bonus pay for amazing work on #OSS 00010000479 +622231380104708644472773562120000000000#750500815631# Joseph Martinez 1121042880000480 +705Gazellejewel bonus pay for amazing work on #OSS 00010000480 +622231380104820784774404303880000000000#164237810858# Emma Jones 1121042880000481 +705Spurthorn bonus pay for amazing work on #OSS 00010000481 +622231380104127110611306760540000000000#344083437520# Ethan Moore 1121042880000482 +705Crowshy bonus pay for amazing work on #OSS 00010000482 +622231380104373510236422587830000000000#602375615546# Mia Martinez 1121042880000483 +705Cockatoosummer bonus pay for amazing work on #OSS 00010000483 +622231380104660801153257154350000000000#070847814326# Sofia Williams 1121042880000484 +705Voicelake bonus pay for amazing work on #OSS 00010000484 +622231380104678410014060873840000000000#622424285177# Matthew Garcia 1121042880000485 +705Ridgenorth bonus pay for amazing work on #OSS 00010000485 +622231380104518667856738737130000000000#860210338110# Ava Williams 1121042880000486 +705Viperholly bonus pay for amazing work on #OSS 00010000486 +622231380104718815434361747020000000000#066060136433# Matthew Williams 1121042880000487 +705Boltskitter bonus pay for amazing work on #OSS 00010000487 +622231380104370527424606425110000000000#231111302126# Aubrey White 1121042880000488 +705Mistressdull bonus pay for amazing work on #OSS 00010000488 +622231380104187654072504135550000000000#853736504484# Jayden Miller 1121042880000489 +705Fingertabby bonus pay for amazing work on #OSS 00010000489 +622231380104223103044672010580000000000#522058682815# Lily Garcia 1121042880000490 +705Watcherbrass bonus pay for amazing work on #OSS 00010000490 +622231380104216853118320188640000000000#848525475776# James Anderson 1121042880000491 +705Roverrowan bonus pay for amazing work on #OSS 00010000491 +622231380104055808345632416870000000000#867410832422# Emily Johnson 1121042880000492 +705Bootvivid bonus pay for amazing work on #OSS 00010000492 +622231380104471518543041838560000000000#041061764860# Ella Martinez 1121042880000493 +705Crownmica bonus pay for amazing work on #OSS 00010000493 +622231380104187811073568420310000000000#683477181267# Michael Harris 1121042880000494 +705Eaterplump bonus pay for amazing work on #OSS 00010000494 +622231380104281400635872240020000000000#218748221501# Elizabeth Jackson 1121042880000495 +705Otterwinter bonus pay for amazing work on #OSS 00010000495 +622231380104233063387832131460000000000#167422151747# Matthew Brown 1121042880000496 +705Armzircon bonus pay for amazing work on #OSS 00010000496 +622231380104788786330565836530000000000#313401442430# Joshua Robinson 1121042880000497 +705Cloudkeen bonus pay for amazing work on #OSS 00010000497 +622231380104752455772504744650000000000#068417820107# Elijah Davis 1121042880000498 +705Weedbrass bonus pay for amazing work on #OSS 00010000498 +622231380104584037348007568080000000000#810656226676# Ethan Martinez 1121042880000499 +705Voiceblue bonus pay for amazing work on #OSS 00010000499 +622231380104883442323513764240000000000#366551882186# Andrew Harris 1121042880000500 +705Unicornquark bonus pay for amazing work on #OSS 00010000500 +622231380104686872020433048800000000000#560522588352# Mia Anderson 1121042880000501 +705Eaglesolstice bonus pay for amazing work on #OSS 00010000501 +622231380104186051667541364280000000000#433425815332# Aiden Davis 1121042880000502 +705Footsteel bonus pay for amazing work on #OSS 00010000502 +622231380104608404704605323380000000000#272525063221# Emma White 1121042880000503 +705Burnfield bonus pay for amazing work on #OSS 00010000503 +622231380104712462172442382720000000000#801524641127# Andrew Robinson 1121042880000504 +705Boltalabaster bonus pay for amazing work on #OSS 00010000504 +622231380104377363751738884080000000000#744548524145# Elizabeth Harris 1121042880000505 +705Salmonstream bonus pay for amazing work on #OSS 00010000505 +622231380104161336727405854810000000000#688611027216# Ella Smith 1121042880000506 +705Jawred bonus pay for amazing work on #OSS 00010000506 +622231380104463375000722123270000000000#881705512245# Abigail White 1121042880000507 +705Pantherspring bonus pay for amazing work on #OSS 00010000507 +622231380104347073055407488280000000000#286285804275# Joseph Jones 1121042880000508 +705Lionfir bonus pay for amazing work on #OSS 00010000508 +622231380104748365686751031720000000000#854746615885# Addison Johnson 1121042880000509 +705Snagglefootmountain bonus pay for amazing work on #OSS 00010000509 +622231380104126205415006636330000000000#162224147228# Andrew Thompson 1121042880000510 +705Catchermeteor bonus pay for amazing work on #OSS 00010000510 +622231380104278874247382174780000000000#714745318424# Zoey Harris 1121042880000511 +705Centaurstorm bonus pay for amazing work on #OSS 00010000511 +622231380104875055803043846270000000000#503878645510# David Anderson 1121042880000512 +705Bellruby bonus pay for amazing work on #OSS 00010000512 +622231380104333052185682657000000000000#006441864627# Isabella Jackson 1121042880000513 +705Samuraiblack bonus pay for amazing work on #OSS 00010000513 +622231380104441232130525682230000000000#885113867882# Elijah Johnson 1121042880000514 +705Lioniridescent bonus pay for amazing work on #OSS 00010000514 +622231380104648034564626568060000000000#172851124377# Sofia White 1121042880000515 +705Cougarnight bonus pay for amazing work on #OSS 00010000515 +622231380104811883664337771220000000000#014405644202# Joseph Williams 1121042880000516 +705Cowlwarp bonus pay for amazing work on #OSS 00010000516 +622231380104756164451888607620000000000#158353675542# Elijah White 1121042880000517 +705Facehickory bonus pay for amazing work on #OSS 00010000517 +622231380104414548500882874080000000000#727568731827# Daniel Jones 1121042880000518 +705Herontwisty bonus pay for amazing work on #OSS 00010000518 +622231380104734671527601701770000000000#321000234138# Andrew Robinson 1121042880000519 +705Oriolesilent bonus pay for amazing work on #OSS 00010000519 +622231380104465214173604646500000000000#645821848646# Chloe Thomas 1121042880000520 +705Jaycat bonus pay for amazing work on #OSS 00010000520 +622231380104088518410835420210000000000#676543272612# Daniel White 1121042880000521 +705Carppinto bonus pay for amazing work on #OSS 00010000521 +622231380104317766075120715570000000000#342847128117# Joshua Brown 1121042880000522 +705Whaleclever bonus pay for amazing work on #OSS 00010000522 +622231380104645881701576130010000000000#000545113540# Elizabeth Wilson 1121042880000523 +705Hairsoft bonus pay for amazing work on #OSS 00010000523 +622231380104825404524746433850000000000#830767027243# James Martin 1121042880000524 +705Hisserproud bonus pay for amazing work on #OSS 00010000524 +622231380104136617362633766040000000000#736814326756# David White 1121042880000525 +705Fliertranslucent bonus pay for amazing work on #OSS 00010000525 +622231380104468284666738154410000000000#118174670022# Natalie Miller 1121042880000526 +705Moosesleet bonus pay for amazing work on #OSS 00010000526 +622231380104306244484721282150000000000#056874740333# Emily Thompson 1121042880000527 +705Griffinvenom bonus pay for amazing work on #OSS 00010000527 +622231380104816335750851876220000000000#534717621038# Olivia Wilson 1121042880000528 +705Sargentcrimson bonus pay for amazing work on #OSS 00010000528 +622231380104501232843153811630000000000#020338633322# Mia Anderson 1121042880000529 +705Stagcurly bonus pay for amazing work on #OSS 00010000529 +622231380104564870488164248210000000000#624171537303# Abigail Thompson 1121042880000530 +705Jawrain bonus pay for amazing work on #OSS 00010000530 +622231380104325418448375771550000000000#422644403282# Elijah Brown 1121042880000531 +705Houndbramble bonus pay for amazing work on #OSS 00010000531 +622231380104414352358061116150000000000#486321277340# Daniel White 1121042880000532 +705Banecerulean bonus pay for amazing work on #OSS 00010000532 +622231380104180688838781884180000000000#057147821136# Sofia Taylor 1121042880000533 +705Oriolefluff bonus pay for amazing work on #OSS 00010000533 +622231380104653174060688574750000000000#427118252606# Lily Johnson 1121042880000534 +705Fairynavy bonus pay for amazing work on #OSS 00010000534 +622231380104803207773075225860000000000#057257268351# Emma Miller 1121042880000535 +705Scorpiontree bonus pay for amazing work on #OSS 00010000535 +622231380104173431413467138470000000000#723740136773# Michael Davis 1121042880000536 +705Braidsmall bonus pay for amazing work on #OSS 00010000536 +622231380104233336457563243080000000000#733477513642# Aubrey Thomas 1121042880000537 +705Dartshimmer bonus pay for amazing work on #OSS 00010000537 +622231380104861441305418028680000000000#672356622174# David Wilson 1121042880000538 +705Diveplum bonus pay for amazing work on #OSS 00010000538 +622231380104250362813620863540000000000#036424184244# Jacob Thompson 1121042880000539 +705Batgem bonus pay for amazing work on #OSS 00010000539 +622231380104023371040631173420000000000#326144306743# William Johnson 1121042880000540 +705Wolverinesulpher bonus pay for amazing work on #OSS 00010000540 +622231380104446836838810382170000000000#144373001803# Abigail Thomas 1121042880000541 +705Racerbird bonus pay for amazing work on #OSS 00010000541 +622231380104378867110483221000000000000#474442448201# Madison Davis 1121042880000542 +705Paladinmercury bonus pay for amazing work on #OSS 00010000542 +622231380104237388381254754460000000000#301117445888# Abigail Robinson 1121042880000543 +705Markprism bonus pay for amazing work on #OSS 00010000543 +622231380104024486578678464240000000000#516177815640# Jacob Martinez 1121042880000544 +705Voiceforest bonus pay for amazing work on #OSS 00010000544 +622231380104563334742771845400000000000#376387860760# James White 1121042880000545 +705Shouldergem bonus pay for amazing work on #OSS 00010000545 +622231380104050213730206277810000000000#565841316673# Abigail Wilson 1121042880000546 +705Bellyolive bonus pay for amazing work on #OSS 00010000546 +622231380104672688518343455120000000000#175637548547# Jacob Martinez 1121042880000547 +705Queenprong bonus pay for amazing work on #OSS 00010000547 +622231380104377716887761515760000000000#880668512607# Matthew Martinez 1121042880000548 +705Lynxcoffee bonus pay for amazing work on #OSS 00010000548 +622231380104114873433822620420000000000#625383148813# Andrew Martinez 1121042880000549 +705Devourerglaze bonus pay for amazing work on #OSS 00010000549 +622231380104080564225663888050000000000#855786653370# Mia Anderson 1121042880000550 +705Ravenquick bonus pay for amazing work on #OSS 00010000550 +622231380104854004607565013060000000000#051136324706# Natalie Williams 1121042880000551 +705Scorpionthunder bonus pay for amazing work on #OSS 00010000551 +622231380104113106582213750130000000000#467264572081# Michael Wilson 1121042880000552 +705Browleaf bonus pay for amazing work on #OSS 00010000552 +622231380104631853848687387220000000000#524515633206# Anthony Johnson 1121042880000553 +705Minnowspark bonus pay for amazing work on #OSS 00010000553 +622231380104462356760683867530000000000#685241242184# Elizabeth Miller 1121042880000554 +705Shirttwisty bonus pay for amazing work on #OSS 00010000554 +622231380104260444500210122500000000000#450264304755# Noah Harris 1121042880000555 +705Raverviolet bonus pay for amazing work on #OSS 00010000555 +622231380104758843066411118460000000000#647050833351# Charlotte Taylor 1121042880000556 +705Roarfancy bonus pay for amazing work on #OSS 00010000556 +622231380104424848588872600130000000000#126334500183# Abigail Miller 1121042880000557 +705Fancierfreckle bonus pay for amazing work on #OSS 00010000557 +622231380104454203633674551030000000000#858826117138# James Moore 1121042880000558 +705Leaderaquamarine bonus pay for amazing work on #OSS 00010000558 +622231380104573854337102383660000000000#365805763364# Benjamin Jones 1121042880000559 +705Yaksprout bonus pay for amazing work on #OSS 00010000559 +622231380104163451100372061570000000000#831125138676# Sofia Anderson 1121042880000560 +705Falconzenith bonus pay for amazing work on #OSS 00010000560 +622231380104062024286684233200000000000#385241472382# Emily Thomas 1121042880000561 +705Runneroil bonus pay for amazing work on #OSS 00010000561 +622231380104818563332155045620000000000#280411012151# Charlotte Miller 1121042880000562 +705Saverdirt bonus pay for amazing work on #OSS 00010000562 +622231380104672344441618112420000000000#701105134772# Lily Jones 1121042880000563 +705Browglacier bonus pay for amazing work on #OSS 00010000563 +622231380104217804701116074520000000000#812340571875# Benjamin Wilson 1121042880000564 +705Swoopslime bonus pay for amazing work on #OSS 00010000564 +622231380104534135471287026300000000000#384374054860# Aubrey Smith 1121042880000565 +705Takerfield bonus pay for amazing work on #OSS 00010000565 +622231380104484878765044007450000000000#234434467702# Matthew Thomas 1121042880000566 +705Eagleslash bonus pay for amazing work on #OSS 00010000566 +622231380104166883706473518580000000000#108720054473# Noah Robinson 1121042880000567 +705Seerfossil bonus pay for amazing work on #OSS 00010000567 +622231380104832745284507714250000000000#153502537734# Charlotte Miller 1121042880000568 +705Oriolehill bonus pay for amazing work on #OSS 00010000568 +622231380104843681485537471800000000000#631611407288# Chloe Miller 1121042880000569 +705Serpentquark bonus pay for amazing work on #OSS 00010000569 +622231380104805137546754486840000000000#206653025632# Aiden Miller 1121042880000570 +705Weedrose bonus pay for amazing work on #OSS 00010000570 +622231380104205850545751041000000000000#272256428210# Aubrey Jackson 1121042880000571 +705Duckplum bonus pay for amazing work on #OSS 00010000571 +622231380104165624602402610760000000000#680618243748# Mason Smith 1121042880000572 +705Scareremerald bonus pay for amazing work on #OSS 00010000572 +622231380104573018240255102300000000000#610131676755# Mason Taylor 1121042880000573 +705Shakerhate bonus pay for amazing work on #OSS 00010000573 +622231380104418818602535771780000000000#210034455377# Daniel Davis 1121042880000574 +705Shoulderaquamarine bonus pay for amazing work on #OSS 00010000574 +622231380104035283276176132800000000000#126243834662# Avery Thompson 1121042880000575 +705Piratesummer bonus pay for amazing work on #OSS 00010000575 +622231380104408133233406665620000000000#103330133335# Andrew Jones 1121042880000576 +705Legroan bonus pay for amazing work on #OSS 00010000576 +622231380104120718561237347480000000000#442144182387# Natalie Garcia 1121042880000577 +705Foxsnapdragon bonus pay for amazing work on #OSS 00010000577 +622231380104368844222185548560000000000#016622657237# Elizabeth Harris 1121042880000578 +705Scalesatin bonus pay for amazing work on #OSS 00010000578 +622231380104326123012776761130000000000#747331256770# Aiden Moore 1121042880000579 +705Hawkgold bonus pay for amazing work on #OSS 00010000579 +622231380104085002067014810480000000000#431640667667# Zoey Davis 1121042880000580 +705Hawksheer bonus pay for amazing work on #OSS 00010000580 +622231380104578640448353645040000000000#027382383062# Daniel Johnson 1121042880000581 +705Ripperiris bonus pay for amazing work on #OSS 00010000581 +622231380104237530131423853160000000000#637307708033# Elijah Davis 1121042880000582 +705Hunterbranch bonus pay for amazing work on #OSS 00010000582 +622231380104882353683551305710000000000#247102503241# Ava Brown 1121042880000583 +705Stingglaze bonus pay for amazing work on #OSS 00010000583 +622231380104785637141380415780000000000#381344862223# Aubrey Jackson 1121042880000584 +705Roarerrag bonus pay for amazing work on #OSS 00010000584 +622231380104885561734631233660000000000#732035573810# Charlotte Miller 1121042880000585 +705Fliersapphire bonus pay for amazing work on #OSS 00010000585 +622231380104614533326740223880000000000#410857847651# Ethan Moore 1121042880000586 +705Wizardrogue bonus pay for amazing work on #OSS 00010000586 +622231380104666385234527225450000000000#061584807424# Noah Miller 1121042880000587 +705Burnmaze bonus pay for amazing work on #OSS 00010000587 +622231380104354444535143350530000000000#346214471471# Daniel Anderson 1121042880000588 +705Heronluminous bonus pay for amazing work on #OSS 00010000588 +622231380104216655061783304540000000000#388573184581# William Thomas 1121042880000589 +705Scribekeen bonus pay for amazing work on #OSS 00010000589 +622231380104163785284051634780000000000#761013218700# Abigail White 1121042880000590 +705Heroprism bonus pay for amazing work on #OSS 00010000590 +622231380104557570282778370330000000000#270627231748# Anthony Miller 1121042880000591 +705Backrose bonus pay for amazing work on #OSS 00010000591 +622231380104223021023334862140000000000#350264355800# Elijah Brown 1121042880000592 +705Capmalachite bonus pay for amazing work on #OSS 00010000592 +622231380104102562040674156340000000000#260445873313# Joshua Williams 1121042880000593 +705Monkeyrowan bonus pay for amazing work on #OSS 00010000593 +622231380104835035384843325760000000000#173802841420# Matthew Jones 1121042880000594 +705Gazellelunar bonus pay for amazing work on #OSS 00010000594 +622231380104871188061805087700000000000#500170176760# David Wilson 1121042880000595 +705Gemshallow bonus pay for amazing work on #OSS 00010000595 +622231380104826861588512808450000000000#363237541810# Joshua Johnson 1121042880000596 +705Masterbog bonus pay for amazing work on #OSS 00010000596 +622231380104750581700777385300000000000#783726631086# Joseph Williams 1121042880000597 +705Chatterheavy bonus pay for amazing work on #OSS 00010000597 +622231380104544255248705851450000000000#884288801018# Ethan Thompson 1121042880000598 +705Crusherwood bonus pay for amazing work on #OSS 00010000598 +622231380104336473224386677040000000000#700824642118# Avery Johnson 1121042880000599 +705Thumbmaze bonus pay for amazing work on #OSS 00010000599 +622231380104200708545385277130000000000#183732337526# Joshua Thomas 1121042880000600 +705Gazellecyber bonus pay for amazing work on #OSS 00010000600 +622231380104244086867334116860000000000#755383040400# Joshua Williams 1121042880000601 +705Scribesolstice bonus pay for amazing work on #OSS 00010000601 +622231380104752858220567184370000000000#170051407655# Ava Jackson 1121042880000602 +705Raptorsilver bonus pay for amazing work on #OSS 00010000602 +622231380104825844282314818010000000000#051271848884# Addison Wilson 1121042880000603 +705Footpyrite bonus pay for amazing work on #OSS 00010000603 +622231380104887003238666642030000000000#776631510017# Sophia Moore 1121042880000604 +705Riderring bonus pay for amazing work on #OSS 00010000604 +622231380104655874813333301280000000000#601610672053# Elizabeth Johnson 1121042880000605 +705Skullmire bonus pay for amazing work on #OSS 00010000605 +622231380104033437630761185840000000000#751262386131# Sofia Thompson 1121042880000606 +705Soarersurf bonus pay for amazing work on #OSS 00010000606 +622231380104568783452251303380000000000#436774438417# Madison Anderson 1121042880000607 +705Curtainpie bonus pay for amazing work on #OSS 00010000607 +622231380104850767746405547840000000000#252824508343# Joshua White 1121042880000608 +705Shriekrazor bonus pay for amazing work on #OSS 00010000608 +622231380104164428652844650380000000000#853152331752# Ava Brown 1121042880000609 +705Bugspot bonus pay for amazing work on #OSS 00010000609 +622231380104473164534441835550000000000#727573216753# Liam Garcia 1121042880000610 +705Chillweak bonus pay for amazing work on #OSS 00010000610 +622231380104526625646703043420000000000#173275382456# Emily Johnson 1121042880000611 +705Chargerpebble bonus pay for amazing work on #OSS 00010000611 +622231380104746370084223277030000000000#474504185157# Anthony White 1121042880000612 +705Noseshard bonus pay for amazing work on #OSS 00010000612 +622231380104447802767705525360000000000#808255181462# Michael Jones 1121042880000613 +705Glazerlaser bonus pay for amazing work on #OSS 00010000613 +622231380104112675584646466660000000000#258822210138# Elizabeth Garcia 1121042880000614 +705Stoneglen bonus pay for amazing work on #OSS 00010000614 +622231380104723555826623078740000000000#306556206302# Ella Robinson 1121042880000615 +705Tonguecoffee bonus pay for amazing work on #OSS 00010000615 +622231380104175575682187468740000000000#806506303745# Alexander Moore 1121042880000616 +705Handgrove bonus pay for amazing work on #OSS 00010000616 +622231380104336082011210361860000000000#775585853642# Michael Thomas 1121042880000617 +705Bootalabaster bonus pay for amazing work on #OSS 00010000617 +622231380104043845002232531100000000000#670105441782# Ethan Wilson 1121042880000618 +705Harevoid bonus pay for amazing work on #OSS 00010000618 +622231380104132103717846348050000000000#504545272046# Aubrey White 1121042880000619 +705Otterroad bonus pay for amazing work on #OSS 00010000619 +622231380104256860436351413540000000000#452026804844# Abigail Davis 1121042880000620 +705Ravenviridian bonus pay for amazing work on #OSS 00010000620 +622231380104460154042018648880000000000#158535567011# Chloe Thompson 1121042880000621 +705Pantherluminous bonus pay for amazing work on #OSS 00010000621 +622231380104831105256121125860000000000#174862362344# Joseph Martin 1121042880000622 +705Jaguarspring bonus pay for amazing work on #OSS 00010000622 +622231380104602522756351810210000000000#765558766523# Mason Garcia 1121042880000623 +705Sightmango bonus pay for amazing work on #OSS 00010000623 +622231380104166312327160600110000000000#503840036732# Avery Martin 1121042880000624 +705Skullrune bonus pay for amazing work on #OSS 00010000624 +622231380104420608333324810060000000000#777475118288# Jacob Anderson 1121042880000625 +705Stallionfir bonus pay for amazing work on #OSS 00010000625 +622231380104146847723047780510000000000#653182400827# Addison Smith 1121042880000626 +705Waspwater bonus pay for amazing work on #OSS 00010000626 +622231380104428575332188138450000000000#771181662221# Zoey Martin 1121042880000627 +705Chopperhorn bonus pay for amazing work on #OSS 00010000627 +622231380104217870240332824880000000000#347884172632# Sophia Moore 1121042880000628 +705Lanternhoneysuckle bonus pay for amazing work on #OSS 00010000628 +622231380104080201743447230630000000000#718346447657# Charlotte Taylor 1121042880000629 +705Scowlchatter bonus pay for amazing work on #OSS 00010000629 +622231380104145482367822858060000000000#513472516422# Joseph Johnson 1121042880000630 +705Batebony bonus pay for amazing work on #OSS 00010000630 +622231380104557850321100862830000000000#883215188401# Andrew Davis 1121042880000631 +705Binderperidot bonus pay for amazing work on #OSS 00010000631 +622231380104676436365270857240000000000#025613617424# Joseph Harris 1121042880000632 +705Touchquilt bonus pay for amazing work on #OSS 00010000632 +622231380104444762004622630340000000000#280840756885# William Martin 1121042880000633 +705Maredirt bonus pay for amazing work on #OSS 00010000633 +622231380104587885383414037660000000000#761207535025# Jacob Miller 1121042880000634 +705Diverdull bonus pay for amazing work on #OSS 00010000634 +622231380104841057240106332450000000000#732682528468# Emma Thomas 1121042880000635 +705Huggerhollow bonus pay for amazing work on #OSS 00010000635 +622231380104054841767785028010000000000#228222804717# Isabella Thomas 1121042880000636 +705Devourerwinter bonus pay for amazing work on #OSS 00010000636 +622231380104567302033000362750000000000#045305105600# Joseph Moore 1121042880000637 +705Warlocklight bonus pay for amazing work on #OSS 00010000637 +622231380104256281816044536570000000000#246205154747# Jayden Davis 1121042880000638 +705Butterflychain bonus pay for amazing work on #OSS 00010000638 +622231380104257227388816680230000000000#780485862208# Aiden Anderson 1121042880000639 +705Raptorstorm bonus pay for amazing work on #OSS 00010000639 +622231380104511016271670807310000000000#332262473263# Benjamin Wilson 1121042880000640 +705Graspglitter bonus pay for amazing work on #OSS 00010000640 +622231380104650535088873564380000000000#850447160541# Joshua White 1121042880000641 +705Dukebold bonus pay for amazing work on #OSS 00010000641 +622231380104403423777107328720000000000#720310365227# Michael Johnson 1121042880000642 +705Gorillaflax bonus pay for amazing work on #OSS 00010000642 +622231380104332748632207828680000000000#788218322256# Sophia Thompson 1121042880000643 +705Scourgesaber bonus pay for amazing work on #OSS 00010000643 +622231380104631117244776263840000000000#527233205002# Anthony Williams 1121042880000644 +705Beakautumn bonus pay for amazing work on #OSS 00010000644 +622231380104783237484116125280000000000#058881335216# Noah Moore 1121042880000645 +705Lanternhorn bonus pay for amazing work on #OSS 00010000645 +622231380104645076725401054350000000000#325820663000# Aiden Martinez 1121042880000646 +705Zebracarnelian bonus pay for amazing work on #OSS 00010000646 +622231380104368555475054816320000000000#368702772365# Emma Garcia 1121042880000647 +705Hisserink bonus pay for amazing work on #OSS 00010000647 +622231380104351240807310600840000000000#225110813583# Benjamin Harris 1121042880000648 +705Shriekermercury bonus pay for amazing work on #OSS 00010000648 +622231380104810844750857414250000000000#103858147308# Isabella Garcia 1121042880000649 +705Bowfoil bonus pay for amazing work on #OSS 00010000649 +622231380104828618726076618210000000000#284183600674# Benjamin Miller 1121042880000650 +705Pawazure bonus pay for amazing work on #OSS 00010000650 +622231380104320613160476744330000000000#155762730714# David Williams 1121042880000651 +705Pixiequill bonus pay for amazing work on #OSS 00010000651 +622231380104562356066178174880000000000#084318662103# Alexander Williams 1121042880000652 +705Bootisland bonus pay for amazing work on #OSS 00010000652 +622231380104448088507707825430000000000#457047087376# Addison Anderson 1121042880000653 +705Dragonsalt bonus pay for amazing work on #OSS 00010000653 +622231380104664354757768876820000000000#105014873503# Daniel Anderson 1121042880000654 +705Savergravel bonus pay for amazing work on #OSS 00010000654 +622231380104405545578546441340000000000#818661844464# Alexander Thomas 1121042880000655 +705Carverbloom bonus pay for amazing work on #OSS 00010000655 +622231380104673881388332035670000000000#606558320165# Chloe Brown 1121042880000656 +705Jestershine bonus pay for amazing work on #OSS 00010000656 +622231380104751381417226616610000000000#534514622236# Isabella Jackson 1121042880000657 +705Turnerstump bonus pay for amazing work on #OSS 00010000657 +622231380104582483883635712640000000000#881076583041# Liam Thomas 1121042880000658 +705Iguananoble bonus pay for amazing work on #OSS 00010000658 +622231380104408815146248007830000000000#451862566814# Lily Anderson 1121042880000659 +705Wingplain bonus pay for amazing work on #OSS 00010000659 +622231380104850476460270103680000000000#103110565418# Anthony Jones 1121042880000660 +705Lionribbon bonus pay for amazing work on #OSS 00010000660 +622231380104337026471000541370000000000#741837517560# Avery Wilson 1121042880000661 +705Singerjust bonus pay for amazing work on #OSS 00010000661 +622231380104547063003237588350000000000#178055333811# Daniel Johnson 1121042880000662 +705Paladinamber bonus pay for amazing work on #OSS 00010000662 +622231380104108374606113270030000000000#676022013460# Liam Smith 1121042880000663 +705Fishpie bonus pay for amazing work on #OSS 00010000663 +622231380104115687348185126100000000000#881038675506# Olivia White 1121042880000664 +705Biteripple bonus pay for amazing work on #OSS 00010000664 +622231380104582856601525054260000000000#783078144268# Zoey Davis 1121042880000665 +705Volesulpher bonus pay for amazing work on #OSS 00010000665 +622231380104021006211423174530000000000#802681155310# James Davis 1121042880000666 +705Chinboom bonus pay for amazing work on #OSS 00010000666 +622231380104688067227186516050000000000#074720018838# Sofia Martinez 1121042880000667 +705Mustangpale bonus pay for amazing work on #OSS 00010000667 +622231380104813145184555285000000000000#544125067447# Mia Brown 1121042880000668 +705Wizardnebula bonus pay for amazing work on #OSS 00010000668 +622231380104861541782463405410000000000#245848338722# Jayden Martinez 1121042880000669 +705Dancerregal bonus pay for amazing work on #OSS 00010000669 +622231380104388624780550815260000000000#573583745505# Joseph Robinson 1121042880000670 +705Toucanrampant bonus pay for amazing work on #OSS 00010000670 +622231380104384127736172237270000000000#523012745247# Avery Martinez 1121042880000671 +705Tonguesplit bonus pay for amazing work on #OSS 00010000671 +622231380104747145313237451210000000000#014122324023# Michael Thompson 1121042880000672 +705Hissleather bonus pay for amazing work on #OSS 00010000672 +622231380104312666782053323600000000000#133758608882# Olivia Jones 1121042880000673 +705Dancerpinto bonus pay for amazing work on #OSS 00010000673 +622231380104567745640010127070000000000#143683833545# Ava Thompson 1121042880000674 +705Craneflash bonus pay for amazing work on #OSS 00010000674 +622231380104234571058736502050000000000#680656821161# Joshua White 1121042880000675 +705Spearnorth bonus pay for amazing work on #OSS 00010000675 +622231380104584215042183053520000000000#652217182384# Joshua Brown 1121042880000676 +705Chillseason bonus pay for amazing work on #OSS 00010000676 +622231380104030867714042542830000000000#851577822636# Anthony Miller 1121042880000677 +705Grabbertulip bonus pay for amazing work on #OSS 00010000677 +622231380104622617422274751040000000000#783012470371# Jayden Thomas 1121042880000678 +705Singerslash bonus pay for amazing work on #OSS 00010000678 +622231380104568053822136155840000000000#112545161582# William Martin 1121042880000679 +705Salmonfree bonus pay for amazing work on #OSS 00010000679 +622231380104558528310283166570000000000#315725721648# Emily Johnson 1121042880000680 +705Ringerbig bonus pay for amazing work on #OSS 00010000680 +622231380104316571015886768050000000000#345026120574# David Garcia 1121042880000681 +705Roareratom bonus pay for amazing work on #OSS 00010000681 +622231380104755336220586646810000000000#060024374305# Aubrey Brown 1121042880000682 +705Songcoal bonus pay for amazing work on #OSS 00010000682 +622231380104451583756227374300000000000#166671472520# Mia Smith 1121042880000683 +705Followerplump bonus pay for amazing work on #OSS 00010000683 +622231380104230701134005337360000000000#417568002348# Sophia Garcia 1121042880000684 +705Bonewater bonus pay for amazing work on #OSS 00010000684 +622231380104332572723457020560000000000#533737716517# Sophia Davis 1121042880000685 +705Fingernimble bonus pay for amazing work on #OSS 00010000685 +622231380104115248106631700010000000000#514500300561# Sophia Thomas 1121042880000686 +705Panthersteel bonus pay for amazing work on #OSS 00010000686 +622231380104235213102870058800000000000#861631774333# Zoey Davis 1121042880000687 +705Driftergrave bonus pay for amazing work on #OSS 00010000687 +622231380104163780042118267450000000000#438442837416# Aiden Wilson 1121042880000688 +705Cubphase bonus pay for amazing work on #OSS 00010000688 +622231380104388345657711701850000000000#764752337157# Elizabeth Robinson 1121042880000689 +705Lordcoral bonus pay for amazing work on #OSS 00010000689 +622231380104381550138372704670000000000#366527682658# Zoey Thompson 1121042880000690 +705Deathspangle bonus pay for amazing work on #OSS 00010000690 +622231380104768002410476667620000000000#671156851285# Avery Williams 1121042880000691 +705Queenshimmer bonus pay for amazing work on #OSS 00010000691 +622231380104461653771031054370000000000#018712207072# Liam Brown 1121042880000692 +705Lasherplain bonus pay for amazing work on #OSS 00010000692 +622231380104042357867533607800000000000#870707311128# Ava White 1121042880000693 +705Ninjadestiny bonus pay for amazing work on #OSS 00010000693 +622231380104862087215534263680000000000#034103525321# Isabella Garcia 1121042880000694 +705Trackerflax bonus pay for amazing work on #OSS 00010000694 +622231380104725151851266767480000000000#854025554531# Natalie Moore 1121042880000695 +705Roarbog bonus pay for amazing work on #OSS 00010000695 +622231380104642034047448818660000000000#301485585666# Natalie Thomas 1121042880000696 +705Hissmica bonus pay for amazing work on #OSS 00010000696 +622231380104600251164112241770000000000#387756153123# Mason Robinson 1121042880000697 +705Lighteralpine bonus pay for amazing work on #OSS 00010000697 +622231380104661217504448260440000000000#280152524520# Aiden White 1121042880000698 +705Glasstorch bonus pay for amazing work on #OSS 00010000698 +622231380104224341523517722500000000000#245755222768# Elizabeth Williams 1121042880000699 +705Razorsunrise bonus pay for amazing work on #OSS 00010000699 +622231380104764343817382628830000000000#641530741677# Zoey Davis 1121042880000700 +705Snakefortune bonus pay for amazing work on #OSS 00010000700 +622231380104801504601617337810000000000#617085881652# Aubrey Jones 1121042880000701 +705Bisonrose bonus pay for amazing work on #OSS 00010000701 +622231380104066856606477165400000000000#041167106851# Liam Thompson 1121042880000702 +705Bellyobsidian bonus pay for amazing work on #OSS 00010000702 +622231380104177078156573414740000000000#258206651413# James Brown 1121042880000703 +705Monkeylove bonus pay for amazing work on #OSS 00010000703 +622231380104081674714740060810000000000#270832111184# Addison Miller 1121042880000704 +705Yakequinox bonus pay for amazing work on #OSS 00010000704 +622231380104852068355401608440000000000#086704606862# Elijah Jackson 1121042880000705 +705Skinnerscratch bonus pay for amazing work on #OSS 00010000705 +622231380104713057088272804630000000000#565222125348# Olivia Johnson 1121042880000706 +705Ladyplanet bonus pay for amazing work on #OSS 00010000706 +622231380104401023885033515160000000000#210602020664# Sofia Smith 1121042880000707 +705Bindertree bonus pay for amazing work on #OSS 00010000707 +622231380104858055528603213550000000000#602243601455# Mia Brown 1121042880000708 +705Ninjabevel bonus pay for amazing work on #OSS 00010000708 +622231380104174063546235341340000000000#086372104611# Mia Thomas 1121042880000709 +705Lasherplump bonus pay for amazing work on #OSS 00010000709 +622231380104276101542877721310000000000#418714221416# Joshua Moore 1121042880000710 +705Jaguarcrystal bonus pay for amazing work on #OSS 00010000710 +622231380104350630051207125240000000000#060281366005# David Thomas 1121042880000711 +705Ravercanyon bonus pay for amazing work on #OSS 00010000711 +622231380104007174575832010080000000000#065247414447# Lily Martinez 1121042880000712 +705Keeperchisel bonus pay for amazing work on #OSS 00010000712 +622231380104840260860427507560000000000#708055324408# Jayden Brown 1121042880000713 +705Spiritshort bonus pay for amazing work on #OSS 00010000713 +622231380104636044762678766370000000000#716621374175# Aubrey Thomas 1121042880000714 +705Horsecandy bonus pay for amazing work on #OSS 00010000714 +622231380104628812756804800750000000000#731806173267# Andrew Anderson 1121042880000715 +705Cougarcanyon bonus pay for amazing work on #OSS 00010000715 +622231380104625720131255171600000000000#461702452228# Andrew Harris 1121042880000716 +705Playergrave bonus pay for amazing work on #OSS 00010000716 +622231380104635015348528018050000000000#734071065776# William Robinson 1121042880000717 +705Dartcedar bonus pay for amazing work on #OSS 00010000717 +622231380104583830083771018080000000000#165446800735# Abigail Smith 1121042880000718 +705Cloakbranch bonus pay for amazing work on #OSS 00010000718 +622231380104618886611366160420000000000#802422022588# Andrew Jackson 1121042880000719 +705Lifteramber bonus pay for amazing work on #OSS 00010000719 +622231380104780417257877551320000000000#048657823256# Mia Brown 1121042880000720 +705Stealerthorn bonus pay for amazing work on #OSS 00010000720 +622231380104142400354786047230000000000#352355137620# Liam Robinson 1121042880000721 +705Swordscratch bonus pay for amazing work on #OSS 00010000721 +622231380104112202731326488130000000000#757884562561# Sophia Brown 1121042880000722 +705Antwax bonus pay for amazing work on #OSS 00010000722 +622231380104656116442452488060000000000#350716201730# Lily Taylor 1121042880000723 +705Huggervoid bonus pay for amazing work on #OSS 00010000723 +622231380104865334776766102710000000000#340028703057# Abigail Martin 1121042880000724 +705Divethread bonus pay for amazing work on #OSS 00010000724 +622231380104608545812762443430000000000#216543220883# Avery Jackson 1121042880000725 +705Roverroot bonus pay for amazing work on #OSS 00010000725 +622231380104441102650650188300000000000#477338102755# Joseph Brown 1121042880000726 +705Rippervine bonus pay for amazing work on #OSS 00010000726 +622231380104554041340161570370000000000#337301168823# Natalie Smith 1121042880000727 +705Traderlunar bonus pay for amazing work on #OSS 00010000727 +622231380104122308326567838220000000000#430514080056# Emily Johnson 1121042880000728 +705Catpuddle bonus pay for amazing work on #OSS 00010000728 +622231380104400812047213028660000000000#743462122824# Lily Williams 1121042880000729 +705Jayiris bonus pay for amazing work on #OSS 00010000729 +622231380104103324334623247720000000000#114303253258# Matthew White 1121042880000730 +705Healerwood bonus pay for amazing work on #OSS 00010000730 +622231380104437556612700385360000000000#681736303401# Madison Brown 1121042880000731 +705Antlergossamer bonus pay for amazing work on #OSS 00010000731 +622231380104780530114188708340000000000#702663655148# Zoey Thompson 1121042880000732 +705Roarbrick bonus pay for amazing work on #OSS 00010000732 +622231380104224605626803725030000000000#546320261324# Natalie Thompson 1121042880000733 +705Lasherwave bonus pay for amazing work on #OSS 00010000733 +622231380104826524230640574700000000000#726888253270# Sofia Thomas 1121042880000734 +705Touchtree bonus pay for amazing work on #OSS 00010000734 +622231380104260070725730544810000000000#684777635486# Daniel Martinez 1121042880000735 +705Moledestiny bonus pay for amazing work on #OSS 00010000735 +622231380104865282337101384450000000000#300158178821# Emma Miller 1121042880000736 +705Whaletiny bonus pay for amazing work on #OSS 00010000736 +622231380104266018218256838450000000000#833358821283# Aubrey Miller 1121042880000737 +705Pixiepie bonus pay for amazing work on #OSS 00010000737 +622231380104867264705244487120000000000#806420878103# Alexander Robinson 1121042880000738 +705Roverthunder bonus pay for amazing work on #OSS 00010000738 +622231380104343220617104367300000000000#241655365524# Joseph Jackson 1121042880000739 +705Snarlspice bonus pay for amazing work on #OSS 00010000739 +622231380104877257627161504280000000000#867841238704# Benjamin Jones 1121042880000740 +705Storkmoor bonus pay for amazing work on #OSS 00010000740 +622231380104320508154783421160000000000#243658801003# Joshua Thomas 1121042880000741 +705Healerrain bonus pay for amazing work on #OSS 00010000741 +622231380104172086117130052240000000000#731787361637# Daniel Williams 1121042880000742 +705Beebush bonus pay for amazing work on #OSS 00010000742 +622231380104168821767328820770000000000#470118727375# Addison Martinez 1121042880000743 +705Elfheavy bonus pay for amazing work on #OSS 00010000743 +622231380104486563766834217360000000000#657085385501# Joseph Smith 1121042880000744 +705Boarshallow bonus pay for amazing work on #OSS 00010000744 +622231380104382357365165587610000000000#182644516054# Jayden Taylor 1121042880000745 +705Chattermarsh bonus pay for amazing work on #OSS 00010000745 +622231380104312014372506130160000000000#542586685057# Jacob Thomas 1121042880000746 +705Chargerbrown bonus pay for amazing work on #OSS 00010000746 +622231380104731602138028803160000000000#218811160243# Chloe Jones 1121042880000747 +705Devourerclear bonus pay for amazing work on #OSS 00010000747 +622231380104857441224131234860000000000#043210420602# Isabella Robinson 1121042880000748 +705Spearshine bonus pay for amazing work on #OSS 00010000748 +622231380104178288602377008330000000000#206276764424# Noah Taylor 1121042880000749 +705Beeplum bonus pay for amazing work on #OSS 00010000749 +622231380104881506163683028410000000000#405250153216# David Thomas 1121042880000750 +705Choppercute bonus pay for amazing work on #OSS 00010000750 +622231380104375383115560143730000000000#671743721683# Aiden Thompson 1121042880000751 +705Butterflyvalley bonus pay for amazing work on #OSS 00010000751 +622231380104356826231168725120000000000#707001287520# Emma Williams 1121042880000752 +705Chestwater bonus pay for amazing work on #OSS 00010000752 +622231380104052372235607806330000000000#046145874345# Chloe Smith 1121042880000753 +705Huntersoft bonus pay for amazing work on #OSS 00010000753 +622231380104166750757005730750000000000#370847464265# Liam Robinson 1121042880000754 +705Knavegrove bonus pay for amazing work on #OSS 00010000754 +622231380104551256247481137300000000000#341412363435# Anthony Taylor 1121042880000755 +705Singerhate bonus pay for amazing work on #OSS 00010000755 +622231380104028853020251878040000000000#206327542468# Chloe Williams 1121042880000756 +705Jackallavender bonus pay for amazing work on #OSS 00010000756 +622231380104106052770134315570000000000#838020173237# Madison Smith 1121042880000757 +705Heroglow bonus pay for amazing work on #OSS 00010000757 +622231380104201504470411584310000000000#533010273874# Daniel Garcia 1121042880000758 +705Seertwisty bonus pay for amazing work on #OSS 00010000758 +622231380104254530263216365740000000000#185415872754# Joshua Martinez 1121042880000759 +705Ringerfuschia bonus pay for amazing work on #OSS 00010000759 +622231380104282718840588358670000000000#303548024454# David Thomas 1121042880000760 +705Burnboulder bonus pay for amazing work on #OSS 00010000760 +622231380104514743233522024560000000000#187266638228# Noah Moore 1121042880000761 +705Watcherrhinestone bonus pay for amazing work on #OSS 00010000761 +622231380104561436677564883630000000000#062148722132# Emma Jackson 1121042880000762 +705Gembronze bonus pay for amazing work on #OSS 00010000762 +622231380104830343636137720050000000000#737236878357# Ava Garcia 1121042880000763 +705Yaklemon bonus pay for amazing work on #OSS 00010000763 +622231380104254451588148127440000000000#257623841118# Zoey Jones 1121042880000764 +705Spritestripe bonus pay for amazing work on #OSS 00010000764 +622231380104400524385277612070000000000#148825045302# Elijah Moore 1121042880000765 +705Terrierwheat bonus pay for amazing work on #OSS 00010000765 +622231380104062244563034653820000000000#330835275844# Olivia Miller 1121042880000766 +705Tongueruby bonus pay for amazing work on #OSS 00010000766 +622231380104334662281372107700000000000#643221164046# Elijah Harris 1121042880000767 +705Boarpineapple bonus pay for amazing work on #OSS 00010000767 +622231380104174253025748366160000000000#501608530455# Jayden Miller 1121042880000768 +705Flywater bonus pay for amazing work on #OSS 00010000768 +622231380104203184588217262530000000000#732210871670# Anthony Thompson 1121042880000769 +705Robincanyon bonus pay for amazing work on #OSS 00010000769 +622231380104640784567758327180000000000#702758088253# Daniel Robinson 1121042880000770 +705Headtulip bonus pay for amazing work on #OSS 00010000770 +622231380104825762047506051250000000000#160732401133# Mia Harris 1121042880000771 +705Ninjacandle bonus pay for amazing work on #OSS 00010000771 +622231380104252638020382615310000000000#240307380774# Joshua Williams 1121042880000772 +705Shirtchip bonus pay for amazing work on #OSS 00010000772 +622231380104474126582584450300000000000#555601342331# Aubrey Thomas 1121042880000773 +705Edgefuschia bonus pay for amazing work on #OSS 00010000773 +622231380104115325444621337610000000000#333136783013# Joshua Smith 1121042880000774 +705Scourgeaquamarine bonus pay for amazing work on #OSS 00010000774 +622231380104058377723542185170000000000#771805516548# Mia Harris 1121042880000775 +705Speakercoal bonus pay for amazing work on #OSS 00010000775 +622231380104200673464424603450000000000#326211286345# Joseph Anderson 1121042880000776 +705Swordcoffee bonus pay for amazing work on #OSS 00010000776 +622231380104523772328876577660000000000#301371831176# Lily Davis 1121042880000777 +705Knavenight bonus pay for amazing work on #OSS 00010000777 +622231380104585021460433800680000000000#711533338442# Olivia Robinson 1121042880000778 +705Fingerharvest bonus pay for amazing work on #OSS 00010000778 +622231380104724403785225844580000000000#216501402846# Sophia Garcia 1121042880000779 +705Vipersilent bonus pay for amazing work on #OSS 00010000779 +622231380104466057185043735230000000000#210280752886# Jacob Martinez 1121042880000780 +705Anteloperoot bonus pay for amazing work on #OSS 00010000780 +622231380104540181066122715220000000000#358164285524# Aiden Johnson 1121042880000781 +705Gorillapitch bonus pay for amazing work on #OSS 00010000781 +622231380104021132283501800240000000000#877325235583# Madison Harris 1121042880000782 +705Spritetwilight bonus pay for amazing work on #OSS 00010000782 +622231380104345406434281682840000000000#324108060368# Joshua Davis 1121042880000783 +705Goatiridescent bonus pay for amazing work on #OSS 00010000783 +622231380104756475333570151310000000000#233216786751# Joseph Anderson 1121042880000784 +705Fighterharvest bonus pay for amazing work on #OSS 00010000784 +622231380104840618855653228280000000000#801350000310# Avery Jones 1121042880000785 +705Gazellequartz bonus pay for amazing work on #OSS 00010000785 +622231380104052047117224285280000000000#131815844770# Matthew Jones 1121042880000786 +705Lashergarnet bonus pay for amazing work on #OSS 00010000786 +622231380104736747661556310110000000000#448584244342# Emily Anderson 1121042880000787 +705Talonrose bonus pay for amazing work on #OSS 00010000787 +622231380104341307317822527220000000000#531704805361# Ava Taylor 1121042880000788 +705Voleblue bonus pay for amazing work on #OSS 00010000788 +622231380104236751507446775010000000000#131288331472# Joseph Wilson 1121042880000789 +705Trackeralder bonus pay for amazing work on #OSS 00010000789 +622231380104078584521751651060000000000#580318580721# Jayden Martin 1121042880000790 +705Kangarooblossom bonus pay for amazing work on #OSS 00010000790 +622231380104508458663181157370000000000#232054481837# Daniel Miller 1121042880000791 +705Orioleeast bonus pay for amazing work on #OSS 00010000791 +622231380104262002732718688000000000000#156742247612# Zoey Anderson 1121042880000792 +705Geckocerulean bonus pay for amazing work on #OSS 00010000792 +622231380104348028530358532870000000000#748105487776# Olivia Jones 1121042880000793 +705Hisstreasure bonus pay for amazing work on #OSS 00010000793 +622231380104513501748865300770000000000#350305386867# Michael Moore 1121042880000794 +705Kickerjasper bonus pay for amazing work on #OSS 00010000794 +622231380104167312235608036870000000000#537028302050# Joshua Garcia 1121042880000795 +705Knaveclear bonus pay for amazing work on #OSS 00010000795 +622231380104500703158337504820000000000#206607413733# Jayden Garcia 1121042880000796 +705Apecopper bonus pay for amazing work on #OSS 00010000796 +622231380104521464074750507110000000000#742235878157# Aubrey Taylor 1121042880000797 +705Doomaquamarine bonus pay for amazing work on #OSS 00010000797 +622231380104870445221462438210000000000#422146807640# Emily Robinson 1121042880000798 +705Viperheather bonus pay for amazing work on #OSS 00010000798 +622231380104377448104227502600000000000#353455406523# Sofia Jones 1121042880000799 +705Crowpebble bonus pay for amazing work on #OSS 00010000799 +622231380104645230853870826610000000000#386222252343# Charlotte Taylor 1121042880000800 +705Legquilt bonus pay for amazing work on #OSS 00010000800 +622231380104486727068841703320000000000#882601473314# Anthony Smith 1121042880000801 +705Snagglefootmercury bonus pay for amazing work on #OSS 00010000801 +622231380104675388858756788320000000000#581051444842# Sofia Martinez 1121042880000802 +705Fairydour bonus pay for amazing work on #OSS 00010000802 +622231380104401048063222071380000000000#603228305022# Isabella Martinez 1121042880000803 +705Kingcord bonus pay for amazing work on #OSS 00010000803 +622231380104676170648552877080000000000#727863048745# Mia Jackson 1121042880000804 +705Giversteel bonus pay for amazing work on #OSS 00010000804 +622231380104650303047024635060000000000#083800154651# Ella Williams 1121042880000805 +705Cranefeather bonus pay for amazing work on #OSS 00010000805 +622231380104344145707703886820000000000#625518060064# Andrew White 1121042880000806 +705Twisteroil bonus pay for amazing work on #OSS 00010000806 +622231380104162504208542032050000000000#488262731178# Daniel Garcia 1121042880000807 +705Ferrettide bonus pay for amazing work on #OSS 00010000807 +622231380104377413325338217030000000000#284471556458# Andrew Martin 1121042880000808 +705Friendobsidian bonus pay for amazing work on #OSS 00010000808 +622231380104410244468047326700000000000#158440652725# Andrew Moore 1121042880000809 +705Parrotmotley bonus pay for amazing work on #OSS 00010000809 +622231380104286610423461462480000000000#548057276641# Liam Anderson 1121042880000810 +705Rabbitstump bonus pay for amazing work on #OSS 00010000810 +622231380104548161188110812150000000000#171582276767# David Garcia 1121042880000811 +705Spikecarnation bonus pay for amazing work on #OSS 00010000811 +622231380104210123156454526620000000000#242213535655# Jacob Wilson 1121042880000812 +705Twisterglen bonus pay for amazing work on #OSS 00010000812 +622231380104274768758870010650000000000#362541335648# Sofia Martin 1121042880000813 +705Paintercanyon bonus pay for amazing work on #OSS 00010000813 +622231380104438746313453736370000000000#223045624271# Alexander Jackson 1121042880000814 +705Fightershore bonus pay for amazing work on #OSS 00010000814 +622231380104587246880827513500000000000#275016111761# Elizabeth Davis 1121042880000815 +705Browtopaz bonus pay for amazing work on #OSS 00010000815 +622231380104843801660785208570000000000#431816777226# Anthony Harris 1121042880000816 +705Ripperspark bonus pay for amazing work on #OSS 00010000816 +622231380104686223075385165000000000000#273248312188# David Smith 1121042880000817 +705Queendenim bonus pay for amazing work on #OSS 00010000817 +622231380104077436325245414010000000000#527021341662# Olivia Robinson 1121042880000818 +705Wizardspectrum bonus pay for amazing work on #OSS 00010000818 +622231380104827525275862584120000000000#236726806825# James Moore 1121042880000819 +705Ratcoffee bonus pay for amazing work on #OSS 00010000819 +622231380104424720031141771100000000000#667207634374# Andrew Anderson 1121042880000820 +705Bearcarnelian bonus pay for amazing work on #OSS 00010000820 +622231380104611036235853720080000000000#602345451275# Andrew Wilson 1121042880000821 +705Carpetsun bonus pay for amazing work on #OSS 00010000821 +622231380104477542774781554820000000000#678622773618# Elizabeth White 1121042880000822 +705Markclover bonus pay for amazing work on #OSS 00010000822 +622231380104271632026204037180000000000#124101046135# Benjamin Robinson 1121042880000823 +705Wolfpurple bonus pay for amazing work on #OSS 00010000823 +622231380104178280356304188700000000000#846436423320# Emma Thompson 1121042880000824 +705Stallionstorm bonus pay for amazing work on #OSS 00010000824 +622231380104346634220807380170000000000#010787265862# Madison Moore 1121042880000825 +705Sageslow bonus pay for amazing work on #OSS 00010000825 +622231380104535731184363327860000000000#340332308801# Emily Jackson 1121042880000826 +705Foxrattle bonus pay for amazing work on #OSS 00010000826 +622231380104163165550700442810000000000#866762431155# Matthew Wilson 1121042880000827 +705Skullthunder bonus pay for amazing work on #OSS 00010000827 +622231380104028374347825238660000000000#326726856828# Madison Martinez 1121042880000828 +705Ferretwhip bonus pay for amazing work on #OSS 00010000828 +622231380104217645210777225710000000000#110182261171# David Robinson 1121042880000829 +705Piperprickle bonus pay for amazing work on #OSS 00010000829 +622231380104884782466637803160000000000#751865085250# Alexander Jackson 1121042880000830 +705Saverclever bonus pay for amazing work on #OSS 00010000830 +622231380104703558265827402830000000000#432120438616# Aubrey Wilson 1121042880000831 +705Gemcharm bonus pay for amazing work on #OSS 00010000831 +622231380104382374033084037300000000000#187430146006# Aubrey Miller 1121042880000832 +705Ripperhoney bonus pay for amazing work on #OSS 00010000832 +622231380104036646321625740070000000000#146024352110# Joseph Brown 1121042880000833 +705Reaperwest bonus pay for amazing work on #OSS 00010000833 +622231380104153344620312662030000000000#460055232163# Sofia Taylor 1121042880000834 +705Carpetviridian bonus pay for amazing work on #OSS 00010000834 +622231380104330613336075447520000000000#327620618171# Emma Jones 1121042880000835 +705Palmtin bonus pay for amazing work on #OSS 00010000835 +622231380104067678561183068530000000000#667055717216# Daniel Robinson 1121042880000836 +705Snarlprickle bonus pay for amazing work on #OSS 00010000836 +622231380104711060777800261560000000000#700442173860# Jacob Jones 1121042880000837 +705Donkeylava bonus pay for amazing work on #OSS 00010000837 +622231380104663228766174406720000000000#671608314650# James Davis 1121042880000838 +705Bisonplume bonus pay for amazing work on #OSS 00010000838 +622231380104100254506713833310000000000#212088356818# Noah Harris 1121042880000839 +705Scarertitanium bonus pay for amazing work on #OSS 00010000839 +622231380104406384583653237160000000000#460274544134# Jacob Moore 1121042880000840 +705Kangaroolake bonus pay for amazing work on #OSS 00010000840 +622231380104085060803382876240000000000#283660302362# Benjamin Miller 1121042880000841 +705Gargoylestump bonus pay for amazing work on #OSS 00010000841 +622231380104533535874184582870000000000#101618387101# Chloe Garcia 1121042880000842 +705Jackalfrost bonus pay for amazing work on #OSS 00010000842 +622231380104448171455456633420000000000#087755535871# Chloe Martin 1121042880000843 +705Fighterplain bonus pay for amazing work on #OSS 00010000843 +622231380104808361121867082710000000000#443511134812# Addison White 1121042880000844 +705Horsepinto bonus pay for amazing work on #OSS 00010000844 +622231380104077405271081038530000000000#338061383043# Sophia Smith 1121042880000845 +705Pipercurly bonus pay for amazing work on #OSS 00010000845 +622231380104440834860542824110000000000#227464087022# Mia Moore 1121042880000846 +705Forgerfish bonus pay for amazing work on #OSS 00010000846 +622231380104654247212623208050000000000#461460047236# Jacob Williams 1121042880000847 +705Flierdeep bonus pay for amazing work on #OSS 00010000847 +622231380104100583875080642050000000000#072341744081# Michael Robinson 1121042880000848 +705Takervolcano bonus pay for amazing work on #OSS 00010000848 +622231380104453421713116606670000000000#451137312853# Abigail Thomas 1121042880000849 +705Iguanasage bonus pay for amazing work on #OSS 00010000849 +622231380104585344503276802750000000000#737826413510# Addison Wilson 1121042880000850 +705Warriorplain bonus pay for amazing work on #OSS 00010000850 +622231380104700203556756648800000000000#325808373011# Jayden Robinson 1121042880000851 +705Friendperidot bonus pay for amazing work on #OSS 00010000851 +622231380104852661800024378800000000000#836748777766# Lily Taylor 1121042880000852 +705Goatplume bonus pay for amazing work on #OSS 00010000852 +622231380104285271140730357230000000000#871277282131# Joseph Garcia 1121042880000853 +705Neckcliff bonus pay for amazing work on #OSS 00010000853 +622231380104261735748601767170000000000#641870011815# Benjamin Martinez 1121042880000854 +705Piratesavage bonus pay for amazing work on #OSS 00010000854 +622231380104023037305807867130000000000#615273805706# Lily Miller 1121042880000855 +705Dartbevel bonus pay for amazing work on #OSS 00010000855 +622231380104308447408576632260000000000#341008141551# Liam Thompson 1121042880000856 +705Masterjelly bonus pay for amazing work on #OSS 00010000856 +622231380104113244140721131370000000000#326750487564# Olivia Anderson 1121042880000857 +705Ottersoft bonus pay for amazing work on #OSS 00010000857 +622231380104538726661305551500000000000#043070784210# Aiden Johnson 1121042880000858 +705Jesterpalm bonus pay for amazing work on #OSS 00010000858 +622231380104478160772857135300000000000#266436810773# Aubrey Davis 1121042880000859 +705Huggerfossil bonus pay for amazing work on #OSS 00010000859 +622231380104758617735736621010000000000#425278442173# Isabella Garcia 1121042880000860 +705Whimseydawn bonus pay for amazing work on #OSS 00010000860 +622231380104231816584176723720000000000#832538574804# David Jones 1121042880000861 +705Crystallunar bonus pay for amazing work on #OSS 00010000861 +622231380104414284876848837540000000000#360668328218# Madison Taylor 1121042880000862 +705Lightningtundra bonus pay for amazing work on #OSS 00010000862 +622231380104523322463130625350000000000#272284287302# Alexander Garcia 1121042880000863 +705Guardianluck bonus pay for amazing work on #OSS 00010000863 +622231380104108486554814648240000000000#115253052208# Addison Jones 1121042880000864 +705Howlervenom bonus pay for amazing work on #OSS 00010000864 +622231380104446445074080607670000000000#262564630586# Charlotte Wilson 1121042880000865 +705Coyoteorange bonus pay for amazing work on #OSS 00010000865 +622231380104763104042063468300000000000#623673844256# Isabella Jackson 1121042880000866 +705Chillquill bonus pay for amazing work on #OSS 00010000866 +622231380104541333526610540730000000000#136328414077# Emma Martin 1121042880000867 +705Toucancarnation bonus pay for amazing work on #OSS 00010000867 +622231380104260130404217130340000000000#225265765408# David Martin 1121042880000868 +705Snagglefootrazor bonus pay for amazing work on #OSS 00010000868 +622231380104686426432854264600000000000#827435454454# William Taylor 1121042880000869 +705Binderstripe bonus pay for amazing work on #OSS 00010000869 +622231380104657761771787714010000000000#151348558578# Anthony Taylor 1121042880000870 +705Griffindot bonus pay for amazing work on #OSS 00010000870 +622231380104733416014034137330000000000#703134156267# Emma Garcia 1121042880000871 +705Eagleroot bonus pay for amazing work on #OSS 00010000871 +622231380104888288488440022240000000000#564147214114# Lily Jackson 1121042880000872 +705Wanderertwilight bonus pay for amazing work on #OSS 00010000872 +622231380104205177500254476470000000000#526276623737# James Harris 1121042880000873 +705Knightgrove bonus pay for amazing work on #OSS 00010000873 +622231380104618283156385370120000000000#332102552514# Elizabeth White 1121042880000874 +705Pawmuck bonus pay for amazing work on #OSS 00010000874 +622231380104401773546700130850000000000#580805013640# Michael Robinson 1121042880000875 +705Eaglerampant bonus pay for amazing work on #OSS 00010000875 +622231380104437400826255670130000000000#556042502478# Ava Jones 1121042880000876 +705Braidfancy bonus pay for amazing work on #OSS 00010000876 +622231380104352237455042117610000000000#323834872027# Emily Thompson 1121042880000877 +705Scorpionmountain bonus pay for amazing work on #OSS 00010000877 +622231380104656750678514518670000000000#182468355656# Emily Martin 1121042880000878 +705Shriekpurple bonus pay for amazing work on #OSS 00010000878 +622231380104525037561048083400000000000#502823736823# Ava Garcia 1121042880000879 +705Frightsleet bonus pay for amazing work on #OSS 00010000879 +622231380104562105336727803310000000000#568416140422# Ethan Martin 1121042880000880 +705Droprowan bonus pay for amazing work on #OSS 00010000880 +622231380104127473107423534220000000000#267851220311# Charlotte Williams 1121042880000881 +705Neckmaze bonus pay for amazing work on #OSS 00010000881 +622231380104625235104865767240000000000#578563280573# James Miller 1121042880000882 +705Cockatootime bonus pay for amazing work on #OSS 00010000882 +622231380104448176203610021630000000000#558505030017# Jayden Robinson 1121042880000883 +705Coyoteplain bonus pay for amazing work on #OSS 00010000883 +622231380104584003818478684530000000000#543760270702# Michael Anderson 1121042880000884 +705Stalkerdawn bonus pay for amazing work on #OSS 00010000884 +622231380104458723044236401430000000000#152857664633# Addison Garcia 1121042880000885 +705Snapemerald bonus pay for amazing work on #OSS 00010000885 +622231380104418107514586602040000000000#573532844046# Olivia Moore 1121042880000886 +705Thiefchestnut bonus pay for amazing work on #OSS 00010000886 +622231380104464514440761117880000000000#385008155022# Zoey Wilson 1121042880000887 +705Painteramber bonus pay for amazing work on #OSS 00010000887 +622231380104803756461654268320000000000#677538814104# Emma Davis 1121042880000888 +705Chinsplash bonus pay for amazing work on #OSS 00010000888 +622231380104842676130108613260000000000#072660381864# Elizabeth Brown 1121042880000889 +705Craftergossamer bonus pay for amazing work on #OSS 00010000889 +622231380104084832276312527220000000000#153125517607# Natalie Williams 1121042880000890 +705Minnowiron bonus pay for amazing work on #OSS 00010000890 +622231380104754871862742256700000000000#004160405167# Emily Brown 1121042880000891 +705Gargoyleplatinum bonus pay for amazing work on #OSS 00010000891 +622231380104674886658170435050000000000#278674376771# Andrew Smith 1121042880000892 +705Scourgewave bonus pay for amazing work on #OSS 00010000892 +622231380104216662672116002570000000000#328002187336# Avery Williams 1121042880000893 +705Heroncute bonus pay for amazing work on #OSS 00010000893 +622231380104666333076215244630000000000#823256361268# Sophia Robinson 1121042880000894 +705Fightervivid bonus pay for amazing work on #OSS 00010000894 +622231380104666605207475626700000000000#862028452158# Olivia Miller 1121042880000895 +705Haircotton bonus pay for amazing work on #OSS 00010000895 +622231380104255350257282724070000000000#818334454550# James Martinez 1121042880000896 +705Whimseyorchid bonus pay for amazing work on #OSS 00010000896 +622231380104578344788675335400000000000#453175170044# James Thompson 1121042880000897 +705Skullroot bonus pay for amazing work on #OSS 00010000897 +622231380104418042466825053070000000000#451837531247# Sophia Harris 1121042880000898 +705Houndink bonus pay for amazing work on #OSS 00010000898 +622231380104181511085132358210000000000#155722621244# Ethan Smith 1121042880000899 +705Yaksouth bonus pay for amazing work on #OSS 00010000899 +622231380104453070311812153780000000000#657626444764# Lily Davis 1121042880000900 +705Spearhot bonus pay for amazing work on #OSS 00010000900 +622231380104668647412331221760000000000#702217704582# Mia Smith 1121042880000901 +705Handcrystal bonus pay for amazing work on #OSS 00010000901 +622231380104132202322346121750000000000#422233114074# Michael Miller 1121042880000902 +705Elfbristle bonus pay for amazing work on #OSS 00010000902 +622231380104332517134216226180000000000#657887333461# Michael Moore 1121042880000903 +705Kangarooglacier bonus pay for amazing work on #OSS 00010000903 +622231380104300226103703430210000000000#718830012756# Matthew Johnson 1121042880000904 +705Ratballistic bonus pay for amazing work on #OSS 00010000904 +622231380104055024471057231340000000000#081723881212# Zoey Johnson 1121042880000905 +705Dancershade bonus pay for amazing work on #OSS 00010000905 +622231380104584515126380510320000000000#043115125207# Ava Harris 1121042880000906 +705Carveralmond bonus pay for amazing work on #OSS 00010000906 +622231380104540875355303312030000000000#282444645756# Charlotte Jackson 1121042880000907 +705Pumatwisty bonus pay for amazing work on #OSS 00010000907 +622231380104077778362331857800000000000#545802772185# Andrew Harris 1121042880000908 +705Scowlglass bonus pay for amazing work on #OSS 00010000908 +622231380104431851311525168380000000000#551771451677# Sophia Martinez 1121042880000909 +705Snarlsilk bonus pay for amazing work on #OSS 00010000909 +622231380104014523722361300650000000000#642012146540# Sofia Johnson 1121042880000910 +705Puppytide bonus pay for amazing work on #OSS 00010000910 +622231380104480227778062412200000000000#510307810502# Andrew Martinez 1121042880000911 +705Grinriver bonus pay for amazing work on #OSS 00010000911 +622231380104818501221443286120000000000#474341461547# Benjamin Jackson 1121042880000912 +705Scalevanilla bonus pay for amazing work on #OSS 00010000912 +622231380104266860868687523110000000000#108405077465# Ava White 1121042880000913 +705Birdchrome bonus pay for amazing work on #OSS 00010000913 +622231380104613361238041317240000000000#786668018343# David Robinson 1121042880000914 +705Condorshallow bonus pay for amazing work on #OSS 00010000914 +622231380104151433003650526200000000000#411815381511# Elijah Davis 1121042880000915 +705Reaperpollen bonus pay for amazing work on #OSS 00010000915 +622231380104182857480421122820000000000#478416538622# Michael Robinson 1121042880000916 +705Seekerebony bonus pay for amazing work on #OSS 00010000916 +622231380104131078371356206570000000000#708702534463# Jayden Garcia 1121042880000917 +705Ratnoon bonus pay for amazing work on #OSS 00010000917 +622231380104511731365258716070000000000#672647703547# Alexander Moore 1121042880000918 +705Spearfir bonus pay for amazing work on #OSS 00010000918 +622231380104230681251203413360000000000#587407537775# Liam Smith 1121042880000919 +705Parrotpuzzle bonus pay for amazing work on #OSS 00010000919 +622231380104736450770143386710000000000#371823861085# Ava Wilson 1121042880000920 +705Beevivid bonus pay for amazing work on #OSS 00010000920 +622231380104737374133761304280000000000#024788870348# Charlotte Moore 1121042880000921 +705Reaperbutter bonus pay for amazing work on #OSS 00010000921 +622231380104482376000718685680000000000#614258147871# Elizabeth Miller 1121042880000922 +705Reapertulip bonus pay for amazing work on #OSS 00010000922 +622231380104888647201012006520000000000#228174534532# Charlotte Smith 1121042880000923 +705Huggersky bonus pay for amazing work on #OSS 00010000923 +622231380104363475880035330120000000000#585466345328# Ella Davis 1121042880000924 +705Takerchecker bonus pay for amazing work on #OSS 00010000924 +622231380104287474266670588770000000000#163602251718# Benjamin Johnson 1121042880000925 +705Gamblerhorse bonus pay for amazing work on #OSS 00010000925 +622231380104201831052872788160000000000#321188288740# Liam White 1121042880000926 +705Shirtflame bonus pay for amazing work on #OSS 00010000926 +622231380104724447186706813400000000000#450350550703# Olivia Jones 1121042880000927 +705Ferretsnow bonus pay for amazing work on #OSS 00010000927 +622231380104366008280383702750000000000#053600426310# Elijah Williams 1121042880000928 +705Stealerwest bonus pay for amazing work on #OSS 00010000928 +622231380104206444221878200840000000000#761833553353# Isabella Williams 1121042880000929 +705Weaverrune bonus pay for amazing work on #OSS 00010000929 +622231380104450080231735714080000000000#203130431731# Aiden Robinson 1121042880000930 +705Stonedog bonus pay for amazing work on #OSS 00010000930 +622231380104443244161612070670000000000#324483105583# Ava Robinson 1121042880000931 +705Foepatch bonus pay for amazing work on #OSS 00010000931 +622231380104201335428145635340000000000#521081782688# Andrew Garcia 1121042880000932 +705Stingerpyrite bonus pay for amazing work on #OSS 00010000932 +622231380104320414336340882530000000000#652150856030# Daniel Williams 1121042880000933 +705Soarerdaffodil bonus pay for amazing work on #OSS 00010000933 +622231380104076172761844143080000000000#308728021377# James Jackson 1121042880000934 +705Howlerbone bonus pay for amazing work on #OSS 00010000934 +622231380104338408635443132020000000000#457356426255# Lily Anderson 1121042880000935 +705Stallionholy bonus pay for amazing work on #OSS 00010000935 +622231380104787638757414382640000000000#827217545536# Benjamin Taylor 1121042880000936 +705Ferretdaffodil bonus pay for amazing work on #OSS 00010000936 +622231380104825742124742274430000000000#064118613075# Avery Garcia 1121042880000937 +705Tonguegem bonus pay for amazing work on #OSS 00010000937 +622231380104482318640523362160000000000#230375613600# Aiden Davis 1121042880000938 +705Dukeyellow bonus pay for amazing work on #OSS 00010000938 +622231380104260026602108704070000000000#026852347146# Zoey Garcia 1121042880000939 +705Weedshy bonus pay for amazing work on #OSS 00010000939 +622231380104155064730831224170000000000#557487383771# Alexander White 1121042880000940 +705Stormlegend bonus pay for amazing work on #OSS 00010000940 +622231380104011668788005004640000000000#505151372740# Natalie Brown 1121042880000941 +705Salmonlaser bonus pay for amazing work on #OSS 00010000941 +622231380104508710706537048460000000000#300055025640# Charlotte Williams 1121042880000942 +705Goatprism bonus pay for amazing work on #OSS 00010000942 +622231380104102115812540554330000000000#714071538857# Liam Jackson 1121042880000943 +705Arrowevening bonus pay for amazing work on #OSS 00010000943 +622231380104434607673241020830000000000#112211464807# Joshua Williams 1121042880000944 +705Spidercharm bonus pay for amazing work on #OSS 00010000944 +622231380104455028877567203240000000000#864517540447# James Robinson 1121042880000945 +705Carpetcedar bonus pay for amazing work on #OSS 00010000945 +622231380104485366421822424160000000000#766580282337# Mason Brown 1121042880000946 +705Runnersun bonus pay for amazing work on #OSS 00010000946 +622231380104177285354003037070000000000#512730321844# Sofia Jones 1121042880000947 +705Swallowfan bonus pay for amazing work on #OSS 00010000947 +622231380104586215234826732850000000000#526324340362# Matthew Martin 1121042880000948 +705Dartquasar bonus pay for amazing work on #OSS 00010000948 +622231380104163622122048644600000000000#145465826780# Ava Williams 1121042880000949 +705Rabbitcurse bonus pay for amazing work on #OSS 00010000949 +622231380104710357220047060180000000000#017633201373# Sophia Martinez 1121042880000950 +705Beetlecrystal bonus pay for amazing work on #OSS 00010000950 +622231380104505746073705240160000000000#336003418620# Liam Williams 1121042880000951 +705Falconsilent bonus pay for amazing work on #OSS 00010000951 +622231380104680683502223547250000000000#100824445541# Addison Moore 1121042880000952 +705Eaterdeep bonus pay for amazing work on #OSS 00010000952 +622231380104038710503633536820000000000#717016066030# Chloe Martin 1121042880000953 +705Jackalflash bonus pay for amazing work on #OSS 00010000953 +622231380104478178612012726210000000000#754048071222# Avery Thompson 1121042880000954 +705Carpdent bonus pay for amazing work on #OSS 00010000954 +622231380104721583565407450820000000000#512410335228# Natalie Harris 1121042880000955 +705Bisonnotch bonus pay for amazing work on #OSS 00010000955 +622231380104426470632020845410000000000#060813628453# Madison Williams 1121042880000956 +705Legendwheat bonus pay for amazing work on #OSS 00010000956 +622231380104162153427055788170000000000#820857225481# Zoey Jones 1121042880000957 +705Razortyphoon bonus pay for amazing work on #OSS 00010000957 +622231380104868232318125472880000000000#203724700431# Mason Thomas 1121042880000958 +705Bunnyhorse bonus pay for amazing work on #OSS 00010000958 +622231380104130258437457001260000000000#517470157447# Jacob Jones 1121042880000959 +705Seedbuttercup bonus pay for amazing work on #OSS 00010000959 +622231380104534480754506412700000000000#450161674671# Zoey Jackson 1121042880000960 +705Lordglitter bonus pay for amazing work on #OSS 00010000960 +622231380104477280774805308340000000000#735375603364# Natalie Martinez 1121042880000961 +705Deerquill bonus pay for amazing work on #OSS 00010000961 +622231380104270277615738515050000000000#141857578570# Joseph Taylor 1121042880000962 +705Glassbrindle bonus pay for amazing work on #OSS 00010000962 +622231380104547613513468773860000000000#361730882467# Jayden Martinez 1121042880000963 +705Horncomet bonus pay for amazing work on #OSS 00010000963 +622231380104734764888660045060000000000#664716786035# Lily Williams 1121042880000964 +705Piratepower bonus pay for amazing work on #OSS 00010000964 +622231380104576854245168467620000000000#837448425337# Mason Taylor 1121042880000965 +705Jackalsticky bonus pay for amazing work on #OSS 00010000965 +622231380104871515002743380000000000000#073251365188# Mason Robinson 1121042880000966 +705Questertopaz bonus pay for amazing work on #OSS 00010000966 +622231380104087731747544058800000000000#655013608677# Jacob Davis 1121042880000967 +705Knightweed bonus pay for amazing work on #OSS 00010000967 +622231380104200314857022127700000000000#132758587451# Zoey Martin 1121042880000968 +705Grabberskitter bonus pay for amazing work on #OSS 00010000968 +622231380104604323746432613710000000000#336442022214# Daniel Robinson 1121042880000969 +705Wolverinespring bonus pay for amazing work on #OSS 00010000969 +622231380104700364157658736400000000000#162636001056# Ava Brown 1121042880000970 +705Kittenblaze bonus pay for amazing work on #OSS 00010000970 +622231380104728667407813132330000000000#034778134518# Natalie Thomas 1121042880000971 +705Deerpurple bonus pay for amazing work on #OSS 00010000971 +622231380104406867182563311730000000000#683864264222# Elijah Jones 1121042880000972 +705Forgerripple bonus pay for amazing work on #OSS 00010000972 +622231380104785268885163658380000000000#087883436403# Charlotte Harris 1121042880000973 +705Samuraiarrow bonus pay for amazing work on #OSS 00010000973 +622231380104425366266111475070000000000#508057401107# Joseph Harris 1121042880000974 +705Gemrogue bonus pay for amazing work on #OSS 00010000974 +622231380104223716303560381060000000000#364082527260# Mia Johnson 1121042880000975 +705Shouldersplit bonus pay for amazing work on #OSS 00010000975 +622231380104533160301302542450000000000#856750002668# Addison Brown 1121042880000976 +705Hisshurricane bonus pay for amazing work on #OSS 00010000976 +622231380104561867054702213670000000000#278810530166# Joshua Thomas 1121042880000977 +705Bugmangrove bonus pay for amazing work on #OSS 00010000977 +622231380104562246836106170150000000000#764020476310# Ava Williams 1121042880000978 +705Zebracypress bonus pay for amazing work on #OSS 00010000978 +622231380104284465154161883510000000000#627417021171# Matthew Johnson 1121042880000979 +705Coyotering bonus pay for amazing work on #OSS 00010000979 +622231380104068867346683264340000000000#632726632560# Isabella Moore 1121042880000980 +705Markrelic bonus pay for amazing work on #OSS 00010000980 +622231380104670236011185275450000000000#012244068402# Elizabeth Thomas 1121042880000981 +705Carvercord bonus pay for amazing work on #OSS 00010000981 +622231380104378482314677746820000000000#177786566664# Ethan Jackson 1121042880000982 +705Throatpickle bonus pay for amazing work on #OSS 00010000982 +622231380104556878544316518880000000000#547180630883# William Moore 1121042880000983 +705Mindblack bonus pay for amazing work on #OSS 00010000983 +622231380104512603872573170530000000000#188425326211# Liam Davis 1121042880000984 +705Dancersprout bonus pay for amazing work on #OSS 00010000984 +622231380104170662748452084550000000000#568203527545# Alexander Robinson 1121042880000985 +705Razordandy bonus pay for amazing work on #OSS 00010000985 +622231380104214451526581678250000000000#245052838420# Charlotte Davis 1121042880000986 +705Speakereast bonus pay for amazing work on #OSS 00010000986 +622231380104120036846280188740000000000#268676043762# Mia Brown 1121042880000987 +705Tradermire bonus pay for amazing work on #OSS 00010000987 +622231380104083574367416254660000000000#703521082330# Ethan Jones 1121042880000988 +705Walkergrass bonus pay for amazing work on #OSS 00010000988 +622231380104154306731288258000000000000#010443241354# Sophia Taylor 1121042880000989 +705Roarerspeckle bonus pay for amazing work on #OSS 00010000989 +622231380104788540162688883660000000000#602316837300# Ava Jackson 1121042880000990 +705Wingsticky bonus pay for amazing work on #OSS 00010000990 +622231380104508472111204362530000000000#765238586754# Andrew Taylor 1121042880000991 +705Wyrmseason bonus pay for amazing work on #OSS 00010000991 +622231380104352657548847202150000000000#866422434251# Aubrey Jones 1121042880000992 +705Bootforest bonus pay for amazing work on #OSS 00010000992 +622231380104566283111873246600000000000#843672228588# Emily Jackson 1121042880000993 +705Slavelapis bonus pay for amazing work on #OSS 00010000993 +622231380104045603033844852670000000000#154465218264# Jacob Thomas 1121042880000994 +705Carpalmond bonus pay for amazing work on #OSS 00010000994 +622231380104341311635782785400000000000#764355713354# Matthew Jones 1121042880000995 +705Riderbloom bonus pay for amazing work on #OSS 00010000995 +622231380104072688667724258340000000000#324064305832# Joseph Wilson 1121042880000996 +705Heronancient bonus pay for amazing work on #OSS 00010000996 +622231380104721848225456503430000000000#648755441577# Aiden Jackson 1121042880000997 +705Scowltyphoon bonus pay for amazing work on #OSS 00010000997 +622231380104445218705876105150000000000#571670301323# Andrew Thomas 1121042880000998 +705Fairymagenta bonus pay for amazing work on #OSS 00010000998 +622231380104564668851085732150000000000#274233727837# Andrew Wilson 1121042880000999 +705Soarerrhinestone bonus pay for amazing work on #OSS 00010000999 +622231380104717022558422710700000000000#634828343354# David Williams 1121042880001000 +705Hissdestiny bonus pay for amazing work on #OSS 00010001000 +622231380104778227211220453010000000000#707462260607# Lily Williams 1121042880001001 +705Racercarnation bonus pay for amazing work on #OSS 00010001001 +622231380104666611484782238220000000000#744651741812# Avery Thomas 1121042880001002 +705Snarltreasure bonus pay for amazing work on #OSS 00010001002 +622231380104663410243150583880000000000#181626563442# Zoey Garcia 1121042880001003 +705Knightevening bonus pay for amazing work on #OSS 00010001003 +622231380104135565051351208580000000000#711746376640# Jacob Martin 1121042880001004 +705Bonecrimson bonus pay for amazing work on #OSS 00010001004 +622231380104775421361241263170000000000#542310711635# Aubrey Thompson 1121042880001005 +705Eaterberyl bonus pay for amazing work on #OSS 00010001005 +622231380104640273781848557830000000000#160612872537# Zoey Wilson 1121042880001006 +705Queencat bonus pay for amazing work on #OSS 00010001006 +622231380104143267005571071710000000000#572143425724# Ethan Wilson 1121042880001007 +705Swisherdestiny bonus pay for amazing work on #OSS 00010001007 +622231380104010502781426455740000000000#530666543354# Elijah Jones 1121042880001008 +705Hunterswift bonus pay for amazing work on #OSS 00010001008 +622231380104673512888150583340000000000#007548037207# Benjamin Anderson 1121042880001009 +705Sightsulpher bonus pay for amazing work on #OSS 00010001009 +622231380104512080588135371160000000000#856141386322# Mason Smith 1121042880001010 +705Weasellaser bonus pay for amazing work on #OSS 00010001010 +622231380104387144772141214160000000000#777486714042# Natalie Anderson 1121042880001011 +705Zebrajust bonus pay for amazing work on #OSS 00010001011 +622231380104557377657615011760000000000#136530112282# Zoey Robinson 1121042880001012 +705Bisonveil bonus pay for amazing work on #OSS 00010001012 +622231380104158483761076165750000000000#612108830386# Addison Brown 1121042880001013 +705Racerroot bonus pay for amazing work on #OSS 00010001013 +622231380104564750675325356140000000000#887515551254# Emily Harris 1121042880001014 +705Jackalproud bonus pay for amazing work on #OSS 00010001014 +622231380104577160355801245160000000000#023831014846# Mason Thompson 1121042880001015 +705Bellymire bonus pay for amazing work on #OSS 00010001015 +622231380104520174436270366840000000000#187081678224# Olivia Robinson 1121042880001016 +705Snoutivory bonus pay for amazing work on #OSS 00010001016 +622231380104402404251522642050000000000#033221486113# Emily Jackson 1121042880001017 +705Princegossamer bonus pay for amazing work on #OSS 00010001017 +622231380104873567312185021640000000000#006486637877# Emily Martin 1121042880001018 +705Scartreasure bonus pay for amazing work on #OSS 00010001018 +622231380104383053088568767620000000000#757778574701# Noah Johnson 1121042880001019 +705Dancerpetal bonus pay for amazing work on #OSS 00010001019 +622231380104886730153033486280000000000#042363731784# Mason Johnson 1121042880001020 +705Burnfringe bonus pay for amazing work on #OSS 00010001020 +622231380104251881102050562870000000000#274172522730# Mason Williams 1121042880001021 +705Gemspeckle bonus pay for amazing work on #OSS 00010001021 +622231380104374288615170037050000000000#541754283530# Alexander Martin 1121042880001022 +705Falconsequoia bonus pay for amazing work on #OSS 00010001022 +622231380104245425357185238880000000000#488108841726# Michael Brown 1121042880001023 +705Playerleather bonus pay for amazing work on #OSS 00010001023 +622231380104424815804051024100000000000#444403058126# Joseph Thompson 1121042880001024 +705Beetleapricot bonus pay for amazing work on #OSS 00010001024 +622231380104045127127774547230000000000#561780436541# Sophia Moore 1121042880001025 +705Burnwood bonus pay for amazing work on #OSS 00010001025 +622231380104646840730154326800000000000#612360641018# Ella Harris 1121042880001026 +705Beerhinestone bonus pay for amazing work on #OSS 00010001026 +622231380104570286501082812210000000000#116822772410# Mia Thompson 1121042880001027 +705Sightholy bonus pay for amazing work on #OSS 00010001027 +622231380104657240316531540440000000000#177215076242# Ethan Johnson 1121042880001028 +705Waspflower bonus pay for amazing work on #OSS 00010001028 +622231380104731270408505512680000000000#543113172630# Michael Moore 1121042880001029 +705Toescarlet bonus pay for amazing work on #OSS 00010001029 +622231380104247883261642004860000000000#245373184426# Mia Brown 1121042880001030 +705Lionchecker bonus pay for amazing work on #OSS 00010001030 +622231380104052651284404081430000000000#813363531066# Charlotte Johnson 1121042880001031 +705Ducksand bonus pay for amazing work on #OSS 00010001031 +622231380104214662840157032070000000000#460775088363# Joseph Jackson 1121042880001032 +705Wizardclever bonus pay for amazing work on #OSS 00010001032 +622231380104666385384674578460000000000#426427655724# Mia Martin 1121042880001033 +705Cloakcitrine bonus pay for amazing work on #OSS 00010001033 +622231380104311781633423661470000000000#042455201500# Mia Jones 1121042880001034 +705Scribesavage bonus pay for amazing work on #OSS 00010001034 +622231380104738828081837071830000000000#226100830488# Jacob Martin 1121042880001035 +705Goatbrave bonus pay for amazing work on #OSS 00010001035 +622231380104744142875671655020000000000#071375226833# Emma Moore 1121042880001036 +705Monkeyroot bonus pay for amazing work on #OSS 00010001036 +622231380104488257873128311880000000000#533811035662# Noah Anderson 1121042880001037 +705Monkeydawn bonus pay for amazing work on #OSS 00010001037 +622231380104781581162816852330000000000#857564632212# Mia Moore 1121042880001038 +705Loondog bonus pay for amazing work on #OSS 00010001038 +622231380104300876474213343650000000000#556870571803# William Martinez 1121042880001039 +705Handapple bonus pay for amazing work on #OSS 00010001039 +622231380104426548070864780780000000000#555736520371# Anthony Anderson 1121042880001040 +705Lashertitanium bonus pay for amazing work on #OSS 00010001040 +622231380104162528542050573650000000000#204828826260# Lily Martin 1121042880001041 +705Parrotsheer bonus pay for amazing work on #OSS 00010001041 +622231380104385153036747177180000000000#142852161107# Avery Johnson 1121042880001042 +705Shriekglen bonus pay for amazing work on #OSS 00010001042 +622231380104883860862407555780000000000#503508612306# Addison Anderson 1121042880001043 +705Heronmire bonus pay for amazing work on #OSS 00010001043 +622231380104753775506436383010000000000#148530820858# Avery Thomas 1121042880001044 +705Kingchrome bonus pay for amazing work on #OSS 00010001044 +622231380104835861718434433160000000000#201315817670# Matthew Anderson 1121042880001045 +705Stealerhazel bonus pay for amazing work on #OSS 00010001045 +622231380104363024844734568670000000000#227848635370# Avery Thompson 1121042880001046 +705Warlockskitter bonus pay for amazing work on #OSS 00010001046 +622231380104187282863055256500000000000#553620222417# Jacob Thomas 1121042880001047 +705Lightningtwilight bonus pay for amazing work on #OSS 00010001047 +622231380104700564133637800870000000000#767480131618# Benjamin Harris 1121042880001048 +705Cowlred bonus pay for amazing work on #OSS 00010001048 +622231380104820086634460865650000000000#330557712247# Zoey Jackson 1121042880001049 +705Curtainpewter bonus pay for amazing work on #OSS 00010001049 +622231380104635135060220701350000000000#704081150185# Anthony Smith 1121042880001050 +705Thiefalmond bonus pay for amazing work on #OSS 00010001050 +622231380104656400605585170780000000000#256086102171# Natalie Garcia 1121042880001051 +705Slothswamp bonus pay for amazing work on #OSS 00010001051 +622231380104884326323134007430000000000#152447808287# Jayden Johnson 1121042880001052 +705Hideheavy bonus pay for amazing work on #OSS 00010001052 +622231380104144414885543830400000000000#263022523544# Elizabeth Martinez 1121042880001053 +705Kittentyphoon bonus pay for amazing work on #OSS 00010001053 +622231380104473852353074402410000000000#884565672421# Emma Garcia 1121042880001054 +705Dartsolstice bonus pay for amazing work on #OSS 00010001054 +622231380104421005622575366780000000000#457860657756# David Robinson 1121042880001055 +705Heronboulder bonus pay for amazing work on #OSS 00010001055 +622231380104532304321185137030000000000#336737687815# Lily Brown 1121042880001056 +705Flysponge bonus pay for amazing work on #OSS 00010001056 +622231380104638464382814182580000000000#084365562440# Jayden Brown 1121042880001057 +705Huntershine bonus pay for amazing work on #OSS 00010001057 +622231380104200822745862613830000000000#555511027154# Mia Williams 1121042880001058 +705Fanciernickel bonus pay for amazing work on #OSS 00010001058 +622231380104201663021218086000000000000#231263885223# Aiden Thomas 1121042880001059 +705Shirtbead bonus pay for amazing work on #OSS 00010001059 +622231380104233515358614403630000000000#314258428000# Elijah Taylor 1121042880001060 +705Tigerquartz bonus pay for amazing work on #OSS 00010001060 +622231380104740135455573575410000000000#722486574458# Anthony Anderson 1121042880001061 +705Leopardplum bonus pay for amazing work on #OSS 00010001061 +622231380104504024541543252720000000000#870535383027# Sofia Anderson 1121042880001062 +705Warlockholly bonus pay for amazing work on #OSS 00010001062 +622231380104283001012754447500000000000#105670052120# Alexander Miller 1121042880001063 +705Mastermetal bonus pay for amazing work on #OSS 00010001063 +622231380104022501250144040010000000000#404003763848# Lily Johnson 1121042880001064 +705Carpglass bonus pay for amazing work on #OSS 00010001064 +622231380104072427677224164520000000000#183480117655# Avery Thomas 1121042880001065 +705Ogrelizard bonus pay for amazing work on #OSS 00010001065 +622231380104413015541114240780000000000#675842710623# Jayden Johnson 1121042880001066 +705Whipspice bonus pay for amazing work on #OSS 00010001066 +622231380104505405373052672300000000000#421271230678# Ethan Harris 1121042880001067 +705Centaurbow bonus pay for amazing work on #OSS 00010001067 +622231380104503338826203541520000000000#880563420737# Matthew Thomas 1121042880001068 +705Neckfire bonus pay for amazing work on #OSS 00010001068 +622231380104248281880783356000000000000#748843343666# Joseph Jones 1121042880001069 +705Fisherrelic bonus pay for amazing work on #OSS 00010001069 +622231380104022571561001570560000000000#261173308828# Olivia Davis 1121042880001070 +705Cloakrowan bonus pay for amazing work on #OSS 00010001070 +622231380104542153337818223310000000000#502252703651# Ella Thompson 1121042880001071 +705Stonetwisty bonus pay for amazing work on #OSS 00010001071 +622231380104667212166277128420000000000#508740578862# Mia Thompson 1121042880001072 +705Roarperidot bonus pay for amazing work on #OSS 00010001072 +622231380104053843532123658330000000000#788847847580# Ethan Martin 1121042880001073 +705Dutchessmirror bonus pay for amazing work on #OSS 00010001073 +622231380104306778056631204450000000000#412317872634# James Moore 1121042880001074 +705Batjuniper bonus pay for amazing work on #OSS 00010001074 +622231380104681827402388160840000000000#026812466125# Elijah Miller 1121042880001075 +705Jackalglacier bonus pay for amazing work on #OSS 00010001075 +622231380104003387688667102310000000000#661421277844# Elizabeth Jones 1121042880001076 +705Ottervivid bonus pay for amazing work on #OSS 00010001076 +622231380104440241302368600360000000000#078514416653# William Garcia 1121042880001077 +705Spurazure bonus pay for amazing work on #OSS 00010001077 +622231380104126846673660027620000000000#857768374485# Ava Martinez 1121042880001078 +705Rayrhinestone bonus pay for amazing work on #OSS 00010001078 +622231380104666856073588772020000000000#202304220865# Aubrey Davis 1121042880001079 +705Sightsequoia bonus pay for amazing work on #OSS 00010001079 +622231380104517663313635633470000000000#542736346337# Michael Moore 1121042880001080 +705Watcherwhite bonus pay for amazing work on #OSS 00010001080 +622231380104553651173074280130000000000#425285406463# Emily Garcia 1121042880001081 +705Razorgiant bonus pay for amazing work on #OSS 00010001081 +622231380104308854756461148560000000000#728037513314# Madison Martinez 1121042880001082 +705Hyenadull bonus pay for amazing work on #OSS 00010001082 +622231380104416476137470713370000000000#212740364056# Olivia Williams 1121042880001083 +705Mythglory bonus pay for amazing work on #OSS 00010001083 +622231380104825302044163031780000000000#533382053278# Daniel Brown 1121042880001084 +705Grabberbutter bonus pay for amazing work on #OSS 00010001084 +622231380104140355850714585540000000000#312328441737# Zoey Moore 1121042880001085 +705Pixiesaber bonus pay for amazing work on #OSS 00010001085 +622231380104811003710367455530000000000#171716520788# Benjamin Garcia 1121042880001086 +705Serpentbig bonus pay for amazing work on #OSS 00010001086 +622231380104171824157087558720000000000#405604058077# Elijah Taylor 1121042880001087 +705Riderglow bonus pay for amazing work on #OSS 00010001087 +622231380104611140464584812580000000000#827686757521# Mia Anderson 1121042880001088 +705Healerfancy bonus pay for amazing work on #OSS 00010001088 +622231380104245451063248270300000000000#126214758563# Olivia Johnson 1121042880001089 +705Facebrown bonus pay for amazing work on #OSS 00010001089 +622231380104705701725826733300000000000#837530350012# Joshua Harris 1121042880001090 +705Hairmountain bonus pay for amazing work on #OSS 00010001090 +622231380104372780430601057630000000000#722282007101# Michael Thomas 1121042880001091 +705Sentrybird bonus pay for amazing work on #OSS 00010001091 +622231380104638026077323423080000000000#458041885246# Liam Thompson 1121042880001092 +705Cloudmeadow bonus pay for amazing work on #OSS 00010001092 +622231380104423842430371554750000000000#328822680125# Emma Martinez 1121042880001093 +705Beetleaquamarine bonus pay for amazing work on #OSS 00010001093 +622231380104775007868206485430000000000#267825751872# Liam Williams 1121042880001094 +705Swordcloud bonus pay for amazing work on #OSS 00010001094 +622231380104305464242242688330000000000#856852247604# Avery Brown 1121042880001095 +705Bardplume bonus pay for amazing work on #OSS 00010001095 +622231380104188078848778857340000000000#687387046818# Aiden Thompson 1121042880001096 +705Slothstar bonus pay for amazing work on #OSS 00010001096 +622231380104414455154868171810000000000#771520056712# Mia Moore 1121042880001097 +705Swoopebony bonus pay for amazing work on #OSS 00010001097 +622231380104225436203854218320000000000#175522722310# James Johnson 1121042880001098 +705Waspviolet bonus pay for amazing work on #OSS 00010001098 +622231380104803280005866450360000000000#130611153648# Natalie Moore 1121042880001099 +705Chillnoble bonus pay for amazing work on #OSS 00010001099 +622231380104710638126320822830000000000#044838834530# Isabella White 1121042880001100 +705Legendhate bonus pay for amazing work on #OSS 00010001100 +622231380104165734215071127700000000000#744384304015# Noah Jones 1121042880001101 +705Flyhorse bonus pay for amazing work on #OSS 00010001101 +622231380104722735554324803780000000000#008623047145# Emily Brown 1121042880001102 +705Beakwarp bonus pay for amazing work on #OSS 00010001102 +622231380104616852018350882860000000000#323567445841# Sophia Brown 1121042880001103 +705Weaverzinc bonus pay for amazing work on #OSS 00010001103 +622231380104630823122621214410000000000#631530175183# Aubrey Brown 1121042880001104 +705Riderbubble bonus pay for amazing work on #OSS 00010001104 +622231380104056276666412165570000000000#638804138607# Matthew Thomas 1121042880001105 +705Snarlstorm bonus pay for amazing work on #OSS 00010001105 +622231380104210535025572582300000000000#305755240368# Natalie Garcia 1121042880001106 +705Givermire bonus pay for amazing work on #OSS 00010001106 +622231380104331084374171828040000000000#013182243582# Elizabeth Harris 1121042880001107 +705Frightlime bonus pay for amazing work on #OSS 00010001107 +622231380104670612517337636720000000000#838862363237# Sofia Anderson 1121042880001108 +705Kangaroocharm bonus pay for amazing work on #OSS 00010001108 +622231380104404527706545338530000000000#224658356470# Elijah Anderson 1121042880001109 +705Cougarberry bonus pay for amazing work on #OSS 00010001109 +622231380104713251744356456500000000000#438511632153# Elizabeth Anderson 1121042880001110 +705Speakerchain bonus pay for amazing work on #OSS 00010001110 +622231380104756030070078586000000000000#873238348307# Sophia Robinson 1121042880001111 +705Lynxlapis bonus pay for amazing work on #OSS 00010001111 +622231380104716024712087518030000000000#874434251087# Addison Moore 1121042880001112 +705Hawkfrill bonus pay for amazing work on #OSS 00010001112 +622231380104260223760413525200000000000#601848281348# Addison Anderson 1121042880001113 +705Bisonskitter bonus pay for amazing work on #OSS 00010001113 +622231380104714572746656506850000000000#324314048024# Olivia Martinez 1121042880001114 +705Volequasar bonus pay for amazing work on #OSS 00010001114 +622231380104641420403048555440000000000#758235815368# Ava Davis 1121042880001115 +705Wanderercerulean bonus pay for amazing work on #OSS 00010001115 +622231380104701481727631846510000000000#158520825243# Mason Moore 1121042880001116 +705Followermalachite bonus pay for amazing work on #OSS 00010001116 +622231380104440411322768406620000000000#725148226737# Avery Davis 1121042880001117 +705Condorlight bonus pay for amazing work on #OSS 00010001117 +622231380104105165034441823750000000000#181710117527# Jayden Jones 1121042880001118 +705Herotrail bonus pay for amazing work on #OSS 00010001118 +622231380104352828456600846380000000000#475051608473# Elijah White 1121042880001119 +705Jaguarplatinum bonus pay for amazing work on #OSS 00010001119 +622231380104027534156740434410000000000#324803623766# Jacob Miller 1121042880001120 +705Bowclever bonus pay for amazing work on #OSS 00010001120 +622231380104487507101315316480000000000#041327738332# Olivia Jones 1121042880001121 +705Giverbead bonus pay for amazing work on #OSS 00010001121 +622231380104300251620008018400000000000#717864572515# Lily Thompson 1121042880001122 +705Toucancliff bonus pay for amazing work on #OSS 00010001122 +622231380104042484024032670030000000000#430843183541# Sofia Jackson 1121042880001123 +705Shoulderlinen bonus pay for amazing work on #OSS 00010001123 +622231380104841400576854215100000000000#406350417353# Noah Moore 1121042880001124 +705Walkerfish bonus pay for amazing work on #OSS 00010001124 +622231380104665147856184012280000000000#368063223823# Zoey Moore 1121042880001125 +705Chanterapple bonus pay for amazing work on #OSS 00010001125 +622231380104321482032114611680000000000#452720623611# Isabella Thompson 1121042880001126 +705Swallowrazor bonus pay for amazing work on #OSS 00010001126 +622231380104441853563143526620000000000#330654848161# Elizabeth Johnson 1121042880001127 +705Healerbevel bonus pay for amazing work on #OSS 00010001127 +622231380104528814780067012040000000000#610700553751# Elijah Miller 1121042880001128 +705Gazelleisland bonus pay for amazing work on #OSS 00010001128 +622231380104651338755133246760000000000#518723151161# Ella Martin 1121042880001129 +705Servantlunar bonus pay for amazing work on #OSS 00010001129 +622231380104572601141104811850000000000#432763286228# Michael Brown 1121042880001130 +705Mothrogue bonus pay for amazing work on #OSS 00010001130 +622231380104104477001767861500000000000#600785338102# David White 1121042880001131 +705Condoragate bonus pay for amazing work on #OSS 00010001131 +622231380104844617816370833340000000000#331715315538# Andrew Williams 1121042880001132 +705Crystalbrook bonus pay for amazing work on #OSS 00010001132 +622231380104351762602647421430000000000#315225522664# Mason Brown 1121042880001133 +705Shieldseed bonus pay for amazing work on #OSS 00010001133 +622231380104805360673044483700000000000#211227550541# Noah Taylor 1121042880001134 +705Crestquill bonus pay for amazing work on #OSS 00010001134 +622231380104034031205230677650000000000#011710656041# Ella Jackson 1121042880001135 +705Sentryshine bonus pay for amazing work on #OSS 00010001135 +622231380104661877544518816260000000000#472143874460# Aubrey Jones 1121042880001136 +705Gemribbon bonus pay for amazing work on #OSS 00010001136 +622231380104264742512204616600000000000#087286107435# Michael Brown 1121042880001137 +705Princemangrove bonus pay for amazing work on #OSS 00010001137 +622231380104575545062803232820000000000#528622142187# Ella Garcia 1121042880001138 +705Chestlapis bonus pay for amazing work on #OSS 00010001138 +622231380104752868062626106070000000000#615328587638# Zoey Williams 1121042880001139 +705Backagate bonus pay for amazing work on #OSS 00010001139 +622231380104824241101183333870000000000#027880251404# Jacob Garcia 1121042880001140 +705Twisterseed bonus pay for amazing work on #OSS 00010001140 +622231380104267324830232182660000000000#012242816008# David Harris 1121042880001141 +705Frightsavage bonus pay for amazing work on #OSS 00010001141 +622231380104563730223238555180000000000#473482216627# Abigail Davis 1121042880001142 +705Soarerember bonus pay for amazing work on #OSS 00010001142 +622231380104660224860147383000000000000#570155528327# Jayden Wilson 1121042880001143 +705Thornrose bonus pay for amazing work on #OSS 00010001143 +622231380104308162488656456140000000000#243330148343# Ella Robinson 1121042880001144 +705Unicornapple bonus pay for amazing work on #OSS 00010001144 +622231380104876255380014221630000000000#522635745783# Joshua Miller 1121042880001145 +705Diverainbow bonus pay for amazing work on #OSS 00010001145 +622231380104875741577346121260000000000#024785540633# Michael Jackson 1121042880001146 +705Flierpyrite bonus pay for amazing work on #OSS 00010001146 +622231380104263516141737428220000000000#650011636118# Ethan Davis 1121042880001147 +705Razorleather bonus pay for amazing work on #OSS 00010001147 +622231380104475254570646014030000000000#104260548154# Elizabeth Johnson 1121042880001148 +705Butterflyberry bonus pay for amazing work on #OSS 00010001148 +622231380104361141474666553480000000000#078755802557# Avery White 1121042880001149 +705Talonflicker bonus pay for amazing work on #OSS 00010001149 +622231380104266755307202284510000000000#352483042470# Matthew Miller 1121042880001150 +705Scourgechecker bonus pay for amazing work on #OSS 00010001150 +622231380104753106385473362020000000000#526758018600# Natalie Smith 1121042880001151 +705Burnmisty bonus pay for amazing work on #OSS 00010001151 +622231380104158118680880606270000000000#547066083803# Ethan Williams 1121042880001152 +705Flyseed bonus pay for amazing work on #OSS 00010001152 +622231380104483087531767570270000000000#253632862446# Ethan Martinez 1121042880001153 +705Waspband bonus pay for amazing work on #OSS 00010001153 +622231380104432204453038388000000000000#434823067847# Liam Martin 1121042880001154 +705Mothcrazy bonus pay for amazing work on #OSS 00010001154 +622231380104703350688384386320000000000#864562613030# Jayden Anderson 1121042880001155 +705Fishvenom bonus pay for amazing work on #OSS 00010001155 +622231380104042417730202676570000000000#377387515307# Sophia Wilson 1121042880001156 +705Hornquark bonus pay for amazing work on #OSS 00010001156 +622231380104371205648144105020000000000#764511441271# Elijah Thomas 1121042880001157 +705Forgerfog bonus pay for amazing work on #OSS 00010001157 +622231380104017068824506345570000000000#831563731215# Madison Brown 1121042880001158 +705Heronspice bonus pay for amazing work on #OSS 00010001158 +622231380104045237327375261110000000000#720766682445# Addison Davis 1121042880001159 +705Skinnerthunder bonus pay for amazing work on #OSS 00010001159 +622231380104670110671015478560000000000#400725101182# Joshua Martin 1121042880001160 +705Volefluff bonus pay for amazing work on #OSS 00010001160 +622231380104232840878168825710000000000#414087025350# William Davis 1121042880001161 +705Stealerspectrum bonus pay for amazing work on #OSS 00010001161 +622231380104633220463343232440000000000#405274146637# Elizabeth Martin 1121042880001162 +705Gemtitanium bonus pay for amazing work on #OSS 00010001162 +622231380104476557206372348710000000000#766252648301# Anthony Williams 1121042880001163 +705Monkeyphase bonus pay for amazing work on #OSS 00010001163 +622231380104366547177758540460000000000#883648348017# Anthony Martin 1121042880001164 +705Crafterwhite bonus pay for amazing work on #OSS 00010001164 +622231380104553863657753667620000000000#022512685115# Joseph Garcia 1121042880001165 +705Followercold bonus pay for amazing work on #OSS 00010001165 +622231380104723175720475773650000000000#850337002708# Jayden Thomas 1121042880001166 +705Herorelic bonus pay for amazing work on #OSS 00010001166 +622231380104723441211546146800000000000#816433727026# William Martin 1121042880001167 +705Seedlizard bonus pay for amazing work on #OSS 00010001167 +622231380104304344266352800550000000000#208440816828# Matthew Brown 1121042880001168 +705Bowclever bonus pay for amazing work on #OSS 00010001168 +622231380104761184601125233120000000000#141474830606# Lily Thompson 1121042880001169 +705Fishmercury bonus pay for amazing work on #OSS 00010001169 +622231380104373057176626704180000000000#743735657155# William Taylor 1121042880001170 +705Beakwheat bonus pay for amazing work on #OSS 00010001170 +622231380104048220615405388830000000000#152365630207# Joshua White 1121042880001171 +705Frillnorth bonus pay for amazing work on #OSS 00010001171 +622231380104322115717116416450000000000#880030467356# Noah Jackson 1121042880001172 +705Tonguelie bonus pay for amazing work on #OSS 00010001172 +622231380104411018760806527260000000000#863365861240# Ella Taylor 1121042880001173 +705Boarsharp bonus pay for amazing work on #OSS 00010001173 +622231380104136020613644380360000000000#458441244386# Avery Williams 1121042880001174 +705Divedenim bonus pay for amazing work on #OSS 00010001174 +622231380104608450035188843710000000000#217473316645# Natalie Taylor 1121042880001175 +705Antlerseason bonus pay for amazing work on #OSS 00010001175 +622231380104277172157736343800000000000#260162518633# James Anderson 1121042880001176 +705Grinsalt bonus pay for amazing work on #OSS 00010001176 +622231380104080675730104873650000000000#231467043577# Isabella Davis 1121042880001177 +705Oriolefierce bonus pay for amazing work on #OSS 00010001177 +622231380104172213576743775380000000000#285812257677# Benjamin Davis 1121042880001178 +705Singerfast bonus pay for amazing work on #OSS 00010001178 +622231380104751580718380451740000000000#635518062452# James Garcia 1121042880001179 +705Bitermuck bonus pay for amazing work on #OSS 00010001179 +622231380104267012321765258610000000000#164706725340# Daniel Davis 1121042880001180 +705Heronswamp bonus pay for amazing work on #OSS 00010001180 +622231380104061703422262034770000000000#083177856115# Aiden Jackson 1121042880001181 +705Lanternpatch bonus pay for amazing work on #OSS 00010001181 +622231380104432264081727810080000000000#061202717602# Avery White 1121042880001182 +705Stormebony bonus pay for amazing work on #OSS 00010001182 +622231380104162702417003341210000000000#568812323175# Natalie Smith 1121042880001183 +705Warlockbrown bonus pay for amazing work on #OSS 00010001183 +622231380104132188111487872030000000000#156437068453# Emily White 1121042880001184 +705Crowglass bonus pay for amazing work on #OSS 00010001184 +622231380104283053523283111510000000000#681827475378# Ella Jones 1121042880001185 +705Headcrack bonus pay for amazing work on #OSS 00010001185 +622231380104631608551874463280000000000#878878613066# Lily Jones 1121042880001186 +705Eyehoneysuckle bonus pay for amazing work on #OSS 00010001186 +622231380104533587422761346710000000000#056640665672# James Brown 1121042880001187 +705Thornquill bonus pay for amazing work on #OSS 00010001187 +622231380104585710235272085850000000000#270765076040# Charlotte Johnson 1121042880001188 +705Carvergreen bonus pay for amazing work on #OSS 00010001188 +622231380104243526150842532220000000000#240485615768# Anthony Miller 1121042880001189 +705Legmeadow bonus pay for amazing work on #OSS 00010001189 +622231380104755737356026463800000000000#304247842150# Olivia Brown 1121042880001190 +705Bugboom bonus pay for amazing work on #OSS 00010001190 +622231380104055371806805808160000000000#233127836457# Jacob Miller 1121042880001191 +705Stoneflicker bonus pay for amazing work on #OSS 00010001191 +622231380104744706101802704330000000000#048301634117# Isabella Anderson 1121042880001192 +705Gorillagiant bonus pay for amazing work on #OSS 00010001192 +622231380104633446804571730070000000000#448066642812# Anthony Robinson 1121042880001193 +705Weaverisland bonus pay for amazing work on #OSS 00010001193 +622231380104641113156141224130000000000#021033105605# Emily Brown 1121042880001194 +705Shirtebony bonus pay for amazing work on #OSS 00010001194 +622231380104850165763011845240000000000#282741160551# Michael Johnson 1121042880001195 +705Weedarrow bonus pay for amazing work on #OSS 00010001195 +622231380104833133372068862270000000000#430324580782# Daniel Taylor 1121042880001196 +705Zebrawest bonus pay for amazing work on #OSS 00010001196 +622231380104361650247701786560000000000#228705432246# Sophia Thompson 1121042880001197 +705Skulltin bonus pay for amazing work on #OSS 00010001197 +622231380104352781121071055840000000000#137411552578# Matthew Martinez 1121042880001198 +705Lynxbrass bonus pay for amazing work on #OSS 00010001198 +622231380104652136084200667200000000000#417811068208# Lily Martinez 1121042880001199 +705Butterflylime bonus pay for amazing work on #OSS 00010001199 +622231380104557178248138564850000000000#465567323020# Emma Brown 1121042880001200 +705Carverdune bonus pay for amazing work on #OSS 00010001200 +622231380104760552033077505070000000000#315155865674# Aubrey Jones 1121042880001201 +705Chinwax bonus pay for amazing work on #OSS 00010001201 +622231380104471278511065374810000000000#440164614538# Matthew White 1121042880001202 +705Songquill bonus pay for amazing work on #OSS 00010001202 +622231380104284807045512786480000000000#012537274310# Ella Johnson 1121042880001203 +705Kickerwhip bonus pay for amazing work on #OSS 00010001203 +622231380104000566275604605700000000000#562871548504# Benjamin Garcia 1121042880001204 +705Giverpickle bonus pay for amazing work on #OSS 00010001204 +622231380104401164828464771550000000000#115506343753# Mia Martin 1121042880001205 +705Spritenight bonus pay for amazing work on #OSS 00010001205 +622231380104650758846054082540000000000#073637862751# Daniel Wilson 1121042880001206 +705Swallowchip bonus pay for amazing work on #OSS 00010001206 +622231380104010704476172875320000000000#145787253240# Sophia Jackson 1121042880001207 +705Gargoylestone bonus pay for amazing work on #OSS 00010001207 +622231380104046205214473147740000000000#408043818618# Andrew Robinson 1121042880001208 +705Forgerlavender bonus pay for amazing work on #OSS 00010001208 +622231380104400864120848204470000000000#876074024374# Emily Smith 1121042880001209 +705Seedbog bonus pay for amazing work on #OSS 00010001209 +622231380104314703128702311570000000000#503248764570# Jacob Martinez 1121042880001210 +705Terriercomet bonus pay for amazing work on #OSS 00010001210 +622231380104611217631565834540000000000#415321414806# Abigail Harris 1121042880001211 +705Sharkvenom bonus pay for amazing work on #OSS 00010001211 +622231380104384008652700057630000000000#808675220471# Andrew Harris 1121042880001212 +705Cowlclever bonus pay for amazing work on #OSS 00010001212 +622231380104861336778655353400000000000#845446680688# Alexander White 1121042880001213 +705Facepeppermint bonus pay for amazing work on #OSS 00010001213 +622231380104473177475030241070000000000#542048401852# Natalie Miller 1121042880001214 +705Raymeadow bonus pay for amazing work on #OSS 00010001214 +622231380104324102721153311780000000000#405010158201# Joshua Garcia 1121042880001215 +705Salmonpickle bonus pay for amazing work on #OSS 00010001215 +622231380104322766806623803850000000000#013672074737# David Jones 1121042880001216 +705Bunnycypress bonus pay for amazing work on #OSS 00010001216 +622231380104271834133801333100000000000#087312123704# Sophia Davis 1121042880001217 +705Gazelleheather bonus pay for amazing work on #OSS 00010001217 +622231380104758403301603054840000000000#480455524837# Alexander Robinson 1121042880001218 +705Pythonphantom bonus pay for amazing work on #OSS 00010001218 +622231380104203206557004785000000000000#408247225182# Lily Miller 1121042880001219 +705Eyegold bonus pay for amazing work on #OSS 00010001219 +622231380104415426270307805160000000000#532670786664# David Garcia 1121042880001220 +705Pegasusiris bonus pay for amazing work on #OSS 00010001220 +622231380104830250854235358120000000000#686433016581# Mia Davis 1121042880001221 +705Antsalt bonus pay for amazing work on #OSS 00010001221 +622231380104451072311367262150000000000#656204387171# Joshua Smith 1121042880001222 +705Gullbald bonus pay for amazing work on #OSS 00010001222 +622231380104468858440564835560000000000#014754006138# Anthony Garcia 1121042880001223 +705Cloakstorm bonus pay for amazing work on #OSS 00010001223 +622231380104215667486077407840000000000#622474223867# Olivia Johnson 1121042880001224 +705Touchcurse bonus pay for amazing work on #OSS 00010001224 +622231380104424417283404536860000000000#066704803511# Abigail Martin 1121042880001225 +705Swisherboom bonus pay for amazing work on #OSS 00010001225 +622231380104350742267371254410000000000#324483706131# Andrew Williams 1121042880001226 +705Mustangcrystal bonus pay for amazing work on #OSS 00010001226 +622231380104303862288170440770000000000#120727563668# Sophia Taylor 1121042880001227 +705Sargenttar bonus pay for amazing work on #OSS 00010001227 +622231380104828466425421068680000000000#223170353535# Addison Williams 1121042880001228 +705Sharkflash bonus pay for amazing work on #OSS 00010001228 +622231380104778644145415117250000000000#122760386453# David Harris 1121042880001229 +705Spikekeen bonus pay for amazing work on #OSS 00010001229 +622231380104613754512748307770000000000#368664432123# Charlotte Jones 1121042880001230 +705Cloakrose bonus pay for amazing work on #OSS 00010001230 +622231380104486378514471322500000000000#775771812216# Noah Davis 1121042880001231 +705Buffalophase bonus pay for amazing work on #OSS 00010001231 +622231380104135363181434867810000000000#131110873267# Jacob Miller 1121042880001232 +705Playerpuzzle bonus pay for amazing work on #OSS 00010001232 +622231380104565077487846005370000000000#705684302102# Noah Robinson 1121042880001233 +705Herohoneysuckle bonus pay for amazing work on #OSS 00010001233 +622231380104033688245756406000000000000#238180143462# William Taylor 1121042880001234 +705Traderspring bonus pay for amazing work on #OSS 00010001234 +622231380104205240027444752100000000000#555856005303# Joseph Moore 1121042880001235 +705Eaterobsidian bonus pay for amazing work on #OSS 00010001235 +622231380104143351410785133640000000000#606751771667# James Brown 1121042880001236 +705Howlertin bonus pay for amazing work on #OSS 00010001236 +622231380104654151883672740650000000000#107283626411# Michael Taylor 1121042880001237 +705Marevanilla bonus pay for amazing work on #OSS 00010001237 +622231380104571013243758780480000000000#162342281362# Sofia Martinez 1121042880001238 +705Thumbpattern bonus pay for amazing work on #OSS 00010001238 +622231380104728230302571680120000000000#702340180617# Avery Martinez 1121042880001239 +705Carpblue bonus pay for amazing work on #OSS 00010001239 +622231380104834656217455747070000000000#868587672753# Addison Thomas 1121042880001240 +705Chillerluminous bonus pay for amazing work on #OSS 00010001240 +622231380104236675710254175080000000000#650287327880# Emily Garcia 1121042880001241 +705Palmdandy bonus pay for amazing work on #OSS 00010001241 +622231380104808177087305466650000000000#533738115715# Madison Brown 1121042880001242 +705Apeglaze bonus pay for amazing work on #OSS 00010001242 +622231380104215328288747848420000000000#613328041355# Lily Anderson 1121042880001243 +705Voiceginger bonus pay for amazing work on #OSS 00010001243 +622231380104653536114662485120000000000#217865822446# Madison Moore 1121042880001244 +705Lanterncookie bonus pay for amazing work on #OSS 00010001244 +622231380104112133516768117060000000000#533505155375# Joseph Davis 1121042880001245 +705Tonguecoral bonus pay for amazing work on #OSS 00010001245 +622231380104002627036757130340000000000#312147281430# Ava Moore 1121042880001246 +705Mustangwhite bonus pay for amazing work on #OSS 00010001246 +622231380104480341807811577740000000000#660652216732# Mia Thompson 1121042880001247 +705Stingriver bonus pay for amazing work on #OSS 00010001247 +622231380104646060806187507320000000000#445858805676# Aiden Harris 1121042880001248 +705Legpattern bonus pay for amazing work on #OSS 00010001248 +622231380104304672130587700060000000000#858177566308# Andrew Moore 1121042880001249 +705Carpgrove bonus pay for amazing work on #OSS 00010001249 +622231380104484528533622808270000000000#501570024201# James Williams 1121042880001250 +705Wolffish bonus pay for amazing work on #OSS 00010001250 +82000025008922512500000000000000000000000000121042882 121042880000001 +5200Wells Fargo 121042882 PPDTrans. Des 200118 1121042880000002 +622231380104238318528040886770000000000#647788123628# Elijah Martinez 1121042880000001 +705Buffalooil bonus pay for amazing work on #OSS 00010000001 +622231380104128667205784642050000000000#801665367624# Daniel Wilson 1121042880000002 +705Mousefir bonus pay for amazing work on #OSS 00010000002 +622231380104813233656602070350000000000#103437816126# Elijah Taylor 1121042880000003 +705Tailhelix bonus pay for amazing work on #OSS 00010000003 +622231380104236605483168678370000000000#045231211467# Jacob Anderson 1121042880000004 +705Frightdiamond bonus pay for amazing work on #OSS 00010000004 +622231380104258612355725067340000000000#570615372512# Elizabeth Wilson 1121042880000005 +705Swordmarble bonus pay for amazing work on #OSS 00010000005 +622231380104342157884688723370000000000#154783846802# Noah Davis 1121042880000006 +705Buffaloflash bonus pay for amazing work on #OSS 00010000006 +622231380104777763440786130710000000000#613461661234# Ava Davis 1121042880000007 +705Thiefstellar bonus pay for amazing work on #OSS 00010000007 +622231380104758487445736457380000000000#833083027385# Daniel Johnson 1121042880000008 +705Pixieapple bonus pay for amazing work on #OSS 00010000008 +622231380104464446018545417670000000000#286161482161# Ella Jackson 1121042880000009 +705Salmonmud bonus pay for amazing work on #OSS 00010000009 +622231380104776814152007080040000000000#837251855553# Jayden Martinez 1121042880000010 +705Crownzest bonus pay for amazing work on #OSS 00010000010 +622231380104763864666036475060000000000#656107304777# James Robinson 1121042880000011 +705Racerglaze bonus pay for amazing work on #OSS 00010000011 +622231380104532413124401344560000000000#810661878052# Emily Williams 1121042880000012 +705Spikemarble bonus pay for amazing work on #OSS 00010000012 +622231380104258743868773362000000000000#251623823376# Mason Martin 1121042880000013 +705Ravenstripe bonus pay for amazing work on #OSS 00010000013 +622231380104244483652162430330000000000#716308752688# Emma Davis 1121042880000014 +705Shakerpine bonus pay for amazing work on #OSS 00010000014 +622231380104508138110877556120000000000#554331246678# Ethan Martinez 1121042880000015 +705Gemfan bonus pay for amazing work on #OSS 00010000015 +622231380104607530771174226620000000000#200888020728# Olivia Davis 1121042880000016 +705Mothplume bonus pay for amazing work on #OSS 00010000016 +622231380104530416500434015210000000000#661361446317# Alexander Martinez 1121042880000017 +705Bitestellar bonus pay for amazing work on #OSS 00010000017 +622231380104010341151511856220000000000#651000568402# Benjamin Jackson 1121042880000018 +705Skullbloom bonus pay for amazing work on #OSS 00010000018 +622231380104672262338015741640000000000#762603677716# Benjamin Robinson 1121042880000019 +705Jawglory bonus pay for amazing work on #OSS 00010000019 +622231380104746642106810336640000000000#150831222167# Alexander Thompson 1121042880000020 +705Curtainmalachite bonus pay for amazing work on #OSS 00010000020 +622231380104328755371088675750000000000#544184171654# Elizabeth Davis 1121042880000021 +705Ladycactus bonus pay for amazing work on #OSS 00010000021 +622231380104470021766554482070000000000#626450111028# Aiden Thomas 1121042880000022 +705Banesilver bonus pay for amazing work on #OSS 00010000022 +622231380104606411228668565030000000000#171067785401# Elizabeth Martin 1121042880000023 +705Whalehorse bonus pay for amazing work on #OSS 00010000023 +622231380104873035042050734870000000000#552080370016# Alexander Anderson 1121042880000024 +705Markcoffee bonus pay for amazing work on #OSS 00010000024 +622231380104843542813766422430000000000#406138288682# Lily Martin 1121042880000025 +705Taloneast bonus pay for amazing work on #OSS 00010000025 +622231380104012640114647680670000000000#078727362724# Olivia Taylor 1121042880000026 +705Scowldandelion bonus pay for amazing work on #OSS 00010000026 +622231380104238876866173182480000000000#651177101675# Sophia Moore 1121042880000027 +705Sparrowshimmer bonus pay for amazing work on #OSS 00010000027 +622231380104012546043377467460000000000#136042571410# Mia Anderson 1121042880000028 +705Scartrail bonus pay for amazing work on #OSS 00010000028 +622231380104160547670855518550000000000#608184687581# Liam Johnson 1121042880000029 +705Savershard bonus pay for amazing work on #OSS 00010000029 +622231380104242482877743142140000000000#345248743747# Jacob Martin 1121042880000030 +705Deathchip bonus pay for amazing work on #OSS 00010000030 +622231380104211647468027442550000000000#405608355216# Natalie White 1121042880000031 +705Hornetglory bonus pay for amazing work on #OSS 00010000031 +622231380104868678001780440230000000000#541165125140# Aubrey Brown 1121042880000032 +705Throatobsidian bonus pay for amazing work on #OSS 00010000032 +622231380104286346471173616270000000000#100473715115# Ethan Johnson 1121042880000033 +705Rayabalone bonus pay for amazing work on #OSS 00010000033 +622231380104428136700514634120000000000#544500568741# Daniel Williams 1121042880000034 +705Dogsequoia bonus pay for amazing work on #OSS 00010000034 +622231380104372071442828878840000000000#063307645556# Joshua Davis 1121042880000035 +705Traderproud bonus pay for amazing work on #OSS 00010000035 +622231380104613204887756527650000000000#742257464483# Abigail Garcia 1121042880000036 +705Piratewax bonus pay for amazing work on #OSS 00010000036 +622231380104575375011564053100000000000#424014488860# Andrew Moore 1121042880000037 +705Ogresnow bonus pay for amazing work on #OSS 00010000037 +622231380104771264682177675040000000000#512351182755# Isabella Miller 1121042880000038 +705Daggerstream bonus pay for amazing work on #OSS 00010000038 +622231380104744822208656366050000000000#786663887501# Joshua Johnson 1121042880000039 +705Maskstellar bonus pay for amazing work on #OSS 00010000039 +622231380104165171181401335630000000000#740115603508# Ethan Jackson 1121042880000040 +705Cubbrindle bonus pay for amazing work on #OSS 00010000040 +622231380104555165762446148640000000000#207256448170# Joshua Moore 1121042880000041 +705Ravercord bonus pay for amazing work on #OSS 00010000041 +622231380104121321275673058760000000000#648751416011# Charlotte Moore 1121042880000042 +705Bowwind bonus pay for amazing work on #OSS 00010000042 +622231380104051477864317883810000000000#676121408515# Sophia White 1121042880000043 +705Piratefree bonus pay for amazing work on #OSS 00010000043 +622231380104344037284442332440000000000#551838521741# Lily Thomas 1121042880000044 +705Crownproud bonus pay for amazing work on #OSS 00010000044 +622231380104603864280348566110000000000#850586836056# Ethan Jackson 1121042880000045 +705Goosenoon bonus pay for amazing work on #OSS 00010000045 +622231380104816231503262455770000000000#826781820514# Joshua Thompson 1121042880000046 +705Roarhate bonus pay for amazing work on #OSS 00010000046 +622231380104332457583082607330000000000#535085517323# Natalie Thomas 1121042880000047 +705Tigerbrave bonus pay for amazing work on #OSS 00010000047 +622231380104461787707365374650000000000#276360462248# Noah Jackson 1121042880000048 +705Swallowgrove bonus pay for amazing work on #OSS 00010000048 +622231380104535317272720283320000000000#705516831116# Mia Thompson 1121042880000049 +705Pixieiris bonus pay for amazing work on #OSS 00010000049 +622231380104325004224611057370000000000#763187172315# Michael Thomas 1121042880000050 +705Chantersand bonus pay for amazing work on #OSS 00010000050 +622231380104463333141102844330000000000#204335888886# Ella Johnson 1121042880000051 +705Foxshag bonus pay for amazing work on #OSS 00010000051 +622231380104565053344768842170000000000#811317487800# Sophia Thomas 1121042880000052 +705Stalkerdot bonus pay for amazing work on #OSS 00010000052 +622231380104375033545522444660000000000#708832303308# Charlotte Anderson 1121042880000053 +705Kittenbloom bonus pay for amazing work on #OSS 00010000053 +622231380104453582075806047600000000000#573541738416# Matthew Brown 1121042880000054 +705Lynxtorch bonus pay for amazing work on #OSS 00010000054 +622231380104883628322012275120000000000#525832440217# Michael Brown 1121042880000055 +705Ratclover bonus pay for amazing work on #OSS 00010000055 +622231380104753518342230404640000000000#433733607468# Ella Jones 1121042880000056 +705Minnowweed bonus pay for amazing work on #OSS 00010000056 +622231380104538640703023152510000000000#518172145564# Natalie Davis 1121042880000057 +705Bowgrove bonus pay for amazing work on #OSS 00010000057 +622231380104767350102172423050000000000#388038488240# Sophia Martinez 1121042880000058 +705Sightvaliant bonus pay for amazing work on #OSS 00010000058 +622231380104334405323421377440000000000#700232470520# Michael Jackson 1121042880000059 +705Parrotgravel bonus pay for amazing work on #OSS 00010000059 +622231380104050360450530206070000000000#778185057457# Avery Davis 1121042880000060 +705Boarjade bonus pay for amazing work on #OSS 00010000060 +622231380104203683304135506570000000000#465704875672# Chloe Davis 1121042880000061 +705Razorsilk bonus pay for amazing work on #OSS 00010000061 +622231380104831367325744721330000000000#317688047027# David Thomas 1121042880000062 +705Scarergrave bonus pay for amazing work on #OSS 00010000062 +622231380104166637116078357580000000000#737153645547# Daniel Robinson 1121042880000063 +705Elkpale bonus pay for amazing work on #OSS 00010000063 +622231380104287188301825611780000000000#745038326502# Sophia Thomas 1121042880000064 +705Hornetbutton bonus pay for amazing work on #OSS 00010000064 +622231380104267000080363580220000000000#700803655257# Zoey Harris 1121042880000065 +705Boariridescent bonus pay for amazing work on #OSS 00010000065 +622231380104886061167045077170000000000#817165716317# Isabella Thompson 1121042880000066 +705Spirittyphoon bonus pay for amazing work on #OSS 00010000066 +622231380104666385064742148400000000000#311013211866# Olivia Martin 1121042880000067 +705Frightmountain bonus pay for amazing work on #OSS 00010000067 +622231380104480746763341887540000000000#515612680730# Zoey Wilson 1121042880000068 +705Knaveforest bonus pay for amazing work on #OSS 00010000068 +622231380104886648222808763430000000000#062471032833# Chloe Smith 1121042880000069 +705Riderdenim bonus pay for amazing work on #OSS 00010000069 +622231380104702550884323328600000000000#886518034044# Ella Johnson 1121042880000070 +705Hoofcyber bonus pay for amazing work on #OSS 00010000070 +622231380104585404266386627060000000000#865632623838# Natalie Williams 1121042880000071 +705Otterfan bonus pay for amazing work on #OSS 00010000071 +622231380104714310613226563640000000000#603731552400# Joseph Johnson 1121042880000072 +705Spritejasper bonus pay for amazing work on #OSS 00010000072 +622231380104142320321343243740000000000#028336360700# Charlotte Davis 1121042880000073 +705Riderfreckle bonus pay for amazing work on #OSS 00010000073 +622231380104262341607875208870000000000#787415111206# William Thomas 1121042880000074 +705Witchseed bonus pay for amazing work on #OSS 00010000074 +622231380104311818054316378070000000000#834361516541# Michael Williams 1121042880000075 +705Scarerslow bonus pay for amazing work on #OSS 00010000075 +622231380104088001568185750740000000000#084425077728# Elizabeth Martinez 1121042880000076 +705Raptorspring bonus pay for amazing work on #OSS 00010000076 +622231380104462378705654767510000000000#502432460435# Natalie Jackson 1121042880000077 +705Legstundra bonus pay for amazing work on #OSS 00010000077 +622231380104841281138216150660000000000#230300866576# Joshua Wilson 1121042880000078 +705Serpentsquare bonus pay for amazing work on #OSS 00010000078 +622231380104610257267417062040000000000#125778612320# Olivia Williams 1121042880000079 +705Kingmetal bonus pay for amazing work on #OSS 00010000079 +622231380104465064216812716600000000000#605845843621# Jacob Brown 1121042880000080 +705Playerbristle bonus pay for amazing work on #OSS 00010000080 +622231380104667152664813748720000000000#565174016018# Aiden White 1121042880000081 +705Flametabby bonus pay for amazing work on #OSS 00010000081 +622231380104784883617662311750000000000#363413484730# William Jones 1121042880000082 +705Watchercrazy bonus pay for amazing work on #OSS 00010000082 +622231380104803103172335445780000000000#472656445614# Emily Johnson 1121042880000083 +705Hawkgreen bonus pay for amazing work on #OSS 00010000083 +622231380104455347512373687740000000000#823052777748# Joseph Miller 1121042880000084 +705Handtorch bonus pay for amazing work on #OSS 00010000084 +622231380104284218447187815080000000000#568762372047# Andrew Martinez 1121042880000085 +705Hunterink bonus pay for amazing work on #OSS 00010000085 +622231380104400441480271611440000000000#285653888653# Daniel Moore 1121042880000086 +705Arrowsponge bonus pay for amazing work on #OSS 00010000086 +622231380104124230832436748060000000000#461157641702# Chloe Williams 1121042880000087 +705Elkjade bonus pay for amazing work on #OSS 00010000087 +622231380104222671211362652630000000000#023610681650# Anthony Wilson 1121042880000088 +705Ridgemad bonus pay for amazing work on #OSS 00010000088 +622231380104564614001863227530000000000#803875016656# Avery Wilson 1121042880000089 +705Craneoil bonus pay for amazing work on #OSS 00010000089 +622231380104706686561634014770000000000#467700524304# Elijah Williams 1121042880000090 +705Sagemetal bonus pay for amazing work on #OSS 00010000090 +622231380104632273155044448100000000000#874583786846# David Martin 1121042880000091 +705Raccoonglimmer bonus pay for amazing work on #OSS 00010000091 +622231380104621117121373217430000000000#704737487876# Noah Davis 1121042880000092 +705Stingercrystal bonus pay for amazing work on #OSS 00010000092 +622231380104144423530062234510000000000#312077781188# Jacob Martin 1121042880000093 +705Heronhail bonus pay for amazing work on #OSS 00010000093 +622231380104306051884226176300000000000#300320882227# Isabella Johnson 1121042880000094 +705Spearbramble bonus pay for amazing work on #OSS 00010000094 +622231380104855614851160580540000000000#384348756214# Addison Anderson 1121042880000095 +705Princemaze bonus pay for amazing work on #OSS 00010000095 +622231380104334434487674741000000000000#325252056078# Michael Miller 1121042880000096 +705Stingsilver bonus pay for amazing work on #OSS 00010000096 +622231380104850466172856025850000000000#352025846715# Emma Harris 1121042880000097 +705Keeperbow bonus pay for amazing work on #OSS 00010000097 +622231380104052520538550682480000000000#813441526126# Emily Smith 1121042880000098 +705Jesterdesert bonus pay for amazing work on #OSS 00010000098 +622231380104248782837434854000000000000#647455157545# Sophia Harris 1121042880000099 +705Talonrust bonus pay for amazing work on #OSS 00010000099 +622231380104658086212741080540000000000#078575560866# Zoey Moore 1121042880000100 +705Cowllime bonus pay for amazing work on #OSS 00010000100 +622231380104245174818883346150000000000#338161571345# Alexander Moore 1121042880000101 +705Lighterlie bonus pay for amazing work on #OSS 00010000101 +622231380104042012121332540520000000000#874421426813# Anthony Harris 1121042880000102 +705Shakerpaper bonus pay for amazing work on #OSS 00010000102 +622231380104570565170478503450000000000#531120264743# Daniel Thompson 1121042880000103 +705Puppywest bonus pay for amazing work on #OSS 00010000103 +622231380104087362224403475740000000000#158748064680# Daniel Miller 1121042880000104 +705Curtainring bonus pay for amazing work on #OSS 00010000104 +622231380104752557371245553250000000000#462478246262# Mason Anderson 1121042880000105 +705Flameshade bonus pay for amazing work on #OSS 00010000105 +622231380104664133683085040010000000000#180575038026# David Taylor 1121042880000106 +705Ferrettulip bonus pay for amazing work on #OSS 00010000106 +622231380104456354845324138580000000000#333212613252# Lily Martin 1121042880000107 +705Raptorblaze bonus pay for amazing work on #OSS 00010000107 +622231380104047824822655381350000000000#847334657355# Anthony Jackson 1121042880000108 +705Stalkergiant bonus pay for amazing work on #OSS 00010000108 +622231380104768103134661353320000000000#270887030358# Olivia Thomas 1121042880000109 +705Frightiridescent bonus pay for amazing work on #OSS 00010000109 +622231380104666322701073518350000000000#562867632002# Daniel Jones 1121042880000110 +705Hissbevel bonus pay for amazing work on #OSS 00010000110 +622231380104651813478842028700000000000#750832808707# Lily Martin 1121042880000111 +705Bellyclover bonus pay for amazing work on #OSS 00010000111 +622231380104018540554026161360000000000#512112368871# Matthew Jackson 1121042880000112 +705Kangaroodestiny bonus pay for amazing work on #OSS 00010000112 +622231380104717324400345136450000000000#837715533808# Addison Wilson 1121042880000113 +705Gemnettle bonus pay for amazing work on #OSS 00010000113 +622231380104600730580323652010000000000#237331085181# Joseph Miller 1121042880000114 +705Leopardboulder bonus pay for amazing work on #OSS 00010000114 +622231380104521143712447752150000000000#538762825810# Sophia Brown 1121042880000115 +705Ribblaze bonus pay for amazing work on #OSS 00010000115 +622231380104430147588342174180000000000#186503073341# Noah White 1121042880000116 +705Catcherdog bonus pay for amazing work on #OSS 00010000116 +622231380104044680326734626000000000000#230114556180# Emily Martinez 1121042880000117 +705Leopardpond bonus pay for amazing work on #OSS 00010000117 +622231380104644211502080461670000000000#664278116342# Addison Miller 1121042880000118 +705Panthernoon bonus pay for amazing work on #OSS 00010000118 +622231380104021270328835612430000000000#247104051750# Alexander Taylor 1121042880000119 +705Scourgeplum bonus pay for amazing work on #OSS 00010000119 +622231380104668841012465456050000000000#870720258340# Jacob White 1121042880000120 +705Toucanrowan bonus pay for amazing work on #OSS 00010000120 +622231380104762020620135705740000000000#075765072006# Aiden Davis 1121042880000121 +705Lightercactus bonus pay for amazing work on #OSS 00010000121 +622231380104824244186056054200000000000#045145366140# Lily Smith 1121042880000122 +705Birdfan bonus pay for amazing work on #OSS 00010000122 +622231380104322420646785476870000000000#782376376885# Emily Brown 1121042880000123 +705Boneglory bonus pay for amazing work on #OSS 00010000123 +622231380104585663632426286180000000000#515306264474# Lily Harris 1121042880000124 +705Paladincurly bonus pay for amazing work on #OSS 00010000124 +622231380104387743830845185320000000000#606831567653# Aiden Moore 1121042880000125 +705Jackalnarrow bonus pay for amazing work on #OSS 00010000125 +622231380104184123373547032050000000000#786538161114# Ella Smith 1121042880000126 +705Cloaktranslucent bonus pay for amazing work on #OSS 00010000126 +622231380104417567263423718380000000000#530618202115# Elijah Miller 1121042880000127 +705Deerdesert bonus pay for amazing work on #OSS 00010000127 +622231380104400341001031658450000000000#027274056357# Sofia Harris 1121042880000128 +705Flydew bonus pay for amazing work on #OSS 00010000128 +622231380104885843425601487710000000000#568230836286# Anthony Harris 1121042880000129 +705Glazerleather bonus pay for amazing work on #OSS 00010000129 +622231380104432640616150554650000000000#788587333362# Charlotte Robinson 1121042880000130 +705Bowdour bonus pay for amazing work on #OSS 00010000130 +622231380104820764424547130640000000000#745044377674# Aubrey Johnson 1121042880000131 +705Stingwest bonus pay for amazing work on #OSS 00010000131 +622231380104277737735826741550000000000#171800457672# Liam Thomas 1121042880000132 +705Salmonmango bonus pay for amazing work on #OSS 00010000132 +622231380104854122255870058780000000000#326456886503# Aiden Williams 1121042880000133 +705Maskstar bonus pay for amazing work on #OSS 00010000133 +622231380104106515240263462640000000000#632361531751# Lily Thomas 1121042880000134 +705Iguanamesquite bonus pay for amazing work on #OSS 00010000134 +622231380104420645201028885030000000000#722143142117# David Smith 1121042880000135 +705Apelapis bonus pay for amazing work on #OSS 00010000135 +622231380104582232544852253640000000000#253581244763# Sofia Davis 1121042880000136 +705Arrowquiver bonus pay for amazing work on #OSS 00010000136 +622231380104624872300427221310000000000#340120812177# Daniel Moore 1121042880000137 +705Knifelava bonus pay for amazing work on #OSS 00010000137 +622231380104614286067441771510000000000#673034842116# Jayden Anderson 1121042880000138 +705Huggerblaze bonus pay for amazing work on #OSS 00010000138 +622231380104260468260123508370000000000#182771241480# Sofia Wilson 1121042880000139 +705Dolphinblaze bonus pay for amazing work on #OSS 00010000139 +622231380104426042443686828800000000000#150711051273# Abigail Johnson 1121042880000140 +705Kickergiant bonus pay for amazing work on #OSS 00010000140 +622231380104141266462233325730000000000#503678700031# Avery Anderson 1121042880000141 +705Mythpine bonus pay for amazing work on #OSS 00010000141 +622231380104723747771746473840000000000#462107177484# Noah Martinez 1121042880000142 +705Antlerdestiny bonus pay for amazing work on #OSS 00010000142 +622231380104242504666421716280000000000#620234072038# David Jones 1121042880000143 +705Stallionglacier bonus pay for amazing work on #OSS 00010000143 +622231380104262452785108178100000000000#857143420562# Charlotte Robinson 1121042880000144 +705Cowlbrindle bonus pay for amazing work on #OSS 00010000144 +622231380104326615807067835110000000000#636383345184# Noah Thomas 1121042880000145 +705Arrowmire bonus pay for amazing work on #OSS 00010000145 +622231380104067557516254557800000000000#486321307767# Sophia Davis 1121042880000146 +705Goosecherry bonus pay for amazing work on #OSS 00010000146 +622231380104130335565753521470000000000#004668487708# Elizabeth Thompson 1121042880000147 +705Armshore bonus pay for amazing work on #OSS 00010000147 +622231380104245347320754273370000000000#841433832682# Elijah Thomas 1121042880000148 +705Leopardfantasy bonus pay for amazing work on #OSS 00010000148 +622231380104617826633311144480000000000#271046808416# William Brown 1121042880000149 +705Beeshell bonus pay for amazing work on #OSS 00010000149 +622231380104168574731180520210000000000#124777756185# Mia Robinson 1121042880000150 +705Bisonwell bonus pay for amazing work on #OSS 00010000150 +622231380104525648208386362720000000000#578713768322# Avery Thompson 1121042880000151 +705Pawbronze bonus pay for amazing work on #OSS 00010000151 +622231380104600641484415086060000000000#378168655051# James Anderson 1121042880000152 +705Crystalseed bonus pay for amazing work on #OSS 00010000152 +622231380104441284588002045550000000000#788052315251# Olivia Miller 1121042880000153 +705Clouddune bonus pay for amazing work on #OSS 00010000153 +622231380104143267370853787830000000000#273452364128# Mason Smith 1121042880000154 +705Carpettranslucent bonus pay for amazing work on #OSS 00010000154 +622231380104853503467328538530000000000#636474743603# Mason Robinson 1121042880000155 +705Grinhazel bonus pay for amazing work on #OSS 00010000155 +622231380104013665052750156400000000000#273485437547# Mia Davis 1121042880000156 +705Deathprickle bonus pay for amazing work on #OSS 00010000156 +622231380104715112608873708200000000000#602673726144# Mia Brown 1121042880000157 +705Gamblerbrave bonus pay for amazing work on #OSS 00010000157 +622231380104067065832861418810000000000#814304158587# Mason Robinson 1121042880000158 +705Walkerpond bonus pay for amazing work on #OSS 00010000158 +622231380104316207714143475570000000000#504154311102# Elijah Martin 1121042880000159 +705Spiritprism bonus pay for amazing work on #OSS 00010000159 +622231380104230718527416807000000000000#681237780180# Elijah Johnson 1121042880000160 +705Bitefree bonus pay for amazing work on #OSS 00010000160 +622231380104647417776703315300000000000#584640835843# Lily Smith 1121042880000161 +705Tailjust bonus pay for amazing work on #OSS 00010000161 +622231380104418178011864646350000000000#867323147341# Liam White 1121042880000162 +705Masksaber bonus pay for amazing work on #OSS 00010000162 +622231380104375140737547706810000000000#364224316730# Sofia Jones 1121042880000163 +705Scarrelic bonus pay for amazing work on #OSS 00010000163 +622231380104846562661266217650000000000#700084803074# Sophia Jackson 1121042880000164 +705Ridermirage bonus pay for amazing work on #OSS 00010000164 +622231380104736156665585047050000000000#061808400110# Anthony White 1121042880000165 +705Songhoney bonus pay for amazing work on #OSS 00010000165 +622231380104378722316076587230000000000#751720745504# Anthony Garcia 1121042880000166 +705Vultureclover bonus pay for amazing work on #OSS 00010000166 +622231380104836571500244513160000000000#881575731762# Abigail Smith 1121042880000167 +705Pythonrune bonus pay for amazing work on #OSS 00010000167 +622231380104810257486275481220000000000#561887701423# James Williams 1121042880000168 +705Fishclover bonus pay for amazing work on #OSS 00010000168 +622231380104254024056058400850000000000#036385155077# Avery Smith 1121042880000169 +705Maskcandle bonus pay for amazing work on #OSS 00010000169 +622231380104586000872208202510000000000#802124307310# Andrew Davis 1121042880000170 +705Pantherglen bonus pay for amazing work on #OSS 00010000170 +622231380104521147404323345800000000000#613010568413# Elijah Garcia 1121042880000171 +705Eyemagenta bonus pay for amazing work on #OSS 00010000171 +622231380104711535022630823750000000000#623636764063# Daniel Johnson 1121042880000172 +705Leopardalder bonus pay for amazing work on #OSS 00010000172 +622231380104557775401511058640000000000#221768005352# Avery Harris 1121042880000173 +705Doghill bonus pay for amazing work on #OSS 00010000173 +622231380104470324263770188840000000000#655228448422# Mason Miller 1121042880000174 +705Ratspice bonus pay for amazing work on #OSS 00010000174 +622231380104764370030147732300000000000#527331677271# Olivia Garcia 1121042880000175 +705Spritecrystal bonus pay for amazing work on #OSS 00010000175 +622231380104172282014527426230000000000#860251687604# Elijah Brown 1121042880000176 +705Heronpatch bonus pay for amazing work on #OSS 00010000176 +622231380104628682872216656740000000000#530186804417# Joseph Thomas 1121042880000177 +705Lordmalachite bonus pay for amazing work on #OSS 00010000177 +622231380104288442828531088370000000000#175776101750# Jacob Wilson 1121042880000178 +705Scorpionthorn bonus pay for amazing work on #OSS 00010000178 +622231380104715781743816766720000000000#227864201475# Elizabeth Moore 1121042880000179 +705Lynxquilt bonus pay for amazing work on #OSS 00010000179 +622231380104847120231142183600000000000#188526086055# Alexander Wilson 1121042880000180 +705Dancertree bonus pay for amazing work on #OSS 00010000180 +622231380104687020721633166160000000000#271328831446# Joseph Robinson 1121042880000181 +705Houndrose bonus pay for amazing work on #OSS 00010000181 +622231380104320616760226185060000000000#604508282835# Ella Davis 1121042880000182 +705Wandererriver bonus pay for amazing work on #OSS 00010000182 +622231380104545873228466180280000000000#684257468100# Ella Anderson 1121042880000183 +705Sightfossil bonus pay for amazing work on #OSS 00010000183 +622231380104168314228241175150000000000#570874176185# Zoey Anderson 1121042880000184 +705Hornetwood bonus pay for amazing work on #OSS 00010000184 +622231380104363305303028037210000000000#764362344473# Avery Jackson 1121042880000185 +705Whiphoneysuckle bonus pay for amazing work on #OSS 00010000185 +622231380104582424557572311870000000000#561586123745# Ava Wilson 1121042880000186 +705Wolverinesnapdragon bonus pay for amazing work on #OSS 00010000186 +622231380104686250821158516220000000000#602021412050# Noah Johnson 1121042880000187 +705Cubrainbow bonus pay for amazing work on #OSS 00010000187 +622231380104536475244542368130000000000#627138665120# Aubrey Thomas 1121042880000188 +705Paladinheavy bonus pay for amazing work on #OSS 00010000188 +622231380104554380220426323410000000000#682563804618# Ella Robinson 1121042880000189 +705Gullphase bonus pay for amazing work on #OSS 00010000189 +622231380104320076053102118420000000000#166377205050# Ava Thomas 1121042880000190 +705Lashergravel bonus pay for amazing work on #OSS 00010000190 +622231380104608705088381154630000000000#515164481284# Benjamin Taylor 1121042880000191 +705Weaverdawn bonus pay for amazing work on #OSS 00010000191 +622231380104032283248127857180000000000#871852011173# Mia Davis 1121042880000192 +705Crystalbrook bonus pay for amazing work on #OSS 00010000192 +622231380104280732507048863210000000000#764124847441# Joshua Thomas 1121042880000193 +705Glazerripple bonus pay for amazing work on #OSS 00010000193 +622231380104872547100714877150000000000#630758163572# Benjamin Wilson 1121042880000194 +705Turnergossamer bonus pay for amazing work on #OSS 00010000194 +622231380104206528150021724260000000000#772646376146# Aubrey Martinez 1121042880000195 +705Diverapid bonus pay for amazing work on #OSS 00010000195 +622231380104023806356273768510000000000#864757641808# Madison Martin 1121042880000196 +705Storkcake bonus pay for amazing work on #OSS 00010000196 +622231380104212126878507074830000000000#877373156527# Matthew Miller 1121042880000197 +705Touchspeckle bonus pay for amazing work on #OSS 00010000197 +622231380104386856321432021220000000000#652802558332# Elijah Anderson 1121042880000198 +705Snapfoil bonus pay for amazing work on #OSS 00010000198 +622231380104222355078884428680000000000#340806174505# Ella Moore 1121042880000199 +705Finfish bonus pay for amazing work on #OSS 00010000199 +622231380104212568185215433810000000000#755045053657# David Johnson 1121042880000200 +705Falconfantasy bonus pay for amazing work on #OSS 00010000200 +622231380104435705173117412470000000000#022860568511# Jacob Wilson 1121042880000201 +705Dolphinblue bonus pay for amazing work on #OSS 00010000201 +622231380104408047647674744350000000000#088437860700# Ella Brown 1121042880000202 +705Samuraitin bonus pay for amazing work on #OSS 00010000202 +622231380104721524428463177140000000000#848438831433# Olivia Johnson 1121042880000203 +705Hoofcactus bonus pay for amazing work on #OSS 00010000203 +622231380104575271057075218860000000000#372552682687# Chloe White 1121042880000204 +705Shakerdull bonus pay for amazing work on #OSS 00010000204 +622231380104346873703620556020000000000#526166313863# Alexander Harris 1121042880000205 +705Ocelotbevel bonus pay for amazing work on #OSS 00010000205 +622231380104226865508340230080000000000#622831543362# Ella Jackson 1121042880000206 +705Thumbpear bonus pay for amazing work on #OSS 00010000206 +622231380104488535133821176660000000000#746264854533# William Anderson 1121042880000207 +705Salmonhot bonus pay for amazing work on #OSS 00010000207 +622231380104418024101758348600000000000#515150232104# Daniel Thomas 1121042880000208 +705Birdcotton bonus pay for amazing work on #OSS 00010000208 +622231380104603587305774228100000000000#002602822145# Olivia Martin 1121042880000209 +705Craftercarnelian bonus pay for amazing work on #OSS 00010000209 +622231380104514587003374273270000000000#607876153582# Charlotte Johnson 1121042880000210 +705Centaurvanilla bonus pay for amazing work on #OSS 00010000210 +622231380104857405060638760600000000000#648075521167# Madison Taylor 1121042880000211 +705Chatternoble bonus pay for amazing work on #OSS 00010000211 +622231380104883748610480763850000000000#105342754138# Michael Martinez 1121042880000212 +705Zebrasky bonus pay for amazing work on #OSS 00010000212 +622231380104763517148575148010000000000#024855710514# Alexander Martinez 1121042880000213 +705Elkfish bonus pay for amazing work on #OSS 00010000213 +622231380104575087568455327270000000000#108114168606# Jacob Martin 1121042880000214 +705Knifeash bonus pay for amazing work on #OSS 00010000214 +622231380104413140157626277040000000000#436272042783# Aubrey Harris 1121042880000215 +705Spursprinkle bonus pay for amazing work on #OSS 00010000215 +622231380104100613771474434030000000000#542244602133# Liam White 1121042880000216 +705Burnwinter bonus pay for amazing work on #OSS 00010000216 +622231380104117346344430501770000000000#452023516286# Avery Martin 1121042880000217 +705Toucannimble bonus pay for amazing work on #OSS 00010000217 +622231380104773615588045783130000000000#541010705767# Daniel Thomas 1121042880000218 +705Skinnercarnelian bonus pay for amazing work on #OSS 00010000218 +622231380104565520520131041400000000000#614354057606# Charlotte Davis 1121042880000219 +705Heronripple bonus pay for amazing work on #OSS 00010000219 +622231380104208275815350087780000000000#338034838124# Sofia Johnson 1121042880000220 +705Knightmica bonus pay for amazing work on #OSS 00010000220 +622231380104288504413151275510000000000#737154375800# Ella Thomas 1121042880000221 +705Bugchocolate bonus pay for amazing work on #OSS 00010000221 +622231380104553441364423272810000000000#741578734586# Mason Martinez 1121042880000222 +705Armpurple bonus pay for amazing work on #OSS 00010000222 +622231380104378562346434227050000000000#230242128023# Sophia Garcia 1121042880000223 +705Droprelic bonus pay for amazing work on #OSS 00010000223 +622231380104355227640881668800000000000#658637645685# Joshua Jackson 1121042880000224 +705Gazellelegend bonus pay for amazing work on #OSS 00010000224 +622231380104288150577052643150000000000#660476247868# Isabella Williams 1121042880000225 +705Whimseybevel bonus pay for amazing work on #OSS 00010000225 +622231380104848104064120851180000000000#026200837723# Zoey Smith 1121042880000226 +705Traderrazor bonus pay for amazing work on #OSS 00010000226 +622231380104015162576632036350000000000#505806154356# Elijah Thomas 1121042880000227 +705Goosebubble bonus pay for amazing work on #OSS 00010000227 +622231380104166183727651241840000000000#357840083504# Joshua Miller 1121042880000228 +705Lizardsharp bonus pay for amazing work on #OSS 00010000228 +622231380104384876040278371450000000000#688614461784# Abigail Robinson 1121042880000229 +705Samuraiquiver bonus pay for amazing work on #OSS 00010000229 +622231380104427650376046363820000000000#174473017726# Noah Smith 1121042880000230 +705Lightningnorth bonus pay for amazing work on #OSS 00010000230 +622231380104813347856741511730000000000#758046280218# Isabella Miller 1121042880000231 +705Zebrabramble bonus pay for amazing work on #OSS 00010000231 +622231380104825428364745654510000000000#085884051615# Isabella Anderson 1121042880000232 +705Shriekebony bonus pay for amazing work on #OSS 00010000232 +622231380104211847717528607350000000000#038364044115# Aiden Miller 1121042880000233 +705Carverrain bonus pay for amazing work on #OSS 00010000233 +622231380104733702430707880340000000000#281134538204# Sophia Davis 1121042880000234 +705Spiderfree bonus pay for amazing work on #OSS 00010000234 +622231380104604506428701457170000000000#631525730275# David Robinson 1121042880000235 +705Chargerspectrum bonus pay for amazing work on #OSS 00010000235 +622231380104770060037715552660000000000#135520117870# Anthony Brown 1121042880000236 +705Ravenskitter bonus pay for amazing work on #OSS 00010000236 +622231380104122212848627358860000000000#560426325750# William Miller 1121042880000237 +705Ringerbuttercup bonus pay for amazing work on #OSS 00010000237 +622231380104250417843810615570000000000#441148355868# Benjamin Moore 1121042880000238 +705Takerbow bonus pay for amazing work on #OSS 00010000238 +622231380104750701674166081320000000000#847532200816# Ethan Thomas 1121042880000239 +705Catcherfire bonus pay for amazing work on #OSS 00010000239 +622231380104538350310264852270000000000#208480353523# Mason Harris 1121042880000240 +705Ringersequoia bonus pay for amazing work on #OSS 00010000240 +622231380104126440347706368250000000000#872404753057# David Jackson 1121042880000241 +705Sageglacier bonus pay for amazing work on #OSS 00010000241 +622231380104474583103406613840000000000#457562128761# Anthony Brown 1121042880000242 +705Legssun bonus pay for amazing work on #OSS 00010000242 +622231380104654437138278431140000000000#772250576248# Abigail Johnson 1121042880000243 +705Painterriver bonus pay for amazing work on #OSS 00010000243 +622231380104724135026205571200000000000#635352133451# Addison Jackson 1121042880000244 +705Lizardroad bonus pay for amazing work on #OSS 00010000244 +622231380104353388678014426340000000000#513320676362# Joseph Miller 1121042880000245 +705Bunnysplit bonus pay for amazing work on #OSS 00010000245 +622231380104173835273038003270000000000#723865063277# Addison Williams 1121042880000246 +705Falconsunrise bonus pay for amazing work on #OSS 00010000246 +622231380104165172408835837260000000000#665274726440# Liam Robinson 1121042880000247 +705Stallionvine bonus pay for amazing work on #OSS 00010000247 +622231380104867481672111261280000000000#762234358660# Sophia Thompson 1121042880000248 +705Curtainbutton bonus pay for amazing work on #OSS 00010000248 +622231380104882345342418646860000000000#121172317386# Jacob Martin 1121042880000249 +705Browrhinestone bonus pay for amazing work on #OSS 00010000249 +622231380104616653325562157080000000000#158773756605# Jayden Miller 1121042880000250 +705Museleather bonus pay for amazing work on #OSS 00010000250 +622231380104407634367048528310000000000#473058478458# James Williams 1121042880000251 +705Cubspiral bonus pay for amazing work on #OSS 00010000251 +622231380104726126107432215160000000000#086885472033# Avery Thomas 1121042880000252 +705Lightersilk bonus pay for amazing work on #OSS 00010000252 +622231380104422343384478506710000000000#651821887310# Abigail Jackson 1121042880000253 +705Turnerphantom bonus pay for amazing work on #OSS 00010000253 +622231380104566573528284655380000000000#163388128510# Daniel Miller 1121042880000254 +705Fishspark bonus pay for amazing work on #OSS 00010000254 +622231380104865653545886480470000000000#401204442663# Joshua Thomas 1121042880000255 +705Cougarhoney bonus pay for amazing work on #OSS 00010000255 +622231380104235082075166272740000000000#052524124524# Daniel Williams 1121042880000256 +705Hyenairis bonus pay for amazing work on #OSS 00010000256 +622231380104014567407346230380000000000#170773362855# Matthew Robinson 1121042880000257 +705Dogglimmer bonus pay for amazing work on #OSS 00010000257 +622231380104012207840813782540000000000#251437384677# Aubrey Thomas 1121042880000258 +705Riderrag bonus pay for amazing work on #OSS 00010000258 +622231380104425011808488338170000000000#582828210087# Olivia Martin 1121042880000259 +705Wyrmchisel bonus pay for amazing work on #OSS 00010000259 +622231380104056181122684812650000000000#371036522517# Madison Anderson 1121042880000260 +705Collarspectrum bonus pay for amazing work on #OSS 00010000260 +622231380104188046811002481320000000000#067262132077# Jacob Johnson 1121042880000261 +705Throatgold bonus pay for amazing work on #OSS 00010000261 +622231380104050250530853668110000000000#614060441726# Avery Anderson 1121042880000262 +705Beakhickory bonus pay for amazing work on #OSS 00010000262 +622231380104777660137455425250000000000#064048506718# Madison Thomas 1121042880000263 +705Pumacanyon bonus pay for amazing work on #OSS 00010000263 +622231380104866560806750625800000000000#318438158317# Matthew Davis 1121042880000264 +705Friendsand bonus pay for amazing work on #OSS 00010000264 +622231380104266770424206114620000000000#465055326121# Ethan Miller 1121042880000265 +705Bellluminous bonus pay for amazing work on #OSS 00010000265 +622231380104172354271841183570000000000#300625774508# Olivia Martinez 1121042880000266 +705Snarlmarble bonus pay for amazing work on #OSS 00010000266 +622231380104381830553760355710000000000#845551526126# Elizabeth Moore 1121042880000267 +705Buffaloboom bonus pay for amazing work on #OSS 00010000267 +622231380104204415760832446170000000000#728413028822# Joshua White 1121042880000268 +705Vulturebramble bonus pay for amazing work on #OSS 00010000268 +622231380104836700215431242520000000000#850708718738# Alexander Garcia 1121042880000269 +705Sagewest bonus pay for amazing work on #OSS 00010000269 +622231380104232417018885778710000000000#756430387832# Elizabeth Anderson 1121042880000270 +705Kangaroovalley bonus pay for amazing work on #OSS 00010000270 +622231380104485365253131185510000000000#365331025563# Madison Thomas 1121042880000271 +705Condorcandle bonus pay for amazing work on #OSS 00010000271 +622231380104332503160068758350000000000#820078352386# Anthony Martinez 1121042880000272 +705Roverplump bonus pay for amazing work on #OSS 00010000272 +622231380104800718852757370320000000000#346004070206# Ethan Thompson 1121042880000273 +705Dolphinivory bonus pay for amazing work on #OSS 00010000273 +622231380104406012753082234370000000000#354560118506# Lily Martin 1121042880000274 +705Condorabalone bonus pay for amazing work on #OSS 00010000274 +622231380104611206152520773010000000000#583626565751# Zoey Brown 1121042880000275 +705Devourerflicker bonus pay for amazing work on #OSS 00010000275 +622231380104045387526730230220000000000#088080824267# Alexander Brown 1121042880000276 +705Parrotprickle bonus pay for amazing work on #OSS 00010000276 +622231380104611818385053642260000000000#085140887856# Aiden Robinson 1121042880000277 +705Hidequark bonus pay for amazing work on #OSS 00010000277 +622231380104582262022664646380000000000#864861314452# William Davis 1121042880000278 +705Dancertin bonus pay for amazing work on #OSS 00010000278 +622231380104577476813224476370000000000#470083362344# Elizabeth Taylor 1121042880000279 +705Footsand bonus pay for amazing work on #OSS 00010000279 +622231380104446632732511278810000000000#655560404457# Aubrey Brown 1121042880000280 +705Spiritapple bonus pay for amazing work on #OSS 00010000280 +622231380104110104274101381480000000000#883836427060# Olivia Anderson 1121042880000281 +705Sharkgentle bonus pay for amazing work on #OSS 00010000281 +622231380104321321635818101170000000000#755752620180# Aubrey Thomas 1121042880000282 +705Legendyellow bonus pay for amazing work on #OSS 00010000282 +622231380104438142377527474300000000000#832124831604# Sofia Miller 1121042880000283 +705Hornpitch bonus pay for amazing work on #OSS 00010000283 +622231380104262386042381603100000000000#168586303642# Charlotte Davis 1121042880000284 +705Shriekvaliant bonus pay for amazing work on #OSS 00010000284 +622231380104754324732210550640000000000#313513218563# Olivia Wilson 1121042880000285 +705Whaledirt bonus pay for amazing work on #OSS 00010000285 +622231380104202385022436005430000000000#433653724577# Jacob Wilson 1121042880000286 +705Tonguecitrine bonus pay for amazing work on #OSS 00010000286 +622231380104860126524781432310000000000#484828316417# Benjamin Anderson 1121042880000287 +705Elkhollow bonus pay for amazing work on #OSS 00010000287 +622231380104542564184016208500000000000#183606847456# Avery Jones 1121042880000288 +705Handzircon bonus pay for amazing work on #OSS 00010000288 +622231380104578536472327100710000000000#256235858108# Charlotte Jackson 1121042880000289 +705Orioleaquamarine bonus pay for amazing work on #OSS 00010000289 +622231380104077100183328324730000000000#600332242581# Sofia Robinson 1121042880000290 +705Donkeyswift bonus pay for amazing work on #OSS 00010000290 +622231380104463330324521263080000000000#425537870507# Sofia Robinson 1121042880000291 +705Fighterroad bonus pay for amazing work on #OSS 00010000291 +622231380104501134285360847080000000000#080532568131# Michael Wilson 1121042880000292 +705Yakwalnut bonus pay for amazing work on #OSS 00010000292 +622231380104481157744461040850000000000#003277153188# Aiden Davis 1121042880000293 +705Cubrampant bonus pay for amazing work on #OSS 00010000293 +622231380104262172805383814680000000000#401751718484# Emma Harris 1121042880000294 +705Fishmud bonus pay for amazing work on #OSS 00010000294 +622231380104603046823828815830000000000#544077130727# Matthew Taylor 1121042880000295 +705Gorillamirage bonus pay for amazing work on #OSS 00010000295 +622231380104008671867075716830000000000#220508268408# Sofia Wilson 1121042880000296 +705Collardune bonus pay for amazing work on #OSS 00010000296 +622231380104867323134504311210000000000#631354547284# Zoey Smith 1121042880000297 +705Duckgreat bonus pay for amazing work on #OSS 00010000297 +622231380104038777480083477210000000000#453107048644# Aiden White 1121042880000298 +705Healermint bonus pay for amazing work on #OSS 00010000298 +622231380104236220743672010450000000000#384148070148# Addison Garcia 1121042880000299 +705Princestone bonus pay for amazing work on #OSS 00010000299 +622231380104125756637402048020000000000#315436846070# Benjamin Brown 1121042880000300 +705Birdlightning bonus pay for amazing work on #OSS 00010000300 +622231380104632043067736323630000000000#248161165461# Elizabeth Williams 1121042880000301 +705Chillermeadow bonus pay for amazing work on #OSS 00010000301 +622231380104170108284041258200000000000#350405345508# Olivia Jackson 1121042880000302 +705Sargentheather bonus pay for amazing work on #OSS 00010000302 +622231380104542300643851143630000000000#248355406718# Lily Jones 1121042880000303 +705Catsly bonus pay for amazing work on #OSS 00010000303 +622231380104483528454170004580000000000#423771022384# Lily Thompson 1121042880000304 +705Dragonchatter bonus pay for amazing work on #OSS 00010000304 +622231380104534837261886070470000000000#162620538204# Zoey Martinez 1121042880000305 +705Samuraisilk bonus pay for amazing work on #OSS 00010000305 +622231380104054825236478221860000000000#057285304778# Elijah Williams 1121042880000306 +705Bellpond bonus pay for amazing work on #OSS 00010000306 +622231380104342372048676034040000000000#551823228482# Lily White 1121042880000307 +705Fangroan bonus pay for amazing work on #OSS 00010000307 +622231380104033638648050757570000000000#208460382052# Ava Johnson 1121042880000308 +705Biterswift bonus pay for amazing work on #OSS 00010000308 +622231380104050004210705018220000000000#735884817714# Zoey Davis 1121042880000309 +705Samurairelic bonus pay for amazing work on #OSS 00010000309 +622231380104084801627724345860000000000#137485122561# Sophia Moore 1121042880000310 +705Burngray bonus pay for amazing work on #OSS 00010000310 +622231380104625501715274217300000000000#706044188201# Joshua Davis 1121042880000311 +705Takercotton bonus pay for amazing work on #OSS 00010000311 +622231380104185004134050047650000000000#132143445756# Lily Martinez 1121042880000312 +705Jackaltwisty bonus pay for amazing work on #OSS 00010000312 +622231380104645737308734867880000000000#555872224641# Benjamin Garcia 1121042880000313 +705Crestnickel bonus pay for amazing work on #OSS 00010000313 +622231380104268115014721403140000000000#776348600817# Chloe Brown 1121042880000314 +705Doommirror bonus pay for amazing work on #OSS 00010000314 +622231380104888642674024181180000000000#221238672634# Joshua Garcia 1121042880000315 +705Chatterstone bonus pay for amazing work on #OSS 00010000315 +622231380104565368718761354510000000000#476164767165# Emily Miller 1121042880000316 +705Buffalodot bonus pay for amazing work on #OSS 00010000316 +622231380104071778476308520430000000000#442783144884# Michael Moore 1121042880000317 +705Loontwisty bonus pay for amazing work on #OSS 00010000317 +622231380104145648623222480270000000000#462555174711# Aiden Wilson 1121042880000318 +705Turnerhorse bonus pay for amazing work on #OSS 00010000318 +622231380104113231085605455170000000000#485571073852# William Harris 1121042880000319 +705Playerblack bonus pay for amazing work on #OSS 00010000319 +622231380104427151328387848750000000000#188844625710# Emily Davis 1121042880000320 +705Hisserhoney bonus pay for amazing work on #OSS 00010000320 +622231380104826031844857583560000000000#563207685846# William Johnson 1121042880000321 +705Ravenregal bonus pay for amazing work on #OSS 00010000321 +622231380104758000484781785050000000000#627337350116# Benjamin Davis 1121042880000322 +705Hornetcactus bonus pay for amazing work on #OSS 00010000322 +622231380104525531642107443800000000000#204155814146# Sophia Robinson 1121042880000323 +705Tonguenorth bonus pay for amazing work on #OSS 00010000323 +622231380104811815021888241170000000000#037723134826# James Wilson 1121042880000324 +705Crestfuschia bonus pay for amazing work on #OSS 00010000324 +622231380104174510228016546540000000000#286761535382# Emma Miller 1121042880000325 +705Handglacier bonus pay for amazing work on #OSS 00010000325 +622231380104778627646066114620000000000#421168436838# Zoey Martin 1121042880000326 +705Fingerwave bonus pay for amazing work on #OSS 00010000326 +622231380104332144482232014080000000000#604867453373# Jacob Garcia 1121042880000327 +705Iguanaviridian bonus pay for amazing work on #OSS 00010000327 +622231380104817525530423624160000000000#677147436661# David Martin 1121042880000328 +705Leopardregal bonus pay for amazing work on #OSS 00010000328 +622231380104870554061883158300000000000#822380654563# Charlotte Martinez 1121042880000329 +705Slicercosmic bonus pay for amazing work on #OSS 00010000329 +622231380104680107237630886300000000000#466781562231# David Moore 1121042880000330 +705Hounddesert bonus pay for amazing work on #OSS 00010000330 +622231380104411316757517602770000000000#760222011661# Andrew Martin 1121042880000331 +705Shriekerlavender bonus pay for amazing work on #OSS 00010000331 +622231380104228564513001304870000000000#712466551741# Jayden Thomas 1121042880000332 +705Hisserslash bonus pay for amazing work on #OSS 00010000332 +622231380104237411663845756360000000000#172703734452# Aubrey Thomas 1121042880000333 +705Legendsnow bonus pay for amazing work on #OSS 00010000333 +622231380104367274603237084310000000000#736823173473# Liam Brown 1121042880000334 +705Foesprinkle bonus pay for amazing work on #OSS 00010000334 +622231380104667571586223608150000000000#464746283240# Jayden Martinez 1121042880000335 +705Warriorhickory bonus pay for amazing work on #OSS 00010000335 +622231380104070626781331801070000000000#781023783346# Joseph Martin 1121042880000336 +705Facepuddle bonus pay for amazing work on #OSS 00010000336 +622231380104417021764122565360000000000#252344483226# Alexander Johnson 1121042880000337 +705Banefire bonus pay for amazing work on #OSS 00010000337 +622231380104577417216145773800000000000#082731257834# Zoey Smith 1121042880000338 +705Scribehickory bonus pay for amazing work on #OSS 00010000338 +622231380104620533364618231440000000000#118454513320# Ella Jones 1121042880000339 +705Foxvoid bonus pay for amazing work on #OSS 00010000339 +622231380104757083204536628510000000000#775401318177# James Garcia 1121042880000340 +705Gemflash bonus pay for amazing work on #OSS 00010000340 +622231380104674136843305281660000000000#865340535565# Chloe White 1121042880000341 +705Weedsticky bonus pay for amazing work on #OSS 00010000341 +622231380104337852015235168600000000000#748422221705# Isabella Moore 1121042880000342 +705Ravergranite bonus pay for amazing work on #OSS 00010000342 +622231380104032507157826673700000000000#537058845622# Mason Davis 1121042880000343 +705Falconvenom bonus pay for amazing work on #OSS 00010000343 +622231380104328150757472803350000000000#703354738221# Benjamin Smith 1121042880000344 +705Roverquartz bonus pay for amazing work on #OSS 00010000344 +622231380104710087388732237640000000000#528736142125# Joseph Jones 1121042880000345 +705Storklinen bonus pay for amazing work on #OSS 00010000345 +622231380104204754882882725380000000000#142671005781# Daniel Martinez 1121042880000346 +705Gamblerstitch bonus pay for amazing work on #OSS 00010000346 +622231380104368153707584753360000000000#446045860667# Charlotte Martinez 1121042880000347 +705Frilliridescent bonus pay for amazing work on #OSS 00010000347 +622231380104178044445505246340000000000#730236338180# Daniel Moore 1121042880000348 +705Divepetal bonus pay for amazing work on #OSS 00010000348 +622231380104804756704001331780000000000#724502323407# Daniel Martinez 1121042880000349 +705Antamber bonus pay for amazing work on #OSS 00010000349 +622231380104575681840657336870000000000#517334650612# Ava Martinez 1121042880000350 +705Bitefate bonus pay for amazing work on #OSS 00010000350 +622231380104367340350244486660000000000#416043844574# Emma Smith 1121042880000351 +705Parrotpear bonus pay for amazing work on #OSS 00010000351 +622231380104518012340150552060000000000#828108603271# Charlotte Jones 1121042880000352 +705Gemvaliant bonus pay for amazing work on #OSS 00010000352 +622231380104126560831062617860000000000#124083672333# David Harris 1121042880000353 +705Parrotglen bonus pay for amazing work on #OSS 00010000353 +622231380104355836741872803180000000000#010648652225# Jayden Williams 1121042880000354 +705Frightcomet bonus pay for amazing work on #OSS 00010000354 +622231380104066818811785453510000000000#805126856762# Mason Davis 1121042880000355 +705Loonmetal bonus pay for amazing work on #OSS 00010000355 +622231380104562710674256152750000000000#517601106642# Zoey Thomas 1121042880000356 +705Stealershard bonus pay for amazing work on #OSS 00010000356 +622231380104214703354180551710000000000#776804143356# Madison Miller 1121042880000357 +705Seerbrown bonus pay for amazing work on #OSS 00010000357 +622231380104474384377607711610000000000#515830820581# Mia Harris 1121042880000358 +705Mindrain bonus pay for amazing work on #OSS 00010000358 +622231380104850007055543414160000000000#312104131338# Andrew Thomas 1121042880000359 +705Baneskitter bonus pay for amazing work on #OSS 00010000359 +622231380104672565738368375070000000000#857352513744# Aiden Martin 1121042880000360 +705Shielddaffodil bonus pay for amazing work on #OSS 00010000360 +622231380104043365580635326510000000000#122078635646# Zoey Martinez 1121042880000361 +705Jaguarweak bonus pay for amazing work on #OSS 00010000361 +622231380104027446735728617020000000000#253112525238# William Jones 1121042880000362 +705Eaglecyan bonus pay for amazing work on #OSS 00010000362 +622231380104182535642048745100000000000#718483083747# Andrew White 1121042880000363 +705Dolphinflame bonus pay for amazing work on #OSS 00010000363 +622231380104816745745815547320000000000#425001172072# Sofia Thomas 1121042880000364 +705Chargerneon bonus pay for amazing work on #OSS 00010000364 +622231380104351123013302141320000000000#456650681083# Mason Thomas 1121042880000365 +705Razorberyl bonus pay for amazing work on #OSS 00010000365 +622231380104378202685661527840000000000#542327182100# Aiden Moore 1121042880000366 +705Hornshag bonus pay for amazing work on #OSS 00010000366 +622231380104058776472687412420000000000#168416363042# Emma Brown 1121042880000367 +705Scarermetal bonus pay for amazing work on #OSS 00010000367 +622231380104027382033570278720000000000#715083367652# Zoey Robinson 1121042880000368 +705Donkeysapphire bonus pay for amazing work on #OSS 00010000368 +622231380104755374057384622860000000000#854884237483# Noah White 1121042880000369 +705Maskgem bonus pay for amazing work on #OSS 00010000369 +622231380104000777406017784360000000000#183468845752# Elijah Robinson 1121042880000370 +705Kittenlove bonus pay for amazing work on #OSS 00010000370 +622231380104683601351265375530000000000#023788064101# Aiden Martin 1121042880000371 +705Whipstar bonus pay for amazing work on #OSS 00010000371 +622231380104228676308640362730000000000#285687244502# Michael Johnson 1121042880000372 +705Forgerwood bonus pay for amazing work on #OSS 00010000372 +622231380104360333350042857130000000000#670681274638# Liam Thompson 1121042880000373 +705Beeshy bonus pay for amazing work on #OSS 00010000373 +622231380104181726507201362430000000000#003207243336# Ethan Anderson 1121042880000374 +705Mythwhip bonus pay for amazing work on #OSS 00010000374 +622231380104065534608123518460000000000#373676516563# Abigail Robinson 1121042880000375 +705Killerred bonus pay for amazing work on #OSS 00010000375 +622231380104303026413125717730000000000#767661274416# Abigail Wilson 1121042880000376 +705Voletwilight bonus pay for amazing work on #OSS 00010000376 +622231380104342827731767747700000000000#348268472832# Alexander Williams 1121042880000377 +705Parrotink bonus pay for amazing work on #OSS 00010000377 +622231380104732050238178818550000000000#780657231668# Elizabeth Davis 1121042880000378 +705Sageautumn bonus pay for amazing work on #OSS 00010000378 +622231380104225326373278565650000000000#247277744366# Ethan Thompson 1121042880000379 +705Hawkviolet bonus pay for amazing work on #OSS 00010000379 +622231380104380570483180476870000000000#182717256443# Jayden Thomas 1121042880000380 +705Gargoylewhite bonus pay for amazing work on #OSS 00010000380 +622231380104506824732810876340000000000#716353308375# Elijah Martin 1121042880000381 +705Howlerlizard bonus pay for amazing work on #OSS 00010000381 +622231380104516072202230735200000000000#622524446412# Aiden Martinez 1121042880000382 +705Mythcold bonus pay for amazing work on #OSS 00010000382 +622231380104510732360241260330000000000#846564886325# Aubrey Williams 1121042880000383 +705Swordbrindle bonus pay for amazing work on #OSS 00010000383 +622231380104455486326720814830000000000#264482326674# Emily White 1121042880000384 +705Apetabby bonus pay for amazing work on #OSS 00010000384 +622231380104770255452886686310000000000#765147317424# Madison Brown 1121042880000385 +705Griffinshore bonus pay for amazing work on #OSS 00010000385 +622231380104831185621033032130000000000#657710155606# Noah White 1121042880000386 +705Crestleaf bonus pay for amazing work on #OSS 00010000386 +622231380104283247013234261030000000000#587268858575# Avery Harris 1121042880000387 +705Sightfoul bonus pay for amazing work on #OSS 00010000387 +622231380104721321726815122160000000000#054558245217# Michael Moore 1121042880000388 +705Reapernotch bonus pay for amazing work on #OSS 00010000388 +622231380104166821608016767480000000000#002082052777# Alexander White 1121042880000389 +705Saverspice bonus pay for amazing work on #OSS 00010000389 +622231380104622280538044057080000000000#105506407372# Emily Smith 1121042880000390 +705Wizardpeach bonus pay for amazing work on #OSS 00010000390 +622231380104781318500007153720000000000#486216707045# Lily Martin 1121042880000391 +705Fliersour bonus pay for amazing work on #OSS 00010000391 +622231380104180163676483167200000000000#541381673277# Joseph Jackson 1121042880000392 +705Bitecrocus bonus pay for amazing work on #OSS 00010000392 +622231380104840584236406534510000000000#520117765582# Andrew Williams 1121042880000393 +705Beetlesummer bonus pay for amazing work on #OSS 00010000393 +622231380104817541838438605200000000000#861771141155# Ava Williams 1121042880000394 +705Servantrag bonus pay for amazing work on #OSS 00010000394 +622231380104737347758660521500000000000#844364276454# Mia Thompson 1121042880000395 +705Dutchessplume bonus pay for amazing work on #OSS 00010000395 +622231380104770874463000278540000000000#231652404355# Mia Jackson 1121042880000396 +705Roversleet bonus pay for amazing work on #OSS 00010000396 +622231380104225418525006817680000000000#748537712155# Elizabeth Williams 1121042880000397 +705Legendtwisty bonus pay for amazing work on #OSS 00010000397 +622231380104551256316105342310000000000#041711663406# Zoey White 1121042880000398 +705Flypale bonus pay for amazing work on #OSS 00010000398 +622231380104051621420350500340000000000#456276823438# Mia Harris 1121042880000399 +705Warlockjasper bonus pay for amazing work on #OSS 00010000399 +622231380104536805581240224160000000000#075552028530# Chloe Johnson 1121042880000400 +705Gempyrite bonus pay for amazing work on #OSS 00010000400 +622231380104331764454473822780000000000#207087530177# Liam Davis 1121042880000401 +705Pumagreen bonus pay for amazing work on #OSS 00010000401 +622231380104557211633817142270000000000#823230122344# Matthew Harris 1121042880000402 +705Handviridian bonus pay for amazing work on #OSS 00010000402 +622231380104846123885420852250000000000#802383262384# Anthony Martinez 1121042880000403 +705Roareralpine bonus pay for amazing work on #OSS 00010000403 +622231380104443420230256085420000000000#230832784867# Emma Smith 1121042880000404 +705Raverquartz bonus pay for amazing work on #OSS 00010000404 +622231380104850267187150138050000000000#414874784808# Benjamin Miller 1121042880000405 +705Scribeleaf bonus pay for amazing work on #OSS 00010000405 +622231380104285286151007766400000000000#512481853126# Andrew Harris 1121042880000406 +705Voicerogue bonus pay for amazing work on #OSS 00010000406 +622231380104304366721215576870000000000#808604855373# Madison Taylor 1121042880000407 +705Mythwinter bonus pay for amazing work on #OSS 00010000407 +622231380104035164284646131600000000000#707340871700# Ethan Jones 1121042880000408 +705Seedplump bonus pay for amazing work on #OSS 00010000408 +622231380104857485634526764170000000000#550343047020# Andrew Smith 1121042880000409 +705Eaterfrill bonus pay for amazing work on #OSS 00010000409 +622231380104475536136783801400000000000#328885078106# Olivia Brown 1121042880000410 +705Dropcream bonus pay for amazing work on #OSS 00010000410 +622231380104441874768840518410000000000#637388837478# Mia Jones 1121042880000411 +705Centaurpink bonus pay for amazing work on #OSS 00010000411 +622231380104176154055041512410000000000#708225234280# Emma Thomas 1121042880000412 +705Swishersaber bonus pay for amazing work on #OSS 00010000412 +622231380104371375160450680860000000000#271107285211# Ava Martinez 1121042880000413 +705Turnerclever bonus pay for amazing work on #OSS 00010000413 +622231380104241040423816544860000000000#605674742414# Elijah Taylor 1121042880000414 +705Deathwinter bonus pay for amazing work on #OSS 00010000414 +622231380104404565012201835170000000000#206072477502# Olivia Robinson 1121042880000415 +705Spriteflower bonus pay for amazing work on #OSS 00010000415 +622231380104728751320668835310000000000#345080275682# Abigail Wilson 1121042880000416 +705Ridershell bonus pay for amazing work on #OSS 00010000416 +622231380104618241386542610240000000000#757112331340# Liam Wilson 1121042880000417 +705Bootscythe bonus pay for amazing work on #OSS 00010000417 +622231380104634074105848451670000000000#072563201743# Elijah Anderson 1121042880000418 +705Storklily bonus pay for amazing work on #OSS 00010000418 +622231380104560311665145172470000000000#561783776260# Anthony Robinson 1121042880000419 +705Lizardperidot bonus pay for amazing work on #OSS 00010000419 +622231380104773732374846584500000000000#810077027758# Matthew Johnson 1121042880000420 +705Seekerginger bonus pay for amazing work on #OSS 00010000420 +622231380104604166005561837400000000000#670060854826# Aubrey Williams 1121042880000421 +705Knightswift bonus pay for amazing work on #OSS 00010000421 +622231380104567707562242871540000000000#304030045082# Natalie Davis 1121042880000422 +705Scarercandle bonus pay for amazing work on #OSS 00010000422 +622231380104502558017138185730000000000#154703004873# Isabella Thompson 1121042880000423 +705Snapperbranch bonus pay for amazing work on #OSS 00010000423 +622231380104865288362277412030000000000#588852117332# Noah Martinez 1121042880000424 +705Reaperdour bonus pay for amazing work on #OSS 00010000424 +622231380104567311507771817640000000000#425630077432# Zoey Miller 1121042880000425 +705Killerhail bonus pay for amazing work on #OSS 00010000425 +622231380104124006674572532520000000000#117606641717# Ava Wilson 1121042880000426 +705Flylinen bonus pay for amazing work on #OSS 00010000426 +622231380104278401656317440210000000000#413234344707# Chloe Miller 1121042880000427 +705Hideclever bonus pay for amazing work on #OSS 00010000427 +622231380104782112843028483860000000000#614487754785# Liam Martin 1121042880000428 +705Dogroad bonus pay for amazing work on #OSS 00010000428 +622231380104704808336126614420000000000#072213738777# Elizabeth Martin 1121042880000429 +705Knightmetal bonus pay for amazing work on #OSS 00010000429 +622231380104231775100281017460000000000#524120301310# Emma Thompson 1121042880000430 +705Stingersprinkle bonus pay for amazing work on #OSS 00010000430 +622231380104657383270675120640000000000#126243212158# James Taylor 1121042880000431 +705Banedune bonus pay for amazing work on #OSS 00010000431 +622231380104337328532472776830000000000#722746168627# Madison Robinson 1121042880000432 +705Healerglass bonus pay for amazing work on #OSS 00010000432 +622231380104556874715072414510000000000#357316878545# Aubrey Thompson 1121042880000433 +705Eyeripple bonus pay for amazing work on #OSS 00010000433 +622231380104567467171604756760000000000#037821520827# Madison Thomas 1121042880000434 +705Swordboulder bonus pay for amazing work on #OSS 00010000434 +622231380104718605406578488700000000000#058384850863# Andrew Anderson 1121042880000435 +705Traderrift bonus pay for amazing work on #OSS 00010000435 +622231380104724386060705467610000000000#436420472150# Daniel Robinson 1121042880000436 +705Nosebristle bonus pay for amazing work on #OSS 00010000436 +622231380104667004366462185660000000000#087424704384# Olivia Taylor 1121042880000437 +705Gemstripe bonus pay for amazing work on #OSS 00010000437 +622231380104060412526342161550000000000#670333326110# Abigail Jones 1121042880000438 +705Goatfern bonus pay for amazing work on #OSS 00010000438 +622231380104571413471886704420000000000#267804567301# Michael Thompson 1121042880000439 +705Grabberisland bonus pay for amazing work on #OSS 00010000439 +622231380104827007060545216320000000000#867333211600# Emily Martinez 1121042880000440 +705Centaursmall bonus pay for amazing work on #OSS 00010000440 +622231380104322867676824321710000000000#664413824305# Avery Garcia 1121042880000441 +705Piperchain bonus pay for amazing work on #OSS 00010000441 +622231380104637763204356554880000000000#262575154828# Andrew Martinez 1121042880000442 +705Bowruby bonus pay for amazing work on #OSS 00010000442 +622231380104276845868161378370000000000#026151212143# Aiden Martin 1121042880000443 +705Stealerlapis bonus pay for amazing work on #OSS 00010000443 +622231380104678088365771657370000000000#812333335581# Emily Martin 1121042880000444 +705Salmoncopper bonus pay for amazing work on #OSS 00010000444 +622231380104663100858817351470000000000#378572804215# Liam White 1121042880000445 +705Boarcopper bonus pay for amazing work on #OSS 00010000445 +622231380104580552736285372000000000000#286703426086# Aiden Thompson 1121042880000446 +705Choppermarsh bonus pay for amazing work on #OSS 00010000446 +622231380104717726625634132350000000000#208012767820# James Miller 1121042880000447 +705Singerround bonus pay for amazing work on #OSS 00010000447 +622231380104730024110705750000000000000#620882586680# Alexander Brown 1121042880000448 +705Lightningchecker bonus pay for amazing work on #OSS 00010000448 +622231380104542550252125078070000000000#063414545022# Ava Jones 1121042880000449 +705Dukedenim bonus pay for amazing work on #OSS 00010000449 +622231380104446402368607067400000000000#267281034350# Aiden Williams 1121042880000450 +705Snapequinox bonus pay for amazing work on #OSS 00010000450 +622231380104161378275180148820000000000#731653374523# Avery Garcia 1121042880000451 +705Harefoul bonus pay for amazing work on #OSS 00010000451 +622231380104600656648183272080000000000#217103612708# Michael White 1121042880000452 +705Foepine bonus pay for amazing work on #OSS 00010000452 +622231380104271517673710685160000000000#633723257412# Addison Johnson 1121042880000453 +705Spritephase bonus pay for amazing work on #OSS 00010000453 +622231380104416136016237023580000000000#658403561210# Aubrey Taylor 1121042880000454 +705Elfplain bonus pay for amazing work on #OSS 00010000454 +622231380104586805116755033710000000000#743752886648# Emily Jackson 1121042880000455 +705Whaledull bonus pay for amazing work on #OSS 00010000455 +622231380104157281120312238720000000000#062422441086# Isabella Anderson 1121042880000456 +705Chargerfeather bonus pay for amazing work on #OSS 00010000456 +622231380104785434438081676550000000000#806164528110# Benjamin Smith 1121042880000457 +705Stonechocolate bonus pay for amazing work on #OSS 00010000457 +622231380104261743666688136730000000000#104130237446# Avery Garcia 1121042880000458 +705Ladyripple bonus pay for amazing work on #OSS 00010000458 +622231380104331003438663825750000000000#400737278880# Olivia Taylor 1121042880000459 +705Huntercookie bonus pay for amazing work on #OSS 00010000459 +622231380104554165448645801140000000000#034288836755# Elizabeth White 1121042880000460 +705Ridgegray bonus pay for amazing work on #OSS 00010000460 +622231380104078303846087416610000000000#414806057051# Sophia Johnson 1121042880000461 +705Antblossom bonus pay for amazing work on #OSS 00010000461 +622231380104654312500806810020000000000#257373537048# Michael Davis 1121042880000462 +705Iguanaember bonus pay for amazing work on #OSS 00010000462 +622231380104780346580573228630000000000#713481644731# Addison Brown 1121042880000463 +705Pantherwhite bonus pay for amazing work on #OSS 00010000463 +622231380104268063810377672300000000000#018146146547# Matthew Davis 1121042880000464 +705Capbone bonus pay for amazing work on #OSS 00010000464 +622231380104148777075607262170000000000#767273418244# Matthew Thomas 1121042880000465 +705Stingcrocus bonus pay for amazing work on #OSS 00010000465 +622231380104737741788400561750000000000#672645500883# Addison Thompson 1121042880000466 +705Hairsheer bonus pay for amazing work on #OSS 00010000466 +622231380104070818126814254010000000000#735260280431# Andrew Brown 1121042880000467 +705Eagletopaz bonus pay for amazing work on #OSS 00010000467 +622231380104766567686318222640000000000#230400714663# Matthew Johnson 1121042880000468 +705Geckoripple bonus pay for amazing work on #OSS 00010000468 +622231380104165181076778443430000000000#613206687047# Anthony Martin 1121042880000469 +705Pythonflash bonus pay for amazing work on #OSS 00010000469 +622231380104781858863343735150000000000#307304167150# Matthew Smith 1121042880000470 +705Chargeriris bonus pay for amazing work on #OSS 00010000470 +622231380104313372074616845760000000000#856757186151# Jacob Robinson 1121042880000471 +705Shakersequoia bonus pay for amazing work on #OSS 00010000471 +622231380104011121475075456430000000000#754532437140# Zoey Anderson 1121042880000472 +705Moosetree bonus pay for amazing work on #OSS 00010000472 +622231380104187273018670203850000000000#332324461668# Joshua Anderson 1121042880000473 +705Cloakeast bonus pay for amazing work on #OSS 00010000473 +622231380104316245144600332530000000000#231812314103# Emily Brown 1121042880000474 +705Mustangdaffodil bonus pay for amazing work on #OSS 00010000474 +622231380104157277284108072760000000000#578238577213# Elijah Moore 1121042880000475 +705Crownplum bonus pay for amazing work on #OSS 00010000475 +622231380104774653815633375570000000000#458580526238# Matthew Wilson 1121042880000476 +705Bitedent bonus pay for amazing work on #OSS 00010000476 +622231380104858877068445573840000000000#583065013324# Avery Jones 1121042880000477 +705Cockatoocalico bonus pay for amazing work on #OSS 00010000477 +622231380104556704852288516070000000000#648603681704# William Martin 1121042880000478 +705Eaglefringe bonus pay for amazing work on #OSS 00010000478 +622231380104036383776565274230000000000#373716530453# Daniel Smith 1121042880000479 +705Ogreautumn bonus pay for amazing work on #OSS 00010000479 +622231380104080130188434618630000000000#856312014852# Madison Thomas 1121042880000480 +705Leopardcrimson bonus pay for amazing work on #OSS 00010000480 +622231380104024232678470480740000000000#675014314133# Andrew White 1121042880000481 +705Pawdandelion bonus pay for amazing work on #OSS 00010000481 +622231380104233703421486756520000000000#033506122606# Elijah Robinson 1121042880000482 +705Runnerhail bonus pay for amazing work on #OSS 00010000482 +622231380104840766014817202730000000000#387417520212# Liam Smith 1121042880000483 +705Napecomet bonus pay for amazing work on #OSS 00010000483 +622231380104484667862250003560000000000#616632680103# Chloe Thomas 1121042880000484 +705Duckhill bonus pay for amazing work on #OSS 00010000484 +622231380104702642282358771380000000000#336285600131# Ethan Anderson 1121042880000485 +705Carpjelly bonus pay for amazing work on #OSS 00010000485 +622231380104074558817081802520000000000#742741305233# Sophia Davis 1121042880000486 +705Venombrick bonus pay for amazing work on #OSS 00010000486 +622231380104133846080606050350000000000#408814815548# Michael Taylor 1121042880000487 +705Oxpuddle bonus pay for amazing work on #OSS 00010000487 +622231380104617486567385130700000000000#132822035505# Ethan Johnson 1121042880000488 +705Playersunset bonus pay for amazing work on #OSS 00010000488 +622231380104873401282225548660000000000#580658284044# James Jones 1121042880000489 +705Kittentreasure bonus pay for amazing work on #OSS 00010000489 +622231380104727666021526031840000000000#842736142268# Chloe White 1121042880000490 +705Sharkmint bonus pay for amazing work on #OSS 00010000490 +622231380104443217171704467470000000000#877877837667# Zoey Moore 1121042880000491 +705Followerfan bonus pay for amazing work on #OSS 00010000491 +622231380104307520340040263010000000000#882234154360# Isabella Davis 1121042880000492 +705Gamblerfringe bonus pay for amazing work on #OSS 00010000492 +622231380104556313771277777750000000000#506022858777# James Jackson 1121042880000493 +705Condorclover bonus pay for amazing work on #OSS 00010000493 +622231380104435143431666440750000000000#641873433305# Anthony Thompson 1121042880000494 +705Handsticky bonus pay for amazing work on #OSS 00010000494 +622231380104850653580408358650000000000#334145130788# Alexander Williams 1121042880000495 +705Bisonrift bonus pay for amazing work on #OSS 00010000495 +622231380104160102010485666720000000000#587735315134# Aiden Miller 1121042880000496 +705Scarcat bonus pay for amazing work on #OSS 00010000496 +622231380104767365107817811780000000000#566043305162# Lily Martinez 1121042880000497 +705Kangaroocaramel bonus pay for amazing work on #OSS 00010000497 +622231380104000627034358202510000000000#051374805375# Emma Harris 1121042880000498 +705Lightningpsychadelic bonus pay for amazing work on #OSS 00010000498 +622231380104833581054330663640000000000#406443167422# Daniel Thompson 1121042880000499 +705Foxkiwi bonus pay for amazing work on #OSS 00010000499 +622231380104685472826586386260000000000#305838758140# Elijah Robinson 1121042880000500 +705Chestsplit bonus pay for amazing work on #OSS 00010000500 +622231380104323503667056386720000000000#501245627367# Ethan White 1121042880000501 +705Dogplain bonus pay for amazing work on #OSS 00010000501 +622231380104888404323106817370000000000#077650838757# Noah Jackson 1121042880000502 +705Gullyellow bonus pay for amazing work on #OSS 00010000502 +622231380104451001723547431160000000000#741452535070# Mia Wilson 1121042880000503 +705Bitesavage bonus pay for amazing work on #OSS 00010000503 +622231380104503017535628276070000000000#175286025874# Mason Thomas 1121042880000504 +705Bootcliff bonus pay for amazing work on #OSS 00010000504 +622231380104521383341784704130000000000#111631734346# Emma Davis 1121042880000505 +705Dogbattle bonus pay for amazing work on #OSS 00010000505 +622231380104414886747002453880000000000#043225706113# Isabella Martinez 1121042880000506 +705Weedhickory bonus pay for amazing work on #OSS 00010000506 +622231380104244166821720583740000000000#656756078324# Charlotte Moore 1121042880000507 +705Flasherpickle bonus pay for amazing work on #OSS 00010000507 +622231380104754161257240123120000000000#007874268760# Chloe Moore 1121042880000508 +705Eyepyrite bonus pay for amazing work on #OSS 00010000508 +622231380104857750377203862020000000000#178481366603# Chloe Harris 1121042880000509 +705Foeroot bonus pay for amazing work on #OSS 00010000509 +622231380104530720456328841860000000000#803675814617# Elijah Jackson 1121042880000510 +705Koalamire bonus pay for amazing work on #OSS 00010000510 +622231380104282251836222151540000000000#253213677831# Addison Miller 1121042880000511 +705Condorpalm bonus pay for amazing work on #OSS 00010000511 +622231380104435188654275602540000000000#805011265886# Olivia Miller 1121042880000512 +705Paintergray bonus pay for amazing work on #OSS 00010000512 +622231380104448208675543505220000000000#750278640783# Noah Moore 1121042880000513 +705Lighteriris bonus pay for amazing work on #OSS 00010000513 +622231380104482123165418765360000000000#588266558306# Daniel Wilson 1121042880000514 +705Crystalpool bonus pay for amazing work on #OSS 00010000514 +622231380104624080766014864250000000000#140148016115# Charlotte Moore 1121042880000515 +705Wandererjungle bonus pay for amazing work on #OSS 00010000515 +622231380104211858057052620320000000000#075123113725# Natalie Taylor 1121042880000516 +705Runnergem bonus pay for amazing work on #OSS 00010000516 +622231380104007162746576025540000000000#152886608620# Ella Williams 1121042880000517 +705Shriekerdark bonus pay for amazing work on #OSS 00010000517 +622231380104245580417051676720000000000#275246872887# Emily Johnson 1121042880000518 +705Birddune bonus pay for amazing work on #OSS 00010000518 +622231380104175358886046745050000000000#444570257288# Aiden Smith 1121042880000519 +705Carpberyl bonus pay for amazing work on #OSS 00010000519 +622231380104502145513786733110000000000#445572815746# David Wilson 1121042880000520 +705Jesterrowan bonus pay for amazing work on #OSS 00010000520 +622231380104052782573516517150000000000#312826024626# James Moore 1121042880000521 +705Ravermidnight bonus pay for amazing work on #OSS 00010000521 +622231380104487056047111371860000000000#414141203462# Anthony Anderson 1121042880000522 +705Pegasusmarsh bonus pay for amazing work on #OSS 00010000522 +622231380104845880647402034840000000000#680288088552# Michael White 1121042880000523 +705Jawmulberry bonus pay for amazing work on #OSS 00010000523 +622231380104703754235604466740000000000#578541752335# Alexander Brown 1121042880000524 +705Leaderband bonus pay for amazing work on #OSS 00010000524 +622231380104658010128581212260000000000#681778817265# Jacob Martin 1121042880000525 +705Banerust bonus pay for amazing work on #OSS 00010000525 +622231380104532407624805231510000000000#678340207116# Avery Davis 1121042880000526 +705Skinnernight bonus pay for amazing work on #OSS 00010000526 +622231380104777422516435283260000000000#610735761840# Benjamin Harris 1121042880000527 +705Ravenflame bonus pay for amazing work on #OSS 00010000527 +622231380104008626061700505400000000000#806587057588# Mason Martin 1121042880000528 +705Thumbink bonus pay for amazing work on #OSS 00010000528 +622231380104115270812245762450000000000#104257074630# Chloe Harris 1121042880000529 +705Pumaneon bonus pay for amazing work on #OSS 00010000529 +622231380104717384278724862300000000000#332885565381# Mia Taylor 1121042880000530 +705Owlsulpher bonus pay for amazing work on #OSS 00010000530 +622231380104236544123634188630000000000#663570101476# Avery Miller 1121042880000531 +705Swallowfierce bonus pay for amazing work on #OSS 00010000531 +622231380104833688042370286200000000000#268601646221# Elizabeth Miller 1121042880000532 +705Antshy bonus pay for amazing work on #OSS 00010000532 +622231380104046685440888770040000000000#713230637167# Charlotte Garcia 1121042880000533 +705Seerwax bonus pay for amazing work on #OSS 00010000533 +622231380104705340571586228300000000000#524067077585# Natalie White 1121042880000534 +705Pumadark bonus pay for amazing work on #OSS 00010000534 +622231380104017544547533863840000000000#775450506880# Mason Moore 1121042880000535 +705Vulturefluff bonus pay for amazing work on #OSS 00010000535 +622231380104175712752033804710000000000#165428712860# Chloe Robinson 1121042880000536 +705Fliersteel bonus pay for amazing work on #OSS 00010000536 +622231380104882742282716328130000000000#474514374008# Liam Smith 1121042880000537 +705Terrierbramble bonus pay for amazing work on #OSS 00010000537 +622231380104461083262713351560000000000#858883040838# William Harris 1121042880000538 +705Weedstellar bonus pay for amazing work on #OSS 00010000538 +622231380104114610484243073130000000000#257114763425# Natalie Martin 1121042880000539 +705Legprairie bonus pay for amazing work on #OSS 00010000539 +622231380104345681018452154030000000000#255510614761# Alexander Davis 1121042880000540 +705Cranemalachite bonus pay for amazing work on #OSS 00010000540 +622231380104240670328007540350000000000#028546234310# Liam Thomas 1121042880000541 +705Masterdaffodil bonus pay for amazing work on #OSS 00010000541 +622231380104183075663432614640000000000#865251756871# Abigail Taylor 1121042880000542 +705Roarcedar bonus pay for amazing work on #OSS 00010000542 +622231380104618770871607108070000000000#767726237114# Chloe Johnson 1121042880000543 +705Spriteviridian bonus pay for amazing work on #OSS 00010000543 +622231380104062512015740557130000000000#505426665072# Emma Miller 1121042880000544 +705Bunnybevel bonus pay for amazing work on #OSS 00010000544 +622231380104846403485771048210000000000#215661432026# Daniel Garcia 1121042880000545 +705Ogrechisel bonus pay for amazing work on #OSS 00010000545 +622231380104532357860822140430000000000#853547732048# Liam Garcia 1121042880000546 +705Serpentriver bonus pay for amazing work on #OSS 00010000546 +622231380104338765411832738610000000000#112250321848# Ethan Smith 1121042880000547 +705Crestalmond bonus pay for amazing work on #OSS 00010000547 +622231380104448150344312051210000000000#585750032708# Joseph Garcia 1121042880000548 +705Ravenflax bonus pay for amazing work on #OSS 00010000548 +622231380104035860108473718820000000000#175457623374# Matthew Jones 1121042880000549 +705Pipereast bonus pay for amazing work on #OSS 00010000549 +622231380104271336563500463870000000000#030305665864# Addison Wilson 1121042880000550 +705Voiceflax bonus pay for amazing work on #OSS 00010000550 +622231380104110868188582625460000000000#324660788403# James Anderson 1121042880000551 +705Beeflame bonus pay for amazing work on #OSS 00010000551 +622231380104564736435715205330000000000#202501006683# Ethan Martinez 1121042880000552 +705Boneebony bonus pay for amazing work on #OSS 00010000552 +622231380104283018528128230270000000000#205663464123# Chloe Brown 1121042880000553 +705Trackcookie bonus pay for amazing work on #OSS 00010000553 +622231380104742077335057715740000000000#615864840576# Sophia Johnson 1121042880000554 +705Kittenhorse bonus pay for amazing work on #OSS 00010000554 +622231380104112147425262342430000000000#664685318124# Noah Wilson 1121042880000555 +705Snappersolstice bonus pay for amazing work on #OSS 00010000555 +622231380104051804246887151500000000000#616154651273# Matthew Martinez 1121042880000556 +705Singersilver bonus pay for amazing work on #OSS 00010000556 +622231380104022433221727552000000000000#575823662152# Elizabeth Johnson 1121042880000557 +705Bonemoor bonus pay for amazing work on #OSS 00010000557 +622231380104007251638063518700000000000#632210603887# Anthony Wilson 1121042880000558 +705Cougarsolar bonus pay for amazing work on #OSS 00010000558 +622231380104740000702742611520000000000#701041567651# Jacob Anderson 1121042880000559 +705Waspspeckle bonus pay for amazing work on #OSS 00010000559 +622231380104223714811066502710000000000#341885727143# Joseph Martin 1121042880000560 +705Spurquilt bonus pay for amazing work on #OSS 00010000560 +622231380104585074455378344520000000000#874370332302# Ella Brown 1121042880000561 +705Dukesalt bonus pay for amazing work on #OSS 00010000561 +622231380104086124465385362370000000000#145455000253# Liam Martin 1121042880000562 +705Viperleather bonus pay for amazing work on #OSS 00010000562 +622231380104208361370411220360000000000#260157182018# James Martin 1121042880000563 +705Hisspattern bonus pay for amazing work on #OSS 00010000563 +622231380104832163425676040630000000000#346407016405# Noah Miller 1121042880000564 +705Bellyshade bonus pay for amazing work on #OSS 00010000564 +622231380104023267351481624220000000000#854586208771# Chloe Miller 1121042880000565 +705Glassquill bonus pay for amazing work on #OSS 00010000565 +622231380104815653742183228780000000000#262116380577# Avery Jones 1121042880000566 +705Dartchrome bonus pay for amazing work on #OSS 00010000566 +622231380104182231006563581050000000000#672726431776# Matthew Thompson 1121042880000567 +705Batmaple bonus pay for amazing work on #OSS 00010000567 +622231380104512746716626136460000000000#620604053255# Aubrey Jackson 1121042880000568 +705Lynxrapid bonus pay for amazing work on #OSS 00010000568 +622231380104040710428150207660000000000#178621075024# Liam Thompson 1121042880000569 +705Kangaroobead bonus pay for amazing work on #OSS 00010000569 +622231380104331606546372040060000000000#430216017652# David Davis 1121042880000570 +705Browruby bonus pay for amazing work on #OSS 00010000570 +622231380104183641077660341030000000000#826473803872# Joseph Garcia 1121042880000571 +705Dartscarlet bonus pay for amazing work on #OSS 00010000571 +622231380104351084448703028450000000000#863486133755# James Robinson 1121042880000572 +705Pawsharp bonus pay for amazing work on #OSS 00010000572 +622231380104754666101687736870000000000#601466665745# Matthew Moore 1121042880000573 +705Pantherfrill bonus pay for amazing work on #OSS 00010000573 +622231380104101647061522510650000000000#100423205412# Elizabeth Wilson 1121042880000574 +705Stormvivid bonus pay for amazing work on #OSS 00010000574 +622231380104228272408153802720000000000#285665800080# Elijah White 1121042880000575 +705Shrieksouth bonus pay for amazing work on #OSS 00010000575 +622231380104254687333012063410000000000#355741254803# Liam Brown 1121042880000576 +705Eatermirage bonus pay for amazing work on #OSS 00010000576 +622231380104242166765252684350000000000#868581561283# Joshua Moore 1121042880000577 +705Fingeriris bonus pay for amazing work on #OSS 00010000577 +622231380104523720666271334460000000000#871430123601# Madison White 1121042880000578 +705Markquick bonus pay for amazing work on #OSS 00010000578 +622231380104473323808085168750000000000#110048634148# Avery White 1121042880000579 +705Scorpionsand bonus pay for amazing work on #OSS 00010000579 +622231380104303364887484604820000000000#234071753710# Jacob Robinson 1121042880000580 +705Witchfluff bonus pay for amazing work on #OSS 00010000580 +622231380104134613864127283320000000000#527031273356# Jayden White 1121042880000581 +705Tigerlaser bonus pay for amazing work on #OSS 00010000581 +622231380104207340352353227380000000000#701770085233# Emily Martin 1121042880000582 +705Ogrenight bonus pay for amazing work on #OSS 00010000582 +622231380104117811742245643630000000000#064832864203# Anthony Williams 1121042880000583 +705Thornforest bonus pay for amazing work on #OSS 00010000583 +622231380104482241673470463080000000000#842843121137# Sophia Moore 1121042880000584 +705Boatree bonus pay for amazing work on #OSS 00010000584 +622231380104827661081483507650000000000#514423356631# Natalie Wilson 1121042880000585 +705Lordcrocus bonus pay for amazing work on #OSS 00010000585 +622231380104216746706787808360000000000#720548440203# Joshua Robinson 1121042880000586 +705Sharkcoffee bonus pay for amazing work on #OSS 00010000586 +622231380104322216805882684580000000000#623383250830# Charlotte Robinson 1121042880000587 +705Shakerluminous bonus pay for amazing work on #OSS 00010000587 +622231380104825072258582852700000000000#444205784324# Ethan Johnson 1121042880000588 +705Kingchisel bonus pay for amazing work on #OSS 00010000588 +622231380104571784462548530610000000000#686888827174# Anthony White 1121042880000589 +705Sightmercury bonus pay for amazing work on #OSS 00010000589 +622231380104088730754875213170000000000#525503885446# James Wilson 1121042880000590 +705Stalkercrimson bonus pay for amazing work on #OSS 00010000590 +622231380104526028747547426440000000000#232768740814# Avery Martinez 1121042880000591 +705Gamblercedar bonus pay for amazing work on #OSS 00010000591 +622231380104045028584545602250000000000#774507020188# Elijah Johnson 1121042880000592 +705Warlockclever bonus pay for amazing work on #OSS 00010000592 +622231380104460738658501306120000000000#364656455830# Aiden Robinson 1121042880000593 +705Mythboom bonus pay for amazing work on #OSS 00010000593 +622231380104124332738578622040000000000#256388188247# Madison Harris 1121042880000594 +705Thunderballistic bonus pay for amazing work on #OSS 00010000594 +622231380104544733375855718160000000000#172170527170# Michael Moore 1121042880000595 +705Fishfuschia bonus pay for amazing work on #OSS 00010000595 +622231380104066005561404338210000000000#581357641700# Addison Martinez 1121042880000596 +705Biterblue bonus pay for amazing work on #OSS 00010000596 +622231380104883008438058615150000000000#872467401676# David Wilson 1121042880000597 +705Hawkmad bonus pay for amazing work on #OSS 00010000597 +622231380104253843261773881050000000000#377421121582# Avery Jones 1121042880000598 +705Wizardfancy bonus pay for amazing work on #OSS 00010000598 +622231380104574414055013731880000000000#468706270643# Addison Jones 1121042880000599 +705Spikeskitter bonus pay for amazing work on #OSS 00010000599 +622231380104447741032822870640000000000#763704061313# Ava Robinson 1121042880000600 +705Collarvenom bonus pay for amazing work on #OSS 00010000600 +622231380104203188553775265820000000000#464540144614# Jayden Johnson 1121042880000601 +705Stoneglimmer bonus pay for amazing work on #OSS 00010000601 +622231380104035053456582353350000000000#772017041652# Anthony Anderson 1121042880000602 +705Samuraiswamp bonus pay for amazing work on #OSS 00010000602 +622231380104730341876034033470000000000#605864722264# Ethan Taylor 1121042880000603 +705Flierrowan bonus pay for amazing work on #OSS 00010000603 +622231380104712861107502556610000000000#333875240583# Isabella Williams 1121042880000604 +705Fangpewter bonus pay for amazing work on #OSS 00010000604 +622231380104152703871436671560000000000#115874882381# Alexander White 1121042880000605 +705Burnchrome bonus pay for amazing work on #OSS 00010000605 +622231380104407018671582538230000000000#647655834037# Jayden Garcia 1121042880000606 +705Scorpionthread bonus pay for amazing work on #OSS 00010000606 +622231380104043774367068543310000000000#765754133623# Jacob Martinez 1121042880000607 +705Flashermetal bonus pay for amazing work on #OSS 00010000607 +622231380104604467507034324080000000000#684676117678# Noah Jones 1121042880000608 +705Pawbow bonus pay for amazing work on #OSS 00010000608 +622231380104760875014571483030000000000#152324533173# Avery Moore 1121042880000609 +705Heronalabaster bonus pay for amazing work on #OSS 00010000609 +622231380104104460706712157320000000000#858011884253# Matthew Robinson 1121042880000610 +705Ripperwheat bonus pay for amazing work on #OSS 00010000610 +622231380104736846433702063620000000000#847416642471# Michael Smith 1121042880000611 +705Backclover bonus pay for amazing work on #OSS 00010000611 +622231380104834654284303682260000000000#688655607334# Addison Harris 1121042880000612 +705Lanternmotley bonus pay for amazing work on #OSS 00010000612 +622231380104217853553506534430000000000#724245773716# Matthew Taylor 1121042880000613 +705Soarershy bonus pay for amazing work on #OSS 00010000613 +622231380104287602145158487870000000000#385621252626# Mia White 1121042880000614 +705Volecotton bonus pay for amazing work on #OSS 00010000614 +622231380104437524575661578040000000000#164732037607# Joseph Johnson 1121042880000615 +705Guardianmeadow bonus pay for amazing work on #OSS 00010000615 +622231380104277210243564427630000000000#416715226230# Isabella Jones 1121042880000616 +705Markcherry bonus pay for amazing work on #OSS 00010000616 +622231380104812224082107214580000000000#458537367303# Chloe Wilson 1121042880000617 +705Wingriver bonus pay for amazing work on #OSS 00010000617 +622231380104373106622364223560000000000#381644735735# Joseph Brown 1121042880000618 +705Kickerboulder bonus pay for amazing work on #OSS 00010000618 +622231380104043772427874553210000000000#828718127857# William Moore 1121042880000619 +705Stallionripple bonus pay for amazing work on #OSS 00010000619 +622231380104266614340281868670000000000#352850055565# Alexander Miller 1121042880000620 +705Flierbrass bonus pay for amazing work on #OSS 00010000620 +622231380104453420847866184040000000000#200157543757# Benjamin Jones 1121042880000621 +705Hunterdaisy bonus pay for amazing work on #OSS 00010000621 +622231380104258368184638226770000000000#673562614001# Aiden Harris 1121042880000622 +705Skinnerwind bonus pay for amazing work on #OSS 00010000622 +622231380104885472047185573380000000000#455503137478# Matthew Brown 1121042880000623 +705Shoulderluminous bonus pay for amazing work on #OSS 00010000623 +622231380104267232576102466340000000000#534513352577# Madison Wilson 1121042880000624 +705Friendjet bonus pay for amazing work on #OSS 00010000624 +622231380104334763375231007600000000000#333733172513# Sofia Williams 1121042880000625 +705Slothvenom bonus pay for amazing work on #OSS 00010000625 +622231380104251342065375286660000000000#882841302738# Sophia Miller 1121042880000626 +705Walkersilk bonus pay for amazing work on #OSS 00010000626 +622231380104204230116504686870000000000#535678760546# Mason Anderson 1121042880000627 +705Volerainbow bonus pay for amazing work on #OSS 00010000627 +622231380104202678031336717670000000000#018355307673# Joseph Smith 1121042880000628 +705Paladinnight bonus pay for amazing work on #OSS 00010000628 +622231380104572105186113656460000000000#133413317380# Avery Moore 1121042880000629 +705Warlockbevel bonus pay for amazing work on #OSS 00010000629 +622231380104810143730780557580000000000#510000030124# Joseph Garcia 1121042880000630 +705Mustangthunder bonus pay for amazing work on #OSS 00010000630 +622231380104480325584226628320000000000#507505578441# Mason Jackson 1121042880000631 +705Elkflannel bonus pay for amazing work on #OSS 00010000631 +622231380104711370408345757530000000000#551414436587# Ella Smith 1121042880000632 +705Questerpebble bonus pay for amazing work on #OSS 00010000632 +622231380104410646285257541710000000000#846124617664# Ethan Davis 1121042880000633 +705Armnorth bonus pay for amazing work on #OSS 00010000633 +622231380104171248808452526260000000000#568211410311# Emma Williams 1121042880000634 +705Pantherwave bonus pay for amazing work on #OSS 00010000634 +622231380104437311586703754460000000000#481271051111# Ella Jones 1121042880000635 +705Dogseed bonus pay for amazing work on #OSS 00010000635 +622231380104035660644354085550000000000#714726666372# Ava Harris 1121042880000636 +705Sparrowholy bonus pay for amazing work on #OSS 00010000636 +622231380104784541600106368610000000000#085785807130# Joshua Brown 1121042880000637 +705Swoopglory bonus pay for amazing work on #OSS 00010000637 +622231380104461078068303180470000000000#668320770323# Joshua Thompson 1121042880000638 +705Bearzircon bonus pay for amazing work on #OSS 00010000638 +622231380104227022371315036250000000000#183105152431# Elijah Miller 1121042880000639 +705Carpemerald bonus pay for amazing work on #OSS 00010000639 +622231380104723114124467354620000000000#380020566522# Benjamin Martinez 1121042880000640 +705Mythmesquite bonus pay for amazing work on #OSS 00010000640 +622231380104040278340247443100000000000#847657522730# Zoey Wilson 1121042880000641 +705Llamascythe bonus pay for amazing work on #OSS 00010000641 +622231380104513787554760670000000000000#660444277066# Aiden Smith 1121042880000642 +705Flamecrazy bonus pay for amazing work on #OSS 00010000642 +622231380104445125055603518220000000000#407780331622# Jacob White 1121042880000643 +705Dutchesssky bonus pay for amazing work on #OSS 00010000643 +622231380104346264728141085750000000000#630570443264# Avery Harris 1121042880000644 +705Reapergem bonus pay for amazing work on #OSS 00010000644 +622231380104684452868357877020000000000#238535812381# Sophia Jackson 1121042880000645 +705Razorwave bonus pay for amazing work on #OSS 00010000645 +622231380104005754220588030280000000000#628040606158# Charlotte Johnson 1121042880000646 +705Scribesplash bonus pay for amazing work on #OSS 00010000646 +622231380104535631272147254370000000000#032834475242# Jacob Williams 1121042880000647 +705Bunnyink bonus pay for amazing work on #OSS 00010000647 +622231380104061773282632782580000000000#160436540066# Sophia Williams 1121042880000648 +705Followerwave bonus pay for amazing work on #OSS 00010000648 +622231380104851566760406560730000000000#572545244224# Joshua Garcia 1121042880000649 +705Hornsilver bonus pay for amazing work on #OSS 00010000649 +622231380104521148410105864620000000000#631007162086# Andrew Anderson 1121042880000650 +705Terrierlightning bonus pay for amazing work on #OSS 00010000650 +622231380104220746271505650510000000000#523558405746# Aiden Jackson 1121042880000651 +705Mindfog bonus pay for amazing work on #OSS 00010000651 +622231380104813383447583812370000000000#138738676305# Mia Moore 1121042880000652 +705Stonebrass bonus pay for amazing work on #OSS 00010000652 +622231380104187588200406343780000000000#433351513463# Anthony Davis 1121042880000653 +705Shriekerballistic bonus pay for amazing work on #OSS 00010000653 +622231380104242688706876002540000000000#737806601621# Abigail White 1121042880000654 +705Bearcandle bonus pay for amazing work on #OSS 00010000654 +622231380104054013711012053810000000000#783562361326# Addison Moore 1121042880000655 +705Wizardcookie bonus pay for amazing work on #OSS 00010000655 +622231380104805130427470827240000000000#822001850668# Ella Jones 1121042880000656 +705Goosescratch bonus pay for amazing work on #OSS 00010000656 +622231380104236522173515648550000000000#166826346817# Liam Moore 1121042880000657 +705Bearglitter bonus pay for amazing work on #OSS 00010000657 +622231380104531143426481878430000000000#748623607440# Elijah Johnson 1121042880000658 +705Toucanpuddle bonus pay for amazing work on #OSS 00010000658 +622231380104464256418346462850000000000#440742253363# Elijah White 1121042880000659 +705Hisseroasis bonus pay for amazing work on #OSS 00010000659 +622231380104676776344876856740000000000#088853121418# Joshua Smith 1121042880000660 +705Cloakgentle bonus pay for amazing work on #OSS 00010000660 +622231380104347240230681423680000000000#461305484244# Aubrey Thompson 1121042880000661 +705Vultureplanet bonus pay for amazing work on #OSS 00010000661 +622231380104813601072483877440000000000#230267228203# Isabella Davis 1121042880000662 +705Roachsatin bonus pay for amazing work on #OSS 00010000662 +622231380104343738050466484850000000000#020108204312# Emma Smith 1121042880000663 +705Coyotegiant bonus pay for amazing work on #OSS 00010000663 +622231380104355521316417703210000000000#702836722038# Aiden Moore 1121042880000664 +705Rayspot bonus pay for amazing work on #OSS 00010000664 +622231380104083466586730425570000000000#143532632548# Daniel Johnson 1121042880000665 +705Scorpionchecker bonus pay for amazing work on #OSS 00010000665 +622231380104184730041826100300000000000#057764586075# Alexander Jones 1121042880000666 +705Hornhail bonus pay for amazing work on #OSS 00010000666 +622231380104582438821526821200000000000#472062053767# Olivia Martinez 1121042880000667 +705Bonepale bonus pay for amazing work on #OSS 00010000667 +622231380104562671353654280880000000000#400177112618# Addison Garcia 1121042880000668 +705Oriolebold bonus pay for amazing work on #OSS 00010000668 +622231380104847341325255363020000000000#114651257714# Aiden Brown 1121042880000669 +705Riderswamp bonus pay for amazing work on #OSS 00010000669 +622231380104153557865274236340000000000#588426871886# Alexander White 1121042880000670 +705Vulturewax bonus pay for amazing work on #OSS 00010000670 +622231380104616440811152688250000000000#214375307243# Jayden Thompson 1121042880000671 +705Riderspring bonus pay for amazing work on #OSS 00010000671 +622231380104407260665654768210000000000#060887351018# Michael Brown 1121042880000672 +705Condortrail bonus pay for amazing work on #OSS 00010000672 +622231380104404304405276282130000000000#823367178578# Olivia White 1121042880000673 +705Razorshade bonus pay for amazing work on #OSS 00010000673 +622231380104386172471523888030000000000#346778086587# Matthew Robinson 1121042880000674 +705Spearlong bonus pay for amazing work on #OSS 00010000674 +622231380104644137814247704210000000000#164366555725# Chloe Thomas 1121042880000675 +705Droppie bonus pay for amazing work on #OSS 00010000675 +622231380104436888466282732080000000000#678520543140# Andrew Davis 1121042880000676 +705Bunnynova bonus pay for amazing work on #OSS 00010000676 +622231380104668117785040267860000000000#046601313808# Avery Thomas 1121042880000677 +705Fisherwave bonus pay for amazing work on #OSS 00010000677 +622231380104347528880281146380000000000#404632447448# Elizabeth Martin 1121042880000678 +705Orioleleaf bonus pay for amazing work on #OSS 00010000678 +622231380104365685308151762320000000000#773617172888# Joseph Miller 1121042880000679 +705Turnermercury bonus pay for amazing work on #OSS 00010000679 +622231380104025606146452511530000000000#041566031825# Emma Wilson 1121042880000680 +705Jaguarspring bonus pay for amazing work on #OSS 00010000680 +622231380104243806773537416700000000000#643035628003# Abigail Jones 1121042880000681 +705Doomshallow bonus pay for amazing work on #OSS 00010000681 +622231380104466615766326054320000000000#725641167673# David Thompson 1121042880000682 +705Warlockpaper bonus pay for amazing work on #OSS 00010000682 +622231380104026406307564528740000000000#815503135518# Anthony Smith 1121042880000683 +705Lasherash bonus pay for amazing work on #OSS 00010000683 +622231380104868371838881205480000000000#605265778853# Ethan Johnson 1121042880000684 +705Palmgreen bonus pay for amazing work on #OSS 00010000684 +622231380104174677034550566860000000000#066458743866# Ethan Smith 1121042880000685 +705Gorillairon bonus pay for amazing work on #OSS 00010000685 +622231380104306680384556737760000000000#752644506772# Anthony Garcia 1121042880000686 +705Songflint bonus pay for amazing work on #OSS 00010000686 +622231380104021821480073263440000000000#652231117241# Addison Moore 1121042880000687 +705Watchermarble bonus pay for amazing work on #OSS 00010000687 +622231380104420135442752467010000000000#746671818850# Madison Smith 1121042880000688 +705Knifecharm bonus pay for amazing work on #OSS 00010000688 +622231380104671315220314517860000000000#580267581654# Aiden Martinez 1121042880000689 +705Ribpale bonus pay for amazing work on #OSS 00010000689 +622231380104778567778575332260000000000#160832824143# Ella Jones 1121042880000690 +705Watcherwax bonus pay for amazing work on #OSS 00010000690 +622231380104167304587551126720000000000#502448213557# Avery Martin 1121042880000691 +705Choppersheer bonus pay for amazing work on #OSS 00010000691 +622231380104168884257038878450000000000#512141748013# Alexander Thompson 1121042880000692 +705Fighterperidot bonus pay for amazing work on #OSS 00010000692 +622231380104620440160055507200000000000#828315775315# Michael Garcia 1121042880000693 +705Salmonclear bonus pay for amazing work on #OSS 00010000693 +622231380104653188882168471030000000000#231313540080# Liam Wilson 1121042880000694 +705Razorcoal bonus pay for amazing work on #OSS 00010000694 +622231380104123623467840317200000000000#544018155608# Alexander Johnson 1121042880000695 +705Kickerpalm bonus pay for amazing work on #OSS 00010000695 +622231380104343581061781648380000000000#521432024148# Michael Williams 1121042880000696 +705Coyotepine bonus pay for amazing work on #OSS 00010000696 +622231380104365328501060732180000000000#380778227786# Emily Thompson 1121042880000697 +705Slavemire bonus pay for amazing work on #OSS 00010000697 +622231380104052761138653680380000000000#233046000186# Addison Garcia 1121042880000698 +705Geckorelic bonus pay for amazing work on #OSS 00010000698 +622231380104380145233775728340000000000#706788055416# Mia White 1121042880000699 +705Swishersilent bonus pay for amazing work on #OSS 00010000699 +622231380104106614112208100220000000000#518731636322# Ava Robinson 1121042880000700 +705Throatrune bonus pay for amazing work on #OSS 00010000700 +622231380104375554511368224550000000000#817683245558# Ava Miller 1121042880000701 +705Bitershy bonus pay for amazing work on #OSS 00010000701 +622231380104547007870518536800000000000#280308148510# Mia Martinez 1121042880000702 +705Swoopskitter bonus pay for amazing work on #OSS 00010000702 +622231380104118535215275305850000000000#686488724816# Elijah Smith 1121042880000703 +705Weedbasalt bonus pay for amazing work on #OSS 00010000703 +622231380104417045376366462840000000000#644128132115# Madison Miller 1121042880000704 +705Swishervolcano bonus pay for amazing work on #OSS 00010000704 +622231380104438276564768201360000000000#426477318287# Aiden Smith 1121042880000705 +705Healerpsychadelic bonus pay for amazing work on #OSS 00010000705 +622231380104680367064060587320000000000#631507813426# Sophia Wilson 1121042880000706 +705Lifterwild bonus pay for amazing work on #OSS 00010000706 +622231380104217712082676444370000000000#040653267150# Emma Taylor 1121042880000707 +705Soarerpale bonus pay for amazing work on #OSS 00010000707 +622231380104667136551654404710000000000#555880641750# Alexander Jones 1121042880000708 +705Nosemuck bonus pay for amazing work on #OSS 00010000708 +622231380104164275067612812320000000000#554081812716# Alexander White 1121042880000709 +705Legendazure bonus pay for amazing work on #OSS 00010000709 +622231380104704572370768871400000000000#744863571507# Isabella Garcia 1121042880000710 +705Viperhoney bonus pay for amazing work on #OSS 00010000710 +622231380104624548517543437610000000000#442450675707# Olivia Martinez 1121042880000711 +705Jesterlie bonus pay for amazing work on #OSS 00010000711 +622231380104277181706321700270000000000#118345563015# David Brown 1121042880000712 +705Stalkernickel bonus pay for amazing work on #OSS 00010000712 +622231380104473124724554727620000000000#204173506071# Isabella Thomas 1121042880000713 +705Yakmetal bonus pay for amazing work on #OSS 00010000713 +622231380104162564157256267260000000000#008080415605# Andrew Thompson 1121042880000714 +705Healermeadow bonus pay for amazing work on #OSS 00010000714 +622231380104474162451702360230000000000#258155263042# Matthew Taylor 1121042880000715 +705Pumamesquite bonus pay for amazing work on #OSS 00010000715 +622231380104347437236232050620000000000#335270273113# Abigail Taylor 1121042880000716 +705Oriolepebble bonus pay for amazing work on #OSS 00010000716 +622231380104717284573228020220000000000#817354723173# Mia Garcia 1121042880000717 +705Doggarnet bonus pay for amazing work on #OSS 00010000717 +622231380104730275086051106270000000000#551748111861# Elijah Martin 1121042880000718 +705Lanternscythe bonus pay for amazing work on #OSS 00010000718 +622231380104470802387044725260000000000#585566708335# Mia Smith 1121042880000719 +705Horseruby bonus pay for amazing work on #OSS 00010000719 +622231380104616201158217188760000000000#613640865866# Noah Brown 1121042880000720 +705Jesterzenith bonus pay for amazing work on #OSS 00010000720 +622231380104232430546571488340000000000#518052806111# Jacob Jackson 1121042880000721 +705Scaledandy bonus pay for amazing work on #OSS 00010000721 +622231380104631038628835766130000000000#483654226623# Joseph Brown 1121042880000722 +705Healerblack bonus pay for amazing work on #OSS 00010000722 +622231380104813335275736873280000000000#363404835347# Andrew Martinez 1121042880000723 +705Minnowpatch bonus pay for amazing work on #OSS 00010000723 +622231380104622816565156840120000000000#588460553745# Noah Moore 1121042880000724 +705Pythondust bonus pay for amazing work on #OSS 00010000724 +622231380104888544410315818270000000000#154043662456# Joseph White 1121042880000725 +705Bardagate bonus pay for amazing work on #OSS 00010000725 +622231380104754458637127710850000000000#881083361023# Joshua Thompson 1121042880000726 +705Tonguechocolate bonus pay for amazing work on #OSS 00010000726 +622231380104587524144786504830000000000#221841102814# James Taylor 1121042880000727 +705Braidstorm bonus pay for amazing work on #OSS 00010000727 +622231380104701145605823107740000000000#462885326802# Daniel Jones 1121042880000728 +705Devourerforest bonus pay for amazing work on #OSS 00010000728 +622231380104202103322714675050000000000#654776637840# Liam Wilson 1121042880000729 +705Fliergarnet bonus pay for amazing work on #OSS 00010000729 +622231380104685022720025713150000000000#471737080448# Charlotte Brown 1121042880000730 +705Mistressgranite bonus pay for amazing work on #OSS 00010000730 +622231380104674238031267131810000000000#160643242822# William Robinson 1121042880000731 +705Anteloperainbow bonus pay for amazing work on #OSS 00010000731 +622231380104176048386777657000000000000#203837402386# Sophia Jackson 1121042880000732 +705Mistressnettle bonus pay for amazing work on #OSS 00010000732 +622231380104815386112346553150000000000#462787763784# Ava Martinez 1121042880000733 +705Pawboom bonus pay for amazing work on #OSS 00010000733 +622231380104420117868830674720000000000#366448852684# Avery Thompson 1121042880000734 +705Pegasustiny bonus pay for amazing work on #OSS 00010000734 +622231380104525564342620867050000000000#811424174622# Emma Harris 1121042880000735 +705Deathskitter bonus pay for amazing work on #OSS 00010000735 +622231380104878648130825884410000000000#710064760532# Ava White 1121042880000736 +705Kangaroopuzzle bonus pay for amazing work on #OSS 00010000736 +622231380104164030064263604110000000000#845612801401# Aubrey Martinez 1121042880000737 +705Singernight bonus pay for amazing work on #OSS 00010000737 +622231380104228805866108056160000000000#823800567483# Sofia Davis 1121042880000738 +705Chargervaliant bonus pay for amazing work on #OSS 00010000738 +622231380104673513271567071580000000000#283266065408# Natalie Thompson 1121042880000739 +705Takercookie bonus pay for amazing work on #OSS 00010000739 +622231380104605716007602610650000000000#647573054276# Avery Davis 1121042880000740 +705Condorsnapdragon bonus pay for amazing work on #OSS 00010000740 +622231380104826480142281043210000000000#245253328266# Abigail Jones 1121042880000741 +705Seekermica bonus pay for amazing work on #OSS 00010000741 +622231380104253486305541150630000000000#532708837764# Addison Garcia 1121042880000742 +705Fairyboulder bonus pay for amazing work on #OSS 00010000742 +622231380104008630850138753860000000000#114303541544# Ella Thompson 1121042880000743 +705Gullfeather bonus pay for amazing work on #OSS 00010000743 +622231380104728431528478263170000000000#830608274235# Joshua White 1121042880000744 +705Tigerripple bonus pay for amazing work on #OSS 00010000744 +622231380104478575483425282510000000000#508746057303# Addison Anderson 1121042880000745 +705Soarermagenta bonus pay for amazing work on #OSS 00010000745 +622231380104483071461685140450000000000#308806763255# Elijah Williams 1121042880000746 +705Sightvine bonus pay for amazing work on #OSS 00010000746 +622231380104740863288611711320000000000#865804878580# Ella Jones 1121042880000747 +705Braidlake bonus pay for amazing work on #OSS 00010000747 +622231380104566481468620365560000000000#400183667005# Joseph Smith 1121042880000748 +705Markdandelion bonus pay for amazing work on #OSS 00010000748 +622231380104242880470434135140000000000#353212704317# Alexander Moore 1121042880000749 +705Chillginger bonus pay for amazing work on #OSS 00010000749 +622231380104242184506404760120000000000#861686018586# James Robinson 1121042880000750 +705Diverlapis bonus pay for amazing work on #OSS 00010000750 +622231380104757860668680500630000000000#058584406317# Sophia Taylor 1121042880000751 +705Cougaragate bonus pay for amazing work on #OSS 00010000751 +622231380104486355771653618230000000000#273024831220# Alexander Thomas 1121042880000752 +705Princessglaze bonus pay for amazing work on #OSS 00010000752 +622231380104524645547414161730000000000#304767611115# Emily Anderson 1121042880000753 +705Sagemisty bonus pay for amazing work on #OSS 00010000753 +622231380104332165508381403820000000000#777850132334# Alexander Garcia 1121042880000754 +705Legharvest bonus pay for amazing work on #OSS 00010000754 +622231380104601206770114367870000000000#618652770857# Mia Thomas 1121042880000755 +705Kittenkeen bonus pay for amazing work on #OSS 00010000755 +622231380104776185045146863860000000000#805353764757# Madison Williams 1121042880000756 +705Slavemud bonus pay for amazing work on #OSS 00010000756 +622231380104540322485367208010000000000#514454607844# Aiden Davis 1121042880000757 +705Paintercherry bonus pay for amazing work on #OSS 00010000757 +622231380104242248860570012830000000000#080332425246# Benjamin Robinson 1121042880000758 +705Flashertree bonus pay for amazing work on #OSS 00010000758 +622231380104367843000000533520000000000#857343338315# Olivia Martin 1121042880000759 +705Wolverinedot bonus pay for amazing work on #OSS 00010000759 +622231380104106514584707480760000000000#611588727667# Daniel Smith 1121042880000760 +705Llamazircon bonus pay for amazing work on #OSS 00010000760 +622231380104866201735875000480000000000#346070688273# William Martinez 1121042880000761 +705Browzest bonus pay for amazing work on #OSS 00010000761 +622231380104242341276157178530000000000#486506053335# James White 1121042880000762 +705Carverbone bonus pay for amazing work on #OSS 00010000762 +622231380104022301155008742120000000000#356285812118# Avery Taylor 1121042880000763 +705Handblaze bonus pay for amazing work on #OSS 00010000763 +622231380104160118062465636170000000000#771183086327# Noah Thompson 1121042880000764 +705Hairchecker bonus pay for amazing work on #OSS 00010000764 +622231380104861816105181363610000000000#676178133830# Zoey Brown 1121042880000765 +705Ladyfluff bonus pay for amazing work on #OSS 00010000765 +622231380104678850622447326840000000000#866072577226# Aubrey Anderson 1121042880000766 +705Carverfancy bonus pay for amazing work on #OSS 00010000766 +622231380104867654643556425010000000000#646785301541# Elijah Martin 1121042880000767 +705Bellytitanium bonus pay for amazing work on #OSS 00010000767 +622231380104838865208220411580000000000#280537235257# Isabella Brown 1121042880000768 +705Roarermirage bonus pay for amazing work on #OSS 00010000768 +622231380104750002284618128270000000000#724512516886# Charlotte Johnson 1121042880000769 +705Fanggranite bonus pay for amazing work on #OSS 00010000769 +622231380104873306633842134720000000000#784113202130# William Garcia 1121042880000770 +705Frightpink bonus pay for amazing work on #OSS 00010000770 +622231380104581522773088063180000000000#482212204345# Addison Harris 1121042880000771 +705Legsred bonus pay for amazing work on #OSS 00010000771 +622231380104831354825674122870000000000#687267556688# Matthew Thomas 1121042880000772 +705Howlerolive bonus pay for amazing work on #OSS 00010000772 +622231380104382642552882747870000000000#274861721811# Emily Smith 1121042880000773 +705Griffinhurricane bonus pay for amazing work on #OSS 00010000773 +622231380104286776805476762220000000000#137222754704# Alexander White 1121042880000774 +705Rayjade bonus pay for amazing work on #OSS 00010000774 +622231380104707834846762521760000000000#348187588472# Aiden Davis 1121042880000775 +705Carpetdestiny bonus pay for amazing work on #OSS 00010000775 +622231380104805277663414505240000000000#011064417286# David Williams 1121042880000776 +705Whimseydull bonus pay for amazing work on #OSS 00010000776 +622231380104546248110328251300000000000#424842242407# Jacob Jones 1121042880000777 +705Falconpollen bonus pay for amazing work on #OSS 00010000777 +622231380104610080187267213030000000000#653045278120# Lily Robinson 1121042880000778 +705Followerbronze bonus pay for amazing work on #OSS 00010000778 +622231380104085221366253056140000000000#018873831542# Joshua Harris 1121042880000779 +705Stormgrove bonus pay for amazing work on #OSS 00010000779 +622231380104772312342816736220000000000#508637371078# Emma Garcia 1121042880000780 +705Chestnebula bonus pay for amazing work on #OSS 00010000780 +622231380104857285668814206430000000000#246371763617# Emily Miller 1121042880000781 +705Legendchain bonus pay for amazing work on #OSS 00010000781 +622231380104836474337883286150000000000#702440575315# Andrew Moore 1121042880000782 +705Pumatitanium bonus pay for amazing work on #OSS 00010000782 +622231380104513832764673403850000000000#017380465111# Charlotte Smith 1121042880000783 +705Crusherwell bonus pay for amazing work on #OSS 00010000783 +622231380104506533822687538410000000000#703535278212# Alexander Thomas 1121042880000784 +705Whalegranite bonus pay for amazing work on #OSS 00010000784 +622231380104032748601541742880000000000#804404607530# Olivia Wilson 1121042880000785 +705Spritefantasy bonus pay for amazing work on #OSS 00010000785 +622231380104366786066313305540000000000#882602474214# Avery Jones 1121042880000786 +705Fangplump bonus pay for amazing work on #OSS 00010000786 +622231380104781674023271734310000000000#831146566736# Chloe Martinez 1121042880000787 +705Hyenacarnation bonus pay for amazing work on #OSS 00010000787 +622231380104301800236050388650000000000#771488334083# Jacob Moore 1121042880000788 +705Lifterdew bonus pay for amazing work on #OSS 00010000788 +622231380104732416327026740510000000000#830501332315# Andrew Taylor 1121042880000789 +705Trackcyber bonus pay for amazing work on #OSS 00010000789 +622231380104317585552140152640000000000#211227674001# Aubrey Miller 1121042880000790 +705Kickerazure bonus pay for amazing work on #OSS 00010000790 +622231380104345100267161614110000000000#240786634682# Liam Jackson 1121042880000791 +705Scribegiant bonus pay for amazing work on #OSS 00010000791 +622231380104808625643210674720000000000#174233300481# Sophia Moore 1121042880000792 +705Antlerpeach bonus pay for amazing work on #OSS 00010000792 +622231380104437061327752887560000000000#087487133650# Emily White 1121042880000793 +705Soarermad bonus pay for amazing work on #OSS 00010000793 +622231380104352253824318412380000000000#141067865205# Zoey Jackson 1121042880000794 +705Snakeblack bonus pay for amazing work on #OSS 00010000794 +622231380104130710584325268480000000000#414604246388# James Garcia 1121042880000795 +705Gamblercitrine bonus pay for amazing work on #OSS 00010000795 +622231380104807274462832652480000000000#486217231728# Daniel Miller 1121042880000796 +705Toothcream bonus pay for amazing work on #OSS 00010000796 +622231380104644714154563163350000000000#721733360136# Daniel Thomas 1121042880000797 +705Scowlglen bonus pay for amazing work on #OSS 00010000797 +622231380104288033550655771460000000000#527274423344# Alexander Johnson 1121042880000798 +705Chopperwax bonus pay for amazing work on #OSS 00010000798 +622231380104434302034605102080000000000#122776036780# Ethan Martinez 1121042880000799 +705Trackercandle bonus pay for amazing work on #OSS 00010000799 +622231380104870685765880685360000000000#407315437235# Zoey Thomas 1121042880000800 +705Elkhollow bonus pay for amazing work on #OSS 00010000800 +622231380104833171661601216020000000000#028467560836# Madison Davis 1121042880000801 +705Mooseclover bonus pay for amazing work on #OSS 00010000801 +622231380104344760576175860330000000000#847046035310# Benjamin Johnson 1121042880000802 +705Kickercake bonus pay for amazing work on #OSS 00010000802 +622231380104274500870135347730000000000#110363814288# James Moore 1121042880000803 +705Jawjasper bonus pay for amazing work on #OSS 00010000803 +622231380104660608513253820530000000000#285622101013# Chloe White 1121042880000804 +705Sentryrelic bonus pay for amazing work on #OSS 00010000804 +622231380104458140710750402720000000000#602644600368# Aubrey Robinson 1121042880000805 +705Tonguecrystal bonus pay for amazing work on #OSS 00010000805 +622231380104012215627556102770000000000#464031606348# Emily White 1121042880000806 +705Throatriver bonus pay for amazing work on #OSS 00010000806 +622231380104643137826322628280000000000#675238518321# Ella Robinson 1121042880000807 +705Elftyphoon bonus pay for amazing work on #OSS 00010000807 +622231380104463846482230674770000000000#014065737680# Jayden Jones 1121042880000808 +705Talonblaze bonus pay for amazing work on #OSS 00010000808 +622231380104478176461316388460000000000#755424818004# Madison White 1121042880000809 +705Zebraberyl bonus pay for amazing work on #OSS 00010000809 +622231380104557223254548884740000000000#568255540685# Joseph White 1121042880000810 +705Braidspring bonus pay for amazing work on #OSS 00010000810 +622231380104230255732276851030000000000#562513646768# Michael Garcia 1121042880000811 +705Catpuddle bonus pay for amazing work on #OSS 00010000811 +622231380104588783536428320880000000000#815245807470# Alexander Thomas 1121042880000812 +705Princessnova bonus pay for amazing work on #OSS 00010000812 +622231380104345658234481210170000000000#644328135324# Avery Thompson 1121042880000813 +705Flierblaze bonus pay for amazing work on #OSS 00010000813 +622231380104167154463847614110000000000#568103746505# Liam Thomas 1121042880000814 +705Museriver bonus pay for amazing work on #OSS 00010000814 +622231380104546043544471812440000000000#081707453840# Mia Jackson 1121042880000815 +705Musespiral bonus pay for amazing work on #OSS 00010000815 +622231380104864308488615185360000000000#274524460152# Joshua Davis 1121042880000816 +705Winghot bonus pay for amazing work on #OSS 00010000816 +622231380104730814544888204840000000000#116735865431# Jayden Taylor 1121042880000817 +705Slayermaze bonus pay for amazing work on #OSS 00010000817 +622231380104706682887435656410000000000#837351805881# Olivia Moore 1121042880000818 +705Elkprong bonus pay for amazing work on #OSS 00010000818 +622231380104067525722263008270000000000#482555112268# Aiden Thomas 1121042880000819 +705Whalemotley bonus pay for amazing work on #OSS 00010000819 +622231380104752381715216262510000000000#657802064561# Emma Jackson 1121042880000820 +705Spikedisco bonus pay for amazing work on #OSS 00010000820 +622231380104713010356465840610000000000#081646751633# Michael Davis 1121042880000821 +705Batheather bonus pay for amazing work on #OSS 00010000821 +622231380104061032185870221030000000000#621361165500# Benjamin Thompson 1121042880000822 +705Markemerald bonus pay for amazing work on #OSS 00010000822 +622231380104728141816246715020000000000#824437834783# Lily Martin 1121042880000823 +705Rabbitlinen bonus pay for amazing work on #OSS 00010000823 +622231380104068736380251563710000000000#047078533166# Zoey Harris 1121042880000824 +705Arrowwind bonus pay for amazing work on #OSS 00010000824 +622231380104572732558260771630000000000#547668642814# Avery Brown 1121042880000825 +705Falconflint bonus pay for amazing work on #OSS 00010000825 +622231380104842586413581520150000000000#047065771007# Joseph White 1121042880000826 +705Palmjewel bonus pay for amazing work on #OSS 00010000826 +622231380104607602736620673400000000000#865855714303# Joshua Miller 1121042880000827 +705Questerplume bonus pay for amazing work on #OSS 00010000827 +622231380104183338558701287470000000000#383651354327# Benjamin Brown 1121042880000828 +705Witchquick bonus pay for amazing work on #OSS 00010000828 +622231380104765768128131185710000000000#201058870848# Alexander Johnson 1121042880000829 +705Buffalocloud bonus pay for amazing work on #OSS 00010000829 +622231380104768064871222473850000000000#873848654526# Ethan Taylor 1121042880000830 +705Samuraisulpher bonus pay for amazing work on #OSS 00010000830 +622231380104444818831062806180000000000#072714380030# Zoey Davis 1121042880000831 +705Braidsilver bonus pay for amazing work on #OSS 00010000831 +622231380104144435183121572820000000000#462480475363# Isabella Moore 1121042880000832 +705Walkerplum bonus pay for amazing work on #OSS 00010000832 +622231380104661082851174573400000000000#672677373543# Sophia Moore 1121042880000833 +705Kingleaf bonus pay for amazing work on #OSS 00010000833 +622231380104501338733202740750000000000#462668546073# Avery Martinez 1121042880000834 +705Beakproud bonus pay for amazing work on #OSS 00010000834 +622231380104400215060327326810000000000#103883504787# Lily Martinez 1121042880000835 +705Huggercopper bonus pay for amazing work on #OSS 00010000835 +622231380104153122436081251200000000000#268616860253# Aubrey Martinez 1121042880000836 +705Devourerholy bonus pay for amazing work on #OSS 00010000836 +622231380104072661054256500470000000000#666805671126# Noah Miller 1121042880000837 +705Witchstripe bonus pay for amazing work on #OSS 00010000837 +622231380104881803481235107370000000000#123050427480# Madison Martinez 1121042880000838 +705Sentrymuck bonus pay for amazing work on #OSS 00010000838 +622231380104272372030464073760000000000#661880764812# Addison Miller 1121042880000839 +705Whalepeat bonus pay for amazing work on #OSS 00010000839 +622231380104016181226858780340000000000#582307826815# William Brown 1121042880000840 +705Condorgreen bonus pay for amazing work on #OSS 00010000840 +622231380104032102322427125160000000000#646505802477# Daniel Johnson 1121042880000841 +705Palmflower bonus pay for amazing work on #OSS 00010000841 +622231380104083672823348168730000000000#667242146204# Noah Martin 1121042880000842 +705Gazellepuddle bonus pay for amazing work on #OSS 00010000842 +622231380104648806742184133680000000000#827132280321# Zoey Williams 1121042880000843 +705Ripperwax bonus pay for amazing work on #OSS 00010000843 +622231380104588255373122731250000000000#478138626546# Benjamin Johnson 1121042880000844 +705Witchginger bonus pay for amazing work on #OSS 00010000844 +622231380104215235776446535320000000000#734544512215# David Anderson 1121042880000845 +705Braidfrost bonus pay for amazing work on #OSS 00010000845 +622231380104263141722085276060000000000#422813061406# Mia Robinson 1121042880000846 +705Iguanashell bonus pay for amazing work on #OSS 00010000846 +622231380104057161685385513870000000000#034354608842# Elijah Miller 1121042880000847 +705Scorpionslow bonus pay for amazing work on #OSS 00010000847 +622231380104237718158056178360000000000#631007078416# Madison White 1121042880000848 +705Raccoonplum bonus pay for amazing work on #OSS 00010000848 +622231380104562250403741136620000000000#652466533843# Ella Garcia 1121042880000849 +705Kickeratom bonus pay for amazing work on #OSS 00010000849 +622231380104021743422842542440000000000#236220782330# Emily Martinez 1121042880000850 +705Reaperplum bonus pay for amazing work on #OSS 00010000850 +622231380104363440775706004620000000000#527062073408# David Smith 1121042880000851 +705Followerrust bonus pay for amazing work on #OSS 00010000851 +622231380104742814288313747610000000000#411747303300# Sofia Taylor 1121042880000852 +705Lionflannel bonus pay for amazing work on #OSS 00010000852 +622231380104548187516464210340000000000#315331432642# Mia Harris 1121042880000853 +705Waspshard bonus pay for amazing work on #OSS 00010000853 +622231380104073412312318342610000000000#057700428647# Lily Jones 1121042880000854 +705Oriolenova bonus pay for amazing work on #OSS 00010000854 +622231380104007035602163583640000000000#430848458643# Avery Thomas 1121042880000855 +705Speakersour bonus pay for amazing work on #OSS 00010000855 +622231380104551110461265783670000000000#418003081328# Daniel Moore 1121042880000856 +705Princessspectrum bonus pay for amazing work on #OSS 00010000856 +622231380104833181845350106570000000000#888213865565# Matthew Johnson 1121042880000857 +705Weaseldew bonus pay for amazing work on #OSS 00010000857 +622231380104467344124016445050000000000#115454660853# Olivia Moore 1121042880000858 +705Boasolstice bonus pay for amazing work on #OSS 00010000858 +622231380104087048076580467840000000000#543643377676# Ethan Martin 1121042880000859 +705Beartree bonus pay for amazing work on #OSS 00010000859 +622231380104308750433423752300000000000#507725074218# Lily Moore 1121042880000860 +705Talonaquamarine bonus pay for amazing work on #OSS 00010000860 +622231380104077371282650611530000000000#417778555316# David Thomas 1121042880000861 +705Spiritthorn bonus pay for amazing work on #OSS 00010000861 +622231380104301851560411570770000000000#530486504617# Elijah Martin 1121042880000862 +705Spearforest bonus pay for amazing work on #OSS 00010000862 +622231380104706281348768636630000000000#618134251723# Ava Wilson 1121042880000863 +705Kittenribbon bonus pay for amazing work on #OSS 00010000863 +622231380104555713110165580460000000000#701803414648# Zoey Harris 1121042880000864 +705Beespark bonus pay for amazing work on #OSS 00010000864 +622231380104363074576637303660000000000#280362627745# Mason Garcia 1121042880000865 +705Deathtide bonus pay for amazing work on #OSS 00010000865 +622231380104177464204126676580000000000#523564822614# Natalie Jackson 1121042880000866 +705Roachdawn bonus pay for amazing work on #OSS 00010000866 +622231380104280426217517147600000000000#745617401370# Madison Williams 1121042880000867 +705Crushershard bonus pay for amazing work on #OSS 00010000867 +622231380104485586257223188010000000000#780053777237# Matthew Jackson 1121042880000868 +705Hawkcoconut bonus pay for amazing work on #OSS 00010000868 +622231380104743843424350047520000000000#550636411545# Liam Jones 1121042880000869 +705Serpentwest bonus pay for amazing work on #OSS 00010000869 +622231380104643674764081847300000000000#284276710751# Zoey Johnson 1121042880000870 +705Buffalomotley bonus pay for amazing work on #OSS 00010000870 +622231380104056343513063065760000000000#508884623643# Madison Davis 1121042880000871 +705Razorgeode bonus pay for amazing work on #OSS 00010000871 +622231380104820305620553750540000000000#356588072722# Sophia Martinez 1121042880000872 +705Pumacoconut bonus pay for amazing work on #OSS 00010000872 +622231380104788887207383886230000000000#283883677801# Sophia Anderson 1121042880000873 +705Capruby bonus pay for amazing work on #OSS 00010000873 +622231380104663380552804200030000000000#161526615640# Elizabeth Martin 1121042880000874 +705Skinnerpollen bonus pay for amazing work on #OSS 00010000874 +622231380104801307210177551250000000000#101735723020# Joseph Moore 1121042880000875 +705Jayplain bonus pay for amazing work on #OSS 00010000875 +622231380104014643534587100820000000000#756003780678# Joseph Miller 1121042880000876 +705Sentrycomet bonus pay for amazing work on #OSS 00010000876 +622231380104477322628014433610000000000#732157374675# Joseph Miller 1121042880000877 +705Fishdull bonus pay for amazing work on #OSS 00010000877 +622231380104422806170550351480000000000#430044784038# Mia Wilson 1121042880000878 +705Crestmisty bonus pay for amazing work on #OSS 00010000878 +622231380104606717608766563000000000000#834235072212# Avery Brown 1121042880000879 +705Thunderdesert bonus pay for amazing work on #OSS 00010000879 +622231380104484712500123741430000000000#314832464584# Ella Taylor 1121042880000880 +705Ninjarowan bonus pay for amazing work on #OSS 00010000880 +622231380104528536357313576540000000000#753460205404# Matthew Martinez 1121042880000881 +705Huggerweak bonus pay for amazing work on #OSS 00010000881 +622231380104306874055536156710000000000#173554640744# Sofia Robinson 1121042880000882 +705Zebradune bonus pay for amazing work on #OSS 00010000882 +622231380104787440588830660750000000000#348684687850# Elizabeth Thomas 1121042880000883 +705Sightbrass bonus pay for amazing work on #OSS 00010000883 +622231380104430865870271266610000000000#611355082411# Isabella Miller 1121042880000884 +705Witchwind bonus pay for amazing work on #OSS 00010000884 +622231380104234185001814446020000000000#586784603257# Natalie Harris 1121042880000885 +705Razortundra bonus pay for amazing work on #OSS 00010000885 +622231380104841477635867856440000000000#480021475406# Ethan White 1121042880000886 +705Anthorn bonus pay for amazing work on #OSS 00010000886 +622231380104263773667131661320000000000#318212877103# Ethan Miller 1121042880000887 +705Toucanobsidian bonus pay for amazing work on #OSS 00010000887 +622231380104668670325147224180000000000#655626676063# Mason Smith 1121042880000888 +705Birdhail bonus pay for amazing work on #OSS 00010000888 +622231380104100720862172723000000000000#666020756868# Benjamin White 1121042880000889 +705Ridergold bonus pay for amazing work on #OSS 00010000889 +622231380104515305706877428740000000000#242004783760# James Martinez 1121042880000890 +705Snakepyrite bonus pay for amazing work on #OSS 00010000890 +622231380104523384032042300810000000000#148311555723# Andrew Thompson 1121042880000891 +705Gazelleshadow bonus pay for amazing work on #OSS 00010000891 +622231380104535451437140301240000000000#034163780158# Avery Garcia 1121042880000892 +705Pawspectrum bonus pay for amazing work on #OSS 00010000892 +622231380104157001647750381130000000000#804753362147# David Anderson 1121042880000893 +705Thunderfan bonus pay for amazing work on #OSS 00010000893 +622231380104511118503154876620000000000#473005247838# Natalie Martinez 1121042880000894 +705Bindertwisty bonus pay for amazing work on #OSS 00010000894 +622231380104130044467851322270000000000#144215101086# Liam Johnson 1121042880000895 +705Crestlemon bonus pay for amazing work on #OSS 00010000895 +622231380104754320012043524160000000000#604420068381# Joshua Thomas 1121042880000896 +705Glassnickel bonus pay for amazing work on #OSS 00010000896 +622231380104362843631754713200000000000#181584325201# Olivia Thomas 1121042880000897 +705Backsprout bonus pay for amazing work on #OSS 00010000897 +622231380104381286873063158030000000000#131487700710# Liam Brown 1121042880000898 +705Banemud bonus pay for amazing work on #OSS 00010000898 +622231380104401117208522822820000000000#830367588381# William Johnson 1121042880000899 +705Moosestream bonus pay for amazing work on #OSS 00010000899 +622231380104180260360271878460000000000#140811215731# Isabella Anderson 1121042880000900 +705Talonglitter bonus pay for amazing work on #OSS 00010000900 +622231380104628123885157871450000000000#528446112554# Joshua Garcia 1121042880000901 +705Boadark bonus pay for amazing work on #OSS 00010000901 +622231380104336134162081268040000000000#661767303433# Anthony Jones 1121042880000902 +705Fanghorse bonus pay for amazing work on #OSS 00010000902 +622231380104836331611063441810000000000#681530831706# Addison Johnson 1121042880000903 +705Dolphinred bonus pay for amazing work on #OSS 00010000903 +622231380104241542435564205740000000000#827045673016# Ethan Harris 1121042880000904 +705Kingabalone bonus pay for amazing work on #OSS 00010000904 +622231380104231205820347874850000000000#377353015631# Jacob Garcia 1121042880000905 +705Saverquark bonus pay for amazing work on #OSS 00010000905 +622231380104000647366230863000000000000#764776630712# Anthony Martin 1121042880000906 +705Roverstump bonus pay for amazing work on #OSS 00010000906 +622231380104572618714622203470000000000#368887460310# Sofia Robinson 1121042880000907 +705Queenlong bonus pay for amazing work on #OSS 00010000907 +622231380104768421123751876030000000000#437501126103# Natalie Wilson 1121042880000908 +705Sightweed bonus pay for amazing work on #OSS 00010000908 +622231380104310415862056718800000000000#547146410068# Avery Smith 1121042880000909 +705Ravenshore bonus pay for amazing work on #OSS 00010000909 +622231380104202401805061060820000000000#508501687255# Addison Taylor 1121042880000910 +705Spurpink bonus pay for amazing work on #OSS 00010000910 +622231380104743678145535677340000000000#028085114763# Aiden Martin 1121042880000911 +705Bitegentle bonus pay for amazing work on #OSS 00010000911 +622231380104266844112012814710000000000#812835787311# Elijah White 1121042880000912 +705Ratfuschia bonus pay for amazing work on #OSS 00010000912 +622231380104745000081332575200000000000#871182838470# Elizabeth Thomas 1121042880000913 +705Scourgealpine bonus pay for amazing work on #OSS 00010000913 +622231380104448682066777551260000000000#356506207583# Aubrey Moore 1121042880000914 +705Zebraglaze bonus pay for amazing work on #OSS 00010000914 +622231380104857782343681208160000000000#542070205180# Addison Robinson 1121042880000915 +705Warlockviolet bonus pay for amazing work on #OSS 00010000915 +622231380104851423815850314510000000000#863768874458# Elijah White 1121042880000916 +705Riderdiamond bonus pay for amazing work on #OSS 00010000916 +622231380104031334821661263560000000000#253403027417# Charlotte Martin 1121042880000917 +705Stingerroad bonus pay for amazing work on #OSS 00010000917 +622231380104120681326716146620000000000#827137830463# Noah White 1121042880000918 +705Chatterbubble bonus pay for amazing work on #OSS 00010000918 +622231380104860530208612374160000000000#500875470036# Emily Harris 1121042880000919 +705Samurairag bonus pay for amazing work on #OSS 00010000919 +622231380104671218223174833220000000000#544287402344# Emily Martinez 1121042880000920 +705Scarersponge bonus pay for amazing work on #OSS 00010000920 +622231380104522620528604200570000000000#134328236884# Zoey Johnson 1121042880000921 +705Thundergranite bonus pay for amazing work on #OSS 00010000921 +622231380104535073804225001300000000000#670387666723# Chloe Harris 1121042880000922 +705Banecherry bonus pay for amazing work on #OSS 00010000922 +622231380104607823532417652240000000000#387107311285# Elijah Taylor 1121042880000923 +705Warlockfrost bonus pay for amazing work on #OSS 00010000923 +622231380104568841327513033070000000000#041278382261# Madison Martin 1121042880000924 +705Faceprickle bonus pay for amazing work on #OSS 00010000924 +622231380104468533311153358210000000000#200304554562# Elizabeth Jackson 1121042880000925 +705Swallowrogue bonus pay for amazing work on #OSS 00010000925 +622231380104226648774583254610000000000#255487845433# Sophia Smith 1121042880000926 +705Wandererchestnut bonus pay for amazing work on #OSS 00010000926 +622231380104162404837861655660000000000#060728582470# Sophia Martinez 1121042880000927 +705Thieftwisty bonus pay for amazing work on #OSS 00010000927 +622231380104831262403264103350000000000#717673307300# Alexander Thompson 1121042880000928 +705Knifesponge bonus pay for amazing work on #OSS 00010000928 +622231380104076235005782032880000000000#038641036076# Jacob Thomas 1121042880000929 +705Collarmoor bonus pay for amazing work on #OSS 00010000929 +622231380104281224526467565880000000000#043871367334# Ethan Johnson 1121042880000930 +705Mustangquasar bonus pay for amazing work on #OSS 00010000930 +622231380104423418262716144820000000000#387252477866# Avery Jackson 1121042880000931 +705Stagbuttercup bonus pay for amazing work on #OSS 00010000931 +622231380104215800824013068200000000000#704633816475# Mason Miller 1121042880000932 +705Dartriver bonus pay for amazing work on #OSS 00010000932 +622231380104484424230282215340000000000#832044580852# Ethan Williams 1121042880000933 +705Hawkprairie bonus pay for amazing work on #OSS 00010000933 +622231380104883707340874317360000000000#724870841715# Elizabeth Williams 1121042880000934 +705Followerebony bonus pay for amazing work on #OSS 00010000934 +622231380104782004308642613660000000000#886678401318# Mason Wilson 1121042880000935 +705Horsenoon bonus pay for amazing work on #OSS 00010000935 +622231380104274461506363420530000000000#387144407402# Daniel Thompson 1121042880000936 +705Craneshort bonus pay for amazing work on #OSS 00010000936 +622231380104718705364166584500000000000#650471762076# Madison White 1121042880000937 +705Fighterflax bonus pay for amazing work on #OSS 00010000937 +622231380104822842734144630230000000000#055672285505# William Brown 1121042880000938 +705Browgiant bonus pay for amazing work on #OSS 00010000938 +622231380104082241747846357840000000000#705681300647# Jacob Wilson 1121042880000939 +705Shriekerzest bonus pay for amazing work on #OSS 00010000939 +622231380104334743846605172620000000000#664341765388# Charlotte Harris 1121042880000940 +705Walkerzinc bonus pay for amazing work on #OSS 00010000940 +622231380104575454705600301160000000000#757318461321# Ella Miller 1121042880000941 +705Owlmuck bonus pay for amazing work on #OSS 00010000941 +622231380104712834207100477600000000000#156377062533# William Wilson 1121042880000942 +705Swallowflicker bonus pay for amazing work on #OSS 00010000942 +622231380104333620524018014450000000000#420256476876# David Miller 1121042880000943 +705Chatterflash bonus pay for amazing work on #OSS 00010000943 +622231380104025667483478411800000000000#856110743487# Chloe Anderson 1121042880000944 +705Kinggold bonus pay for amazing work on #OSS 00010000944 +622231380104822218464263535230000000000#356057126322# William Robinson 1121042880000945 +705Goosecoconut bonus pay for amazing work on #OSS 00010000945 +622231380104542543707854855540000000000#116006150554# William Martinez 1121042880000946 +705Grasphazel bonus pay for amazing work on #OSS 00010000946 +622231380104384038625865334110000000000#553262770385# Liam Brown 1121042880000947 +705Storksand bonus pay for amazing work on #OSS 00010000947 +622231380104526531260615170460000000000#803135757108# Ella Jones 1121042880000948 +705Facehill bonus pay for amazing work on #OSS 00010000948 +622231380104256007872438076360000000000#228808561467# Liam Thompson 1121042880000949 +705Songtundra bonus pay for amazing work on #OSS 00010000949 +622231380104215523826063376720000000000#704178226853# Zoey Harris 1121042880000950 +705Beetlegray bonus pay for amazing work on #OSS 00010000950 +622231380104167166573541100570000000000#150546554674# Mason Miller 1121042880000951 +705Seedcandy bonus pay for amazing work on #OSS 00010000951 +622231380104418415854177401370000000000#271216357007# Emma Jackson 1121042880000952 +705Diverapid bonus pay for amazing work on #OSS 00010000952 +622231380104305408686566207600000000000#773076123148# Aiden Martinez 1121042880000953 +705Loonswift bonus pay for amazing work on #OSS 00010000953 +622231380104255832140237804270000000000#006682875485# Benjamin Taylor 1121042880000954 +705Legsdiamond bonus pay for amazing work on #OSS 00010000954 +622231380104304504310338471480000000000#465861047252# Sophia Harris 1121042880000955 +705Coyotesatin bonus pay for amazing work on #OSS 00010000955 +622231380104353664536016623000000000000#418742464533# Abigail White 1121042880000956 +705Roachstellar bonus pay for amazing work on #OSS 00010000956 +622231380104453677547150581100000000000#331666178086# Sophia Anderson 1121042880000957 +705Crestdaisy bonus pay for amazing work on #OSS 00010000957 +622231380104012156518523371340000000000#517707807102# Mason Thomas 1121042880000958 +705Weedpool bonus pay for amazing work on #OSS 00010000958 +622231380104084586542725884840000000000#002112618118# Mia Brown 1121042880000959 +705Roachhorse bonus pay for amazing work on #OSS 00010000959 +622231380104228858108554863680000000000#770675684578# Mason Anderson 1121042880000960 +705Boarbrave bonus pay for amazing work on #OSS 00010000960 +622231380104554370736506685300000000000#644560202301# Jayden Davis 1121042880000961 +705Pythonroot bonus pay for amazing work on #OSS 00010000961 +622231380104025300644363744700000000000#865553163627# Elizabeth Thompson 1121042880000962 +705Burnbrave bonus pay for amazing work on #OSS 00010000962 +622231380104550743642131075250000000000#221400630451# Ava Martin 1121042880000963 +705Whaleplump bonus pay for amazing work on #OSS 00010000963 +622231380104660553665306553140000000000#612714763223# Andrew Moore 1121042880000964 +705Tailscythe bonus pay for amazing work on #OSS 00010000964 +622231380104111313152002804700000000000#475165886261# Jayden Jones 1121042880000965 +705Sageblossom bonus pay for amazing work on #OSS 00010000965 +622231380104021017248215580500000000000#770103338375# Alexander Johnson 1121042880000966 +705Backnorth bonus pay for amazing work on #OSS 00010000966 +622231380104340827225704421570000000000#288067378715# Isabella Martinez 1121042880000967 +705Pigmarsh bonus pay for amazing work on #OSS 00010000967 +622231380104343236067804646110000000000#176344663383# Aiden Harris 1121042880000968 +705Bowink bonus pay for amazing work on #OSS 00010000968 +622231380104084711014403665870000000000#700687215131# Ella Wilson 1121042880000969 +705Guardianpitch bonus pay for amazing work on #OSS 00010000969 +622231380104770435484336810650000000000#187650165356# Matthew Anderson 1121042880000970 +705Collarjasper bonus pay for amazing work on #OSS 00010000970 +622231380104262255023555157260000000000#062873061358# Charlotte Smith 1121042880000971 +705Scalewarp bonus pay for amazing work on #OSS 00010000971 +622231380104378883816108173140000000000#206837513452# William Williams 1121042880000972 +705Hornmarsh bonus pay for amazing work on #OSS 00010000972 +622231380104068134127606162230000000000#555326730528# Benjamin Moore 1121042880000973 +705Swoopblaze bonus pay for amazing work on #OSS 00010000973 +622231380104355816845027567180000000000#224160870744# Aiden Robinson 1121042880000974 +705Coyotecypress bonus pay for amazing work on #OSS 00010000974 +622231380104217553465121214300000000000#010878860138# Andrew Smith 1121042880000975 +705Healerpie bonus pay for amazing work on #OSS 00010000975 +622231380104552441406038288420000000000#020118816254# Alexander Davis 1121042880000976 +705Snagglefootscratch bonus pay for amazing work on #OSS 00010000976 +622231380104671663344115105780000000000#151683605621# Chloe Thompson 1121042880000977 +705Boarsugar bonus pay for amazing work on #OSS 00010000977 +622231380104432881857213238850000000000#530768431354# Andrew Jackson 1121042880000978 +705Frillchrome bonus pay for amazing work on #OSS 00010000978 +622231380104111425751756475020000000000#114300785640# Olivia Williams 1121042880000979 +705Hunterindigo bonus pay for amazing work on #OSS 00010000979 +622231380104300184707200207340000000000#652476411610# Jacob Williams 1121042880000980 +705Wolfvine bonus pay for amazing work on #OSS 00010000980 +622231380104388745801836450050000000000#835462502204# David Jones 1121042880000981 +705Griffinnorth bonus pay for amazing work on #OSS 00010000981 +622231380104312151081777753330000000000#562062827344# Lily Wilson 1121042880000982 +705Arrowspectrum bonus pay for amazing work on #OSS 00010000982 +622231380104811155751738225100000000000#656141415441# Isabella Martin 1121042880000983 +705Goatcrystal bonus pay for amazing work on #OSS 00010000983 +622231380104136725443846541710000000000#633415712303# Joseph Garcia 1121042880000984 +705Hairpewter bonus pay for amazing work on #OSS 00010000984 +622231380104475541587858186250000000000#506025006827# Noah Moore 1121042880000985 +705Mindvalley bonus pay for amazing work on #OSS 00010000985 +622231380104142321101652014830000000000#616541783355# Joseph Jones 1121042880000986 +705Tailplatinum bonus pay for amazing work on #OSS 00010000986 +622231380104473180481474102880000000000#128362630343# Sophia Davis 1121042880000987 +705Swishersleet bonus pay for amazing work on #OSS 00010000987 +622231380104648701162143240840000000000#005674800722# Charlotte Moore 1121042880000988 +705Fingercake bonus pay for amazing work on #OSS 00010000988 +622231380104128652478830840380000000000#080735167545# Natalie Harris 1121042880000989 +705Touchseed bonus pay for amazing work on #OSS 00010000989 +622231380104620846451501205340000000000#573318324758# Elizabeth Moore 1121042880000990 +705Divefrost bonus pay for amazing work on #OSS 00010000990 +622231380104672468521412036540000000000#345046736583# Natalie Robinson 1121042880000991 +705Hairmad bonus pay for amazing work on #OSS 00010000991 +622231380104253742385850033750000000000#650510863244# Jayden Jackson 1121042880000992 +705Queendestiny bonus pay for amazing work on #OSS 00010000992 +622231380104112502086355102280000000000#205736453118# Alexander Robinson 1121042880000993 +705Whimseybutton bonus pay for amazing work on #OSS 00010000993 +622231380104266773582214145150000000000#810377032272# Benjamin Anderson 1121042880000994 +705Markfoam bonus pay for amazing work on #OSS 00010000994 +622231380104762522114630562410000000000#318047105551# Aubrey Harris 1121042880000995 +705Gazellesnapdragon bonus pay for amazing work on #OSS 00010000995 +622231380104527601658157835750000000000#244206813786# Olivia Anderson 1121042880000996 +705Mythcake bonus pay for amazing work on #OSS 00010000996 +622231380104571187400617351340000000000#720446321472# Jacob Robinson 1121042880000997 +705Eagleabalone bonus pay for amazing work on #OSS 00010000997 +622231380104745822673415616610000000000#762083514541# Aubrey Garcia 1121042880000998 +705Knifeswamp bonus pay for amazing work on #OSS 00010000998 +622231380104643388408725481340000000000#527135253281# Zoey Garcia 1121042880000999 +705Weaverfair bonus pay for amazing work on #OSS 00010000999 +622231380104222300176652743860000000000#277472125478# Joshua Moore 1121042880001000 +705Huggersequoia bonus pay for amazing work on #OSS 00010001000 +622231380104824163674763350320000000000#156064030415# Sophia Robinson 1121042880001001 +705Moleglacier bonus pay for amazing work on #OSS 00010001001 +622231380104324821217464861220000000000#303782845236# Joshua Brown 1121042880001002 +705Slayerdark bonus pay for amazing work on #OSS 00010001002 +622231380104501584411305246840000000000#244230800151# Charlotte Smith 1121042880001003 +705Spiritviolet bonus pay for amazing work on #OSS 00010001003 +622231380104711321425786151650000000000#863010418531# Benjamin Martinez 1121042880001004 +705Dropcharm bonus pay for amazing work on #OSS 00010001004 +622231380104346284607836885540000000000#850317162255# Daniel Miller 1121042880001005 +705Toothclear bonus pay for amazing work on #OSS 00010001005 +622231380104031051478237217200000000000#837437533765# Jacob Wilson 1121042880001006 +705Condorfoul bonus pay for amazing work on #OSS 00010001006 +622231380104188055081358271320000000000#256454660208# Isabella Davis 1121042880001007 +705Eaglesour bonus pay for amazing work on #OSS 00010001007 +622231380104623121711200431630000000000#824304812087# Joshua Harris 1121042880001008 +705Lordslash bonus pay for amazing work on #OSS 00010001008 +622231380104445607126863155560000000000#033545474730# William Harris 1121042880001009 +705Browgreen bonus pay for amazing work on #OSS 00010001009 +622231380104302774647618372630000000000#548364183030# Ethan Wilson 1121042880001010 +705Ripperchatter bonus pay for amazing work on #OSS 00010001010 +622231380104110356418036573540000000000#684540247741# Madison Robinson 1121042880001011 +705Jayshallow bonus pay for amazing work on #OSS 00010001011 +622231380104640272143520185770000000000#251482260805# Joshua Anderson 1121042880001012 +705Riderheather bonus pay for amazing work on #OSS 00010001012 +622231380104482742368414015600000000000#871074230843# Charlotte Smith 1121042880001013 +705Deathshine bonus pay for amazing work on #OSS 00010001013 +622231380104753007626665751110000000000#320505002338# Chloe Martinez 1121042880001014 +705Scarpitch bonus pay for amazing work on #OSS 00010001014 +622231380104681871687305672760000000000#400542143537# Sophia Robinson 1121042880001015 +705Wingphantom bonus pay for amazing work on #OSS 00010001015 +622231380104468287621354761770000000000#852308106863# Sophia Martin 1121042880001016 +705Hawkpalm bonus pay for amazing work on #OSS 00010001016 +622231380104482571558752836430000000000#027382341787# Charlotte Moore 1121042880001017 +705Stalkerberyl bonus pay for amazing work on #OSS 00010001017 +622231380104181027100847040520000000000#375423688560# Sophia Davis 1121042880001018 +705Chestbow bonus pay for amazing work on #OSS 00010001018 +622231380104754672746655338780000000000#584334248710# Michael White 1121042880001019 +705Voicecyber bonus pay for amazing work on #OSS 00010001019 +622231380104336441231523332580000000000#086316828060# David Johnson 1121042880001020 +705Skullhail bonus pay for amazing work on #OSS 00010001020 +622231380104537462312341304800000000000#471647487702# David Martin 1121042880001021 +705Stingershort bonus pay for amazing work on #OSS 00010001021 +622231380104475458212682213660000000000#724041334886# Zoey Miller 1121042880001022 +705Antivory bonus pay for amazing work on #OSS 00010001022 +622231380104704502460755305240000000000#456033875524# Noah Taylor 1121042880001023 +705Tongueapricot bonus pay for amazing work on #OSS 00010001023 +622231380104472731324143383610000000000#102533465031# Addison White 1121042880001024 +705Flylong bonus pay for amazing work on #OSS 00010001024 +622231380104386722753711445510000000000#312377522624# Emily Davis 1121042880001025 +705Salmoncypress bonus pay for amazing work on #OSS 00010001025 +622231380104125177024800750480000000000#184082476744# Emily Thomas 1121042880001026 +705Centaursnow bonus pay for amazing work on #OSS 00010001026 +622231380104247881387484778360000000000#853684128321# Emma Wilson 1121042880001027 +705Facelead bonus pay for amazing work on #OSS 00010001027 +622231380104111768557086073730000000000#300575123063# Jacob Anderson 1121042880001028 +705Herocedar bonus pay for amazing work on #OSS 00010001028 +622231380104128417737553147840000000000#762460651323# Zoey Garcia 1121042880001029 +705Wyrmamber bonus pay for amazing work on #OSS 00010001029 +622231380104788205063753623750000000000#376018713407# Andrew Smith 1121042880001030 +705Armblaze bonus pay for amazing work on #OSS 00010001030 +622231380104003608328714674110000000000#426425803115# David Thompson 1121042880001031 +705Turnerdour bonus pay for amazing work on #OSS 00010001031 +622231380104766376486518114460000000000#270371283574# Matthew Miller 1121042880001032 +705Curtainsugar bonus pay for amazing work on #OSS 00010001032 +622231380104152046485126122730000000000#150554602260# Emily Taylor 1121042880001033 +705Geckocerulean bonus pay for amazing work on #OSS 00010001033 +622231380104521853080052714860000000000#784083553730# Lily Jones 1121042880001034 +705Guardianchisel bonus pay for amazing work on #OSS 00010001034 +622231380104156331578421740200000000000#455622464502# Michael Miller 1121042880001035 +705Fishermica bonus pay for amazing work on #OSS 00010001035 +622231380104006426453715114170000000000#486757328847# Daniel Jones 1121042880001036 +705Queenwalnut bonus pay for amazing work on #OSS 00010001036 +622231380104756634710273022500000000000#653834654164# Benjamin Garcia 1121042880001037 +705Leoparddune bonus pay for amazing work on #OSS 00010001037 +622231380104887620448072284820000000000#756874356333# Natalie Anderson 1121042880001038 +705Spurchecker bonus pay for amazing work on #OSS 00010001038 +622231380104115726572010725660000000000#605064552664# David Martin 1121042880001039 +705Lashercrystal bonus pay for amazing work on #OSS 00010001039 +622231380104612027348461122430000000000#785105315432# Aubrey Smith 1121042880001040 +705Legendcandle bonus pay for amazing work on #OSS 00010001040 +622231380104841317333405223810000000000#464243048722# Isabella Thomas 1121042880001041 +705Mustangglacier bonus pay for amazing work on #OSS 00010001041 +622231380104258131204748056370000000000#213667273222# Sophia Smith 1121042880001042 +705Ridgebrindle bonus pay for amazing work on #OSS 00010001042 +622231380104807218220302775430000000000#003048015300# Avery Miller 1121042880001043 +705Finbubble bonus pay for amazing work on #OSS 00010001043 +622231380104200804036620465560000000000#778380267050# Liam Robinson 1121042880001044 +705Moleglitter bonus pay for amazing work on #OSS 00010001044 +622231380104256226664465673500000000000#580518883028# Joseph White 1121042880001045 +705Raynebula bonus pay for amazing work on #OSS 00010001045 +622231380104686047477550513870000000000#132117875086# Emma Jones 1121042880001046 +705Falconbrook bonus pay for amazing work on #OSS 00010001046 +622231380104373708140880184700000000000#018602278512# Aubrey White 1121042880001047 +705Bellypink bonus pay for amazing work on #OSS 00010001047 +622231380104353713854108006470000000000#111274606606# David Miller 1121042880001048 +705Rippergray bonus pay for amazing work on #OSS 00010001048 +622231380104466571445871087040000000000#410478231440# Chloe Thompson 1121042880001049 +705Giverlightning bonus pay for amazing work on #OSS 00010001049 +622231380104002523341527737340000000000#523207118328# Jacob Johnson 1121042880001050 +705Ferretsilver bonus pay for amazing work on #OSS 00010001050 +622231380104848185161660276630000000000#343322156141# Alexander Jackson 1121042880001051 +705Scribewild bonus pay for amazing work on #OSS 00010001051 +622231380104420140041638516620000000000#626704766551# Daniel Martin 1121042880001052 +705Stonecoal bonus pay for amazing work on #OSS 00010001052 +622231380104234744058266005730000000000#483370467462# Noah Robinson 1121042880001053 +705Weaverjungle bonus pay for amazing work on #OSS 00010001053 +622231380104331640084351820300000000000#111375068580# James Jones 1121042880001054 +705Swallowbead bonus pay for amazing work on #OSS 00010001054 +622231380104635602818451546260000000000#751670067806# David Davis 1121042880001055 +705Jackalfern bonus pay for amazing work on #OSS 00010001055 +622231380104813357763604010740000000000#811880815300# Mason Taylor 1121042880001056 +705Cranetorch bonus pay for amazing work on #OSS 00010001056 +622231380104687704061008116570000000000#564586410030# Andrew Williams 1121042880001057 +705Cranebead bonus pay for amazing work on #OSS 00010001057 +622231380104165073875781783600000000000#685511862752# Benjamin Anderson 1121042880001058 +705Ridervenom bonus pay for amazing work on #OSS 00010001058 +622231380104766317617771784170000000000#820520755171# Aubrey Robinson 1121042880001059 +705Crafterlace bonus pay for amazing work on #OSS 00010001059 +622231380104451473520214472640000000000#058868586747# Aubrey Jackson 1121042880001060 +705Mistressphantom bonus pay for amazing work on #OSS 00010001060 +622231380104211344803662266420000000000#224115651554# Noah Thompson 1121042880001061 +705Braidvivid bonus pay for amazing work on #OSS 00010001061 +622231380104148521386386433110000000000#326302181100# David Taylor 1121042880001062 +705Leaderlead bonus pay for amazing work on #OSS 00010001062 +622231380104115477246555061060000000000#038077470113# Andrew Thompson 1121042880001063 +705Arrowwool bonus pay for amazing work on #OSS 00010001063 +622231380104468517800647871100000000000#874078714453# Jacob Jones 1121042880001064 +705Runnermad bonus pay for amazing work on #OSS 00010001064 +622231380104666500045274356600000000000#228478527474# Olivia Martinez 1121042880001065 +705Storksponge bonus pay for amazing work on #OSS 00010001065 +622231380104742018376246272260000000000#574100228511# Mason Anderson 1121042880001066 +705Legsripple bonus pay for amazing work on #OSS 00010001066 +622231380104328226773705221660000000000#404468054703# Noah Davis 1121042880001067 +705Skullsleet bonus pay for amazing work on #OSS 00010001067 +622231380104151120331350540750000000000#310044806132# Daniel Martinez 1121042880001068 +705Carverdaisy bonus pay for amazing work on #OSS 00010001068 +622231380104418023053873417600000000000#771766888017# Elijah Martin 1121042880001069 +705Razormaple bonus pay for amazing work on #OSS 00010001069 +622231380104618646111104776320000000000#102317434071# Addison Garcia 1121042880001070 +705Sharklight bonus pay for amazing work on #OSS 00010001070 +622231380104168303075046063460000000000#262836742112# Andrew Williams 1121042880001071 +705Songsatin bonus pay for amazing work on #OSS 00010001071 +622231380104171840823005443460000000000#540608880767# Abigail Anderson 1121042880001072 +705Knavesoft bonus pay for amazing work on #OSS 00010001072 +622231380104580517203003404540000000000#208524541858# William White 1121042880001073 +705Slicergreen bonus pay for amazing work on #OSS 00010001073 +622231380104446553340148121760000000000#633062785027# Liam Taylor 1121042880001074 +705Dolphincoral bonus pay for amazing work on #OSS 00010001074 +622231380104730805500727447720000000000#518270550341# Daniel White 1121042880001075 +705Armspring bonus pay for amazing work on #OSS 00010001075 +622231380104511178401283188810000000000#264646642073# Olivia White 1121042880001076 +705Grabbernimble bonus pay for amazing work on #OSS 00010001076 +622231380104051527626432212130000000000#134226077038# Ethan Martinez 1121042880001077 +705Oriolehorn bonus pay for amazing work on #OSS 00010001077 +622231380104621340614774115410000000000#166284281380# Lily Miller 1121042880001078 +705Owlluminous bonus pay for amazing work on #OSS 00010001078 +622231380104500142151684046270000000000#017386370271# William Jackson 1121042880001079 +705Glazerpie bonus pay for amazing work on #OSS 00010001079 +622231380104165338851874876780000000000#421004406574# William Martin 1121042880001080 +705Scribebrave bonus pay for amazing work on #OSS 00010001080 +622231380104345380328142374460000000000#434766845247# Ella Smith 1121042880001081 +705Sightbush bonus pay for amazing work on #OSS 00010001081 +622231380104740016517075573660000000000#248702254707# Madison Wilson 1121042880001082 +705Jawjelly bonus pay for amazing work on #OSS 00010001082 +622231380104463558087224001270000000000#121644146238# Avery White 1121042880001083 +705Ogregreen bonus pay for amazing work on #OSS 00010001083 +622231380104477645340618100840000000000#281765444254# Liam Smith 1121042880001084 +705Boargray bonus pay for amazing work on #OSS 00010001084 +622231380104173218148206517640000000000#403000238330# Ethan Jackson 1121042880001085 +705Trackgentle bonus pay for amazing work on #OSS 00010001085 +622231380104505551466570378650000000000#587310312347# Michael Moore 1121042880001086 +705Speakerpond bonus pay for amazing work on #OSS 00010001086 +622231380104064401460458834360000000000#486566752826# William Davis 1121042880001087 +705Trackercake bonus pay for amazing work on #OSS 00010001087 +622231380104172375657545578070000000000#128204421685# Mason Thomas 1121042880001088 +705Frillsnow bonus pay for amazing work on #OSS 00010001088 +622231380104435874685513871650000000000#170851871344# Madison Moore 1121042880001089 +705Knifelapis bonus pay for amazing work on #OSS 00010001089 +622231380104503013652423777010000000000#800484525674# Aiden Johnson 1121042880001090 +705Catcherhickory bonus pay for amazing work on #OSS 00010001090 +622231380104403734404358153530000000000#165156113020# Aiden Anderson 1121042880001091 +705Flybog bonus pay for amazing work on #OSS 00010001091 +622231380104488470371737228150000000000#507816650475# Ella Brown 1121042880001092 +705Ninjablaze bonus pay for amazing work on #OSS 00010001092 +622231380104377836137160340500000000000#631810840634# Anthony Garcia 1121042880001093 +705Viperballistic bonus pay for amazing work on #OSS 00010001093 +622231380104517353177154147640000000000#453726522180# Avery Jones 1121042880001094 +705Coyoteplume bonus pay for amazing work on #OSS 00010001094 +622231380104136461561118702500000000000#022705388736# Ava Taylor 1121042880001095 +705Backalmond bonus pay for amazing work on #OSS 00010001095 +622231380104166148104635781470000000000#886744745283# Jacob Thompson 1121042880001096 +705Knightbristle bonus pay for amazing work on #OSS 00010001096 +622231380104346432014472485350000000000#478865086652# Elizabeth White 1121042880001097 +705Foxlinen bonus pay for amazing work on #OSS 00010001097 +622231380104145526736404731780000000000#700648881228# Mason Davis 1121042880001098 +705Dutchesssilent bonus pay for amazing work on #OSS 00010001098 +622231380104272841752753072440000000000#217384445823# Aiden Martin 1121042880001099 +705Razorred bonus pay for amazing work on #OSS 00010001099 +622231380104846307257620546000000000000#374705381282# Avery Thompson 1121042880001100 +705Gazellehoneysuckle bonus pay for amazing work on #OSS 00010001100 +622231380104046603762876134700000000000#426054183524# Jacob Davis 1121042880001101 +705Musesilk bonus pay for amazing work on #OSS 00010001101 +622231380104561852706845443010000000000#405530718564# Daniel Taylor 1121042880001102 +705Apejade bonus pay for amazing work on #OSS 00010001102 +622231380104626882831244128860000000000#783525848333# Andrew Thomas 1121042880001103 +705Arrowbrick bonus pay for amazing work on #OSS 00010001103 +622231380104614464170011053830000000000#228600547704# Joshua Johnson 1121042880001104 +705Grabberolive bonus pay for amazing work on #OSS 00010001104 +622231380104702576884875616650000000000#716316628551# Charlotte Williams 1121042880001105 +705Followerglass bonus pay for amazing work on #OSS 00010001105 +622231380104058422256230703570000000000#605420051833# Alexander Johnson 1121042880001106 +705Tradersulpher bonus pay for amazing work on #OSS 00010001106 +622231380104728780680527547730000000000#724650820670# Jayden Williams 1121042880001107 +705Roarmad bonus pay for amazing work on #OSS 00010001107 +622231380104426157521711440520000000000#875448701048# Joseph Thomas 1121042880001108 +705Trackcarnation bonus pay for amazing work on #OSS 00010001108 +622231380104743320865500447730000000000#465554475811# Natalie Harris 1121042880001109 +705Lordweed bonus pay for amazing work on #OSS 00010001109 +622231380104628785164503136040000000000#353761552758# Sophia Martinez 1121042880001110 +705Backgeode bonus pay for amazing work on #OSS 00010001110 +622231380104085251564007228220000000000#707831637337# Lily Johnson 1121042880001111 +705Vulturerampant bonus pay for amazing work on #OSS 00010001111 +622231380104868481782827370460000000000#664633612822# Alexander Williams 1121042880001112 +705Leaderstitch bonus pay for amazing work on #OSS 00010001112 +622231380104665764567656426770000000000#756851875752# Jayden Martinez 1121042880001113 +705Cranespiral bonus pay for amazing work on #OSS 00010001113 +622231380104863267827876050310000000000#775472812132# Matthew Taylor 1121042880001114 +705Catblue bonus pay for amazing work on #OSS 00010001114 +622231380104215818054522048150000000000#057705050265# Abigail Davis 1121042880001115 +705Viperspice bonus pay for amazing work on #OSS 00010001115 +622231380104471128684888024640000000000#688781826032# Benjamin Martinez 1121042880001116 +705Ducklake bonus pay for amazing work on #OSS 00010001116 +622231380104633107508163000740000000000#524077751068# David Moore 1121042880001117 +705Singermidnight bonus pay for amazing work on #OSS 00010001117 +622231380104408738338567670180000000000#162845237532# Jayden Thompson 1121042880001118 +705Slicermeadow bonus pay for amazing work on #OSS 00010001118 +622231380104376231113082558540000000000#466544366600# Ethan White 1121042880001119 +705Swisherwalnut bonus pay for amazing work on #OSS 00010001119 +622231380104445032786241105080000000000#342650806180# Jayden Jackson 1121042880001120 +705Moosezircon bonus pay for amazing work on #OSS 00010001120 +622231380104081076481056623560000000000#585450481471# Michael Harris 1121042880001121 +705Chantersunrise bonus pay for amazing work on #OSS 00010001121 +622231380104806348538081781110000000000#610126763377# Olivia Johnson 1121042880001122 +705Frillfancy bonus pay for amazing work on #OSS 00010001122 +622231380104288530774046070280000000000#804271101417# Mason Johnson 1121042880001123 +705Piperbrook bonus pay for amazing work on #OSS 00010001123 +622231380104704845332541211260000000000#202724812866# Ethan Brown 1121042880001124 +705Owlsleet bonus pay for amazing work on #OSS 00010001124 +622231380104485643686541430430000000000#776406237317# Michael Thompson 1121042880001125 +705Wyrmatom bonus pay for amazing work on #OSS 00010001125 +622231380104256544462157355320000000000#210773165432# Daniel Thompson 1121042880001126 +705Killerjewel bonus pay for amazing work on #OSS 00010001126 +622231380104854803468266601030000000000#604412816517# Avery Anderson 1121042880001127 +705Dropcream bonus pay for amazing work on #OSS 00010001127 +622231380104824642344005566370000000000#182517400443# Aubrey Martinez 1121042880001128 +705Mousebone bonus pay for amazing work on #OSS 00010001128 +622231380104445523487481030880000000000#214678320364# Emily Thomas 1121042880001129 +705Warlockebony bonus pay for amazing work on #OSS 00010001129 +622231380104556008221678372050000000000#182782016141# Emma White 1121042880001130 +705Lifterdark bonus pay for amazing work on #OSS 00010001130 +622231380104074884266260686360000000000#111755684415# Mason Martin 1121042880001131 +705Fairycoffee bonus pay for amazing work on #OSS 00010001131 +622231380104867756214808853870000000000#252341288686# Lily Anderson 1121042880001132 +705Ridgehurricane bonus pay for amazing work on #OSS 00010001132 +622231380104413028681203214860000000000#555828687224# James Anderson 1121042880001133 +705Keepersheer bonus pay for amazing work on #OSS 00010001133 +622231380104155565108161681580000000000#422031650276# Avery Harris 1121042880001134 +705Lightningtorch bonus pay for amazing work on #OSS 00010001134 +622231380104753422017468388800000000000#847366442343# Emma Williams 1121042880001135 +705Eyelong bonus pay for amazing work on #OSS 00010001135 +622231380104376038544104372430000000000#061815723433# Daniel Smith 1121042880001136 +705Antelopeoasis bonus pay for amazing work on #OSS 00010001136 +622231380104072465530755655110000000000#531508078653# Andrew Thompson 1121042880001137 +705Browthunder bonus pay for amazing work on #OSS 00010001137 +622231380104513144252525832770000000000#687867777572# Sophia Thomas 1121042880001138 +705Whimseynebula bonus pay for amazing work on #OSS 00010001138 +622231380104211556381087562680000000000#433870028275# Anthony White 1121042880001139 +705Thunderpaper bonus pay for amazing work on #OSS 00010001139 +622231380104063665032151558420000000000#231116604717# Charlotte Harris 1121042880001140 +705Bugjungle bonus pay for amazing work on #OSS 00010001140 +622231380104472722177716001000000000000#725637786871# Zoey White 1121042880001141 +705Walkerfrill bonus pay for amazing work on #OSS 00010001141 +622231380104036028666347527000000000000#357732076657# Daniel Martinez 1121042880001142 +705Dragonglass bonus pay for amazing work on #OSS 00010001142 +622231380104257734231246328010000000000#184638667877# Andrew Wilson 1121042880001143 +705Stingerroot bonus pay for amazing work on #OSS 00010001143 +622231380104876761444274528840000000000#084214046281# Mason Moore 1121042880001144 +705Gemclover bonus pay for amazing work on #OSS 00010001144 +622231380104230353327566488180000000000#788365884573# Jacob Garcia 1121042880001145 +705Swordrelic bonus pay for amazing work on #OSS 00010001145 +622231380104238688361675151170000000000#836318268331# Joshua Robinson 1121042880001146 +705Carverpetal bonus pay for amazing work on #OSS 00010001146 +622231380104387777858574506780000000000#815343225400# Mia Davis 1121042880001147 +705Lightningpalm bonus pay for amazing work on #OSS 00010001147 +622231380104033111172556414000000000000#474673456145# Ethan Robinson 1121042880001148 +705Storkspectrum bonus pay for amazing work on #OSS 00010001148 +622231380104426053168841366620000000000#508264305680# Noah Smith 1121042880001149 +705Shriekerpsychadelic bonus pay for amazing work on #OSS 00010001149 +622231380104762437856261854640000000000#235142755636# Jayden Moore 1121042880001150 +705Witchflannel bonus pay for amazing work on #OSS 00010001150 +622231380104525280175631706440000000000#724630215318# Emily Williams 1121042880001151 +705Shiftmulberry bonus pay for amazing work on #OSS 00010001151 +622231380104030014818422556360000000000#156658353140# Aubrey Williams 1121042880001152 +705Handwell bonus pay for amazing work on #OSS 00010001152 +622231380104840403014170582130000000000#847225130832# Aiden Miller 1121042880001153 +705Ogrecypress bonus pay for amazing work on #OSS 00010001153 +622231380104432420556141703210000000000#674545853430# Mason Thompson 1121042880001154 +705Killerblaze bonus pay for amazing work on #OSS 00010001154 +622231380104000673165823325560000000000#217847035574# Noah Moore 1121042880001155 +705Raccoonpolar bonus pay for amazing work on #OSS 00010001155 +622231380104520624672514885610000000000#140446383284# Lily Moore 1121042880001156 +705Bellfoul bonus pay for amazing work on #OSS 00010001156 +622231380104332772406677188080000000000#474638260880# Mason Jones 1121042880001157 +705Thumbsponge bonus pay for amazing work on #OSS 00010001157 +622231380104848842014405722880000000000#821300012145# Mason Moore 1121042880001158 +705Spikecrack bonus pay for amazing work on #OSS 00010001158 +622231380104227316814051654100000000000#377883004454# Mason Smith 1121042880001159 +705Herocedar bonus pay for amazing work on #OSS 00010001159 +622231380104660677426564163010000000000#452518723761# Elizabeth Jones 1121042880001160 +705Catcomet bonus pay for amazing work on #OSS 00010001160 +622231380104071521845684420060000000000#626732582646# Lily Taylor 1121042880001161 +705Gempsychadelic bonus pay for amazing work on #OSS 00010001161 +622231380104458122885143882700000000000#573360034462# James Martinez 1121042880001162 +705Chillerrust bonus pay for amazing work on #OSS 00010001162 +622231380104282083325873826050000000000#123087876527# Anthony Robinson 1121042880001163 +705Thieftranslucent bonus pay for amazing work on #OSS 00010001163 +622231380104512762875521373470000000000#757363803584# Avery Jones 1121042880001164 +705Servantpickle bonus pay for amazing work on #OSS 00010001164 +622231380104106476388672188570000000000#534223510465# Jayden Martinez 1121042880001165 +705Wolfdiamond bonus pay for amazing work on #OSS 00010001165 +622231380104006512006513157140000000000#614636078487# Abigail Moore 1121042880001166 +705Collarcoffee bonus pay for amazing work on #OSS 00010001166 +622231380104761736334334338450000000000#750428718375# Abigail Brown 1121042880001167 +705Arrowpeppermint bonus pay for amazing work on #OSS 00010001167 +622231380104122288105616060460000000000#847757216782# Addison Jones 1121042880001168 +705Razorjust bonus pay for amazing work on #OSS 00010001168 +622231380104243851626327214340000000000#427434822366# Alexander Martin 1121042880001169 +705Napedaffodil bonus pay for amazing work on #OSS 00010001169 +622231380104011231884520515760000000000#424805455340# Sophia Anderson 1121042880001170 +705Fangbrown bonus pay for amazing work on #OSS 00010001170 +622231380104182536660517624270000000000#412152766382# Jayden Johnson 1121042880001171 +705Snarlflax bonus pay for amazing work on #OSS 00010001171 +622231380104683007888462062610000000000#766322255342# Madison Jackson 1121042880001172 +705Throatdandelion bonus pay for amazing work on #OSS 00010001172 +622231380104850834051183222580000000000#684456417452# Chloe Davis 1121042880001173 +705Musedune bonus pay for amazing work on #OSS 00010001173 +622231380104378025477262520010000000000#615243148380# Benjamin Robinson 1121042880001174 +705Eyedog bonus pay for amazing work on #OSS 00010001174 +622231380104568100556232854720000000000#101184544267# Addison Taylor 1121042880001175 +705Salmonscarlet bonus pay for amazing work on #OSS 00010001175 +622231380104154688541173152630000000000#748033452830# Madison Garcia 1121042880001176 +705Jaguarflicker bonus pay for amazing work on #OSS 00010001176 +622231380104804734340842734840000000000#784486711365# Elijah Brown 1121042880001177 +705Scarerdour bonus pay for amazing work on #OSS 00010001177 +622231380104736814784475768680000000000#282520325335# Jacob Martinez 1121042880001178 +705Zebraglen bonus pay for amazing work on #OSS 00010001178 +622231380104277000031463256880000000000#612205167011# Daniel Brown 1121042880001179 +705Gamblerpolar bonus pay for amazing work on #OSS 00010001179 +622231380104833844747346805580000000000#376803263334# Jayden Garcia 1121042880001180 +705Lanternscarlet bonus pay for amazing work on #OSS 00010001180 +622231380104654453386564360110000000000#271304167812# Ava Jackson 1121042880001181 +705Riderspeckle bonus pay for amazing work on #OSS 00010001181 +622231380104458388271263563180000000000#040152070325# Alexander Wilson 1121042880001182 +705Spiritdent bonus pay for amazing work on #OSS 00010001182 +622231380104125336313712401300000000000#055713005512# Anthony Anderson 1121042880001183 +705Chestsurf bonus pay for amazing work on #OSS 00010001183 +622231380104144423406858011280000000000#628058071458# Michael Moore 1121042880001184 +705Butterflymango bonus pay for amazing work on #OSS 00010001184 +622231380104760648776503808350000000000#201724255002# Isabella Brown 1121042880001185 +705Soarergiant bonus pay for amazing work on #OSS 00010001185 +622231380104652526027774347120000000000#253327844554# Alexander Brown 1121042880001186 +705Raptorjust bonus pay for amazing work on #OSS 00010001186 +622231380104084524311714751230000000000#874073201451# Elijah Taylor 1121042880001187 +705Fighterblossom bonus pay for amazing work on #OSS 00010001187 +622231380104342536481088075700000000000#621243286224# Mason Miller 1121042880001188 +705Chestspring bonus pay for amazing work on #OSS 00010001188 +622231380104671523003201531330000000000#617434852584# Zoey Jones 1121042880001189 +705Skinnerdew bonus pay for amazing work on #OSS 00010001189 +622231380104560375016352887160000000000#085800353201# Avery Wilson 1121042880001190 +705Ocelotink bonus pay for amazing work on #OSS 00010001190 +622231380104640536667561463050000000000#658844072058# William Jones 1121042880001191 +705Shakerpaint bonus pay for amazing work on #OSS 00010001191 +622231380104045386456285467640000000000#240784355284# Ethan Wilson 1121042880001192 +705Heronspring bonus pay for amazing work on #OSS 00010001192 +622231380104128008435143624200000000000#671451248757# Jacob Robinson 1121042880001193 +705Baneglow bonus pay for amazing work on #OSS 00010001193 +622231380104351805788626610250000000000#314180763785# Liam Martin 1121042880001194 +705Bitebloom bonus pay for amazing work on #OSS 00010001194 +622231380104586320543257320570000000000#335708415256# Emma Thompson 1121042880001195 +705Shieldchisel bonus pay for amazing work on #OSS 00010001195 +622231380104600344476613515060000000000#413042284038# Charlotte Thompson 1121042880001196 +705Pegasustabby bonus pay for amazing work on #OSS 00010001196 +622231380104710554352311014720000000000#128861341063# Elijah Jones 1121042880001197 +705Chatterwell bonus pay for amazing work on #OSS 00010001197 +622231380104477014016511226000000000000#780245642243# David Miller 1121042880001198 +705Cloaksprout bonus pay for amazing work on #OSS 00010001198 +622231380104074321031101711060000000000#758777266760# Anthony Moore 1121042880001199 +705Forgerpaint bonus pay for amazing work on #OSS 00010001199 +622231380104488263744118618550000000000#481655483688# Sophia Davis 1121042880001200 +705Deathlove bonus pay for amazing work on #OSS 00010001200 +622231380104536028050354268300000000000#427873831824# Elijah Smith 1121042880001201 +705Roverclear bonus pay for amazing work on #OSS 00010001201 +622231380104838844011455475580000000000#460884776216# Aiden Harris 1121042880001202 +705Centaurfern bonus pay for amazing work on #OSS 00010001202 +622231380104258651866362521560000000000#585707125217# Avery Smith 1121042880001203 +705Hideequinox bonus pay for amazing work on #OSS 00010001203 +622231380104517560206415816770000000000#314506762240# Avery Robinson 1121042880001204 +705Devourervine bonus pay for amazing work on #OSS 00010001204 +622231380104567221220346753710000000000#278560422771# Elizabeth Williams 1121042880001205 +705Serpentdew bonus pay for amazing work on #OSS 00010001205 +622231380104412808723065356580000000000#338376656487# Isabella Williams 1121042880001206 +705Cockatoofringe bonus pay for amazing work on #OSS 00010001206 +622231380104343657674485641420000000000#707848413827# David Jackson 1121042880001207 +705Burnalabaster bonus pay for amazing work on #OSS 00010001207 +622231380104115741883348466610000000000#757403216506# Jacob Anderson 1121042880001208 +705Dragonscratch bonus pay for amazing work on #OSS 00010001208 +622231380104057487366353860340000000000#632840480452# Abigail Brown 1121042880001209 +705Backsplit bonus pay for amazing work on #OSS 00010001209 +622231380104543746087318118830000000000#600482253054# Daniel Anderson 1121042880001210 +705Slothmangrove bonus pay for amazing work on #OSS 00010001210 +622231380104044161518516031080000000000#833788405433# Emma Taylor 1121042880001211 +705Carplime bonus pay for amazing work on #OSS 00010001211 +622231380104515677263415346530000000000#613517060255# Avery Wilson 1121042880001212 +705Skinnergeode bonus pay for amazing work on #OSS 00010001212 +622231380104060103170362501330000000000#127361161351# Ella White 1121042880001213 +705Donkeysulpher bonus pay for amazing work on #OSS 00010001213 +622231380104805585014101505680000000000#662483856222# Jayden Thompson 1121042880001214 +705Dancercitrine bonus pay for amazing work on #OSS 00010001214 +622231380104616470473584827040000000000#720714106266# Avery Williams 1121042880001215 +705Knifewood bonus pay for amazing work on #OSS 00010001215 +622231380104717453036034843800000000000#172350773115# Benjamin Jones 1121042880001216 +705Mouseevening bonus pay for amazing work on #OSS 00010001216 +622231380104434281643251536420000000000#156627848528# Olivia Brown 1121042880001217 +705Mythsnapdragon bonus pay for amazing work on #OSS 00010001217 +622231380104814343177867481270000000000#622515668086# Benjamin Moore 1121042880001218 +705Lightersprout bonus pay for amazing work on #OSS 00010001218 +622231380104265131541648875710000000000#513284317320# William Garcia 1121042880001219 +705Stingrain bonus pay for amazing work on #OSS 00010001219 +622231380104403826603326484710000000000#412550438013# Jacob Wilson 1121042880001220 +705Pumabutton bonus pay for amazing work on #OSS 00010001220 +622231380104441127274304151310000000000#422054175573# Ethan Harris 1121042880001221 +705Bitegreat bonus pay for amazing work on #OSS 00010001221 +622231380104701872541608568100000000000#107162003131# Noah Jackson 1121042880001222 +705Flashertide bonus pay for amazing work on #OSS 00010001222 +622231380104823846716617246110000000000#528287006481# Mia Martin 1121042880001223 +705Snapperweak bonus pay for amazing work on #OSS 00010001223 +622231380104762525353156542140000000000#104377082421# William Robinson 1121042880001224 +705Dragonglen bonus pay for amazing work on #OSS 00010001224 +622231380104327215132431228230000000000#573170566511# Joshua Thomas 1121042880001225 +705Griffinkiwi bonus pay for amazing work on #OSS 00010001225 +622231380104186273046366854660000000000#777350773032# Benjamin Wilson 1121042880001226 +705Maskrampant bonus pay for amazing work on #OSS 00010001226 +622231380104305666272123160480000000000#363543531007# Isabella Anderson 1121042880001227 +705Stealercomet bonus pay for amazing work on #OSS 00010001227 +622231380104848306724433444600000000000#458835874566# Joseph Jackson 1121042880001228 +705Seekereast bonus pay for amazing work on #OSS 00010001228 +622231380104014485366657338500000000000#584640183156# Alexander Taylor 1121042880001229 +705Hooffancy bonus pay for amazing work on #OSS 00010001229 +622231380104411851270651238270000000000#234513045068# Alexander Brown 1121042880001230 +705Herohoneysuckle bonus pay for amazing work on #OSS 00010001230 +622231380104520152323586601230000000000#820875660371# Isabella Taylor 1121042880001231 +705Wizardchrome bonus pay for amazing work on #OSS 00010001231 +622231380104284822200133855460000000000#562666183038# Ella Moore 1121042880001232 +705Lynxbristle bonus pay for amazing work on #OSS 00010001232 +622231380104621743131487483020000000000#031241140274# Benjamin White 1121042880001233 +705Mooseshell bonus pay for amazing work on #OSS 00010001233 +622231380104831257632568856300000000000#648613441236# Sofia Williams 1121042880001234 +705Bellydesert bonus pay for amazing work on #OSS 00010001234 +622231380104516807066582116760000000000#147644057608# Addison Wilson 1121042880001235 +705Ladygiant bonus pay for amazing work on #OSS 00010001235 +622231380104754740134452282010000000000#675563765786# Matthew Smith 1121042880001236 +705Handshard bonus pay for amazing work on #OSS 00010001236 +622231380104171883507823752620000000000#231583863731# Joseph Robinson 1121042880001237 +705Scarwild bonus pay for amazing work on #OSS 00010001237 +622231380104011401816500138070000000000#700050442567# Sophia Miller 1121042880001238 +705Graspriver bonus pay for amazing work on #OSS 00010001238 +622231380104478060436675770520000000000#856336807030# Daniel Martin 1121042880001239 +705Browjet bonus pay for amazing work on #OSS 00010001239 +622231380104247762271778305520000000000#017281431436# Emma Moore 1121042880001240 +705Gorillarowan bonus pay for amazing work on #OSS 00010001240 +622231380104356068137246751300000000000#181537318124# Isabella Robinson 1121042880001241 +705Skullseed bonus pay for amazing work on #OSS 00010001241 +622231380104086846565884078400000000000#825432584827# Ava Williams 1121042880001242 +705Pantherpurple bonus pay for amazing work on #OSS 00010001242 +622231380104170451370341108210000000000#506602483818# Avery Miller 1121042880001243 +705Buffalofoul bonus pay for amazing work on #OSS 00010001243 +622231380104741831165576187810000000000#860143565077# Aubrey Wilson 1121042880001244 +705Shiftmaple bonus pay for amazing work on #OSS 00010001244 +622231380104182125058608372800000000000#512275671725# Elijah White 1121042880001245 +705Pixiemango bonus pay for amazing work on #OSS 00010001245 +622231380104418357012666101520000000000#578817744376# Daniel Anderson 1121042880001246 +705Lorddune bonus pay for amazing work on #OSS 00010001246 +622231380104454124806352015740000000000#882128682314# Addison Martin 1121042880001247 +705Hissflame bonus pay for amazing work on #OSS 00010001247 +622231380104630303268731314100000000000#047528262433# Joshua Martinez 1121042880001248 +705Guardianpaint bonus pay for amazing work on #OSS 00010001248 +622231380104528224683617541630000000000#568566602561# Lily Taylor 1121042880001249 +705Weaverspeckle bonus pay for amazing work on #OSS 00010001249 +622231380104608462031036343630000000000#880311072130# Matthew Jackson 1121042880001250 +705Kangaroomud bonus pay for amazing work on #OSS 00010001250 +82000025008922512500000000000000000000000000121042882 121042880000002 +5200Wells Fargo 121042882 PPDTrans. Des 200118 1121042880000003 +622231380104087777145318638830000000000#751524448457# Andrew Anderson 1121042880000001 +705Fangvivid bonus pay for amazing work on #OSS 00010000001 +622231380104618653816782848080000000000#305008243835# Mia Harris 1121042880000002 +705Ridercrystal bonus pay for amazing work on #OSS 00010000002 +622231380104680052480336847260000000000#478166625722# Abigail White 1121042880000003 +705Wolfdent bonus pay for amazing work on #OSS 00010000003 +622231380104771184032052643610000000000#645420273311# Emma White 1121042880000004 +705Flyplatinum bonus pay for amazing work on #OSS 00010000004 +622231380104826702210027030430000000000#348266265783# Abigail Moore 1121042880000005 +705Gorillabitter bonus pay for amazing work on #OSS 00010000005 +622231380104822404551613830320000000000#662831665234# Lily Davis 1121042880000006 +705Jawalder bonus pay for amazing work on #OSS 00010000006 +622231380104053181406216465580000000000#816853744611# Aubrey Jackson 1121042880000007 +705Grinjungle bonus pay for amazing work on #OSS 00010000007 +622231380104443041172862126560000000000#406571020873# Emily Johnson 1121042880000008 +705Jaywave bonus pay for amazing work on #OSS 00010000008 +622231380104575502261170257350000000000#353800683876# Emma Johnson 1121042880000009 +705Scorpionsleet bonus pay for amazing work on #OSS 00010000009 +622231380104825162631402366310000000000#256626543887# Sofia Robinson 1121042880000010 +705Chantershade bonus pay for amazing work on #OSS 00010000010 +622231380104173568664408005820000000000#877546003786# Elijah White 1121042880000011 +705Questercarnelian bonus pay for amazing work on #OSS 00010000011 +622231380104022828182711878150000000000#834114456608# Ava Jackson 1121042880000012 +705Beetlefancy bonus pay for amazing work on #OSS 00010000012 +622231380104118567042068076720000000000#117551141682# Avery White 1121042880000013 +705Sightfir bonus pay for amazing work on #OSS 00010000013 +622231380104782447876866581040000000000#311854254513# James Martinez 1121042880000014 +705Butterflydirt bonus pay for amazing work on #OSS 00010000014 +622231380104824188533276255760000000000#444645158347# Emma Harris 1121042880000015 +705Braidlong bonus pay for amazing work on #OSS 00010000015 +622231380104840407813631702340000000000#504050846502# Chloe Robinson 1121042880000016 +705Turnerfoil bonus pay for amazing work on #OSS 00010000016 +622231380104356002468753783140000000000#246048446880# Emma Davis 1121042880000017 +705Skullglen bonus pay for amazing work on #OSS 00010000017 +622231380104112023486433255550000000000#161321311677# James Martinez 1121042880000018 +705Centaurtree bonus pay for amazing work on #OSS 00010000018 +622231380104001407472433426830000000000#033484463717# Emily Brown 1121042880000019 +705Cloudcream bonus pay for amazing work on #OSS 00010000019 +622231380104304111257317276560000000000#865240560552# Mia Brown 1121042880000020 +705Mindspice bonus pay for amazing work on #OSS 00010000020 +622231380104216266064115701080000000000#022521713582# Alexander Anderson 1121042880000021 +705Pythonazure bonus pay for amazing work on #OSS 00010000021 +622231380104260148833846425580000000000#656252413574# Ava Martin 1121042880000022 +705Flashermotley bonus pay for amazing work on #OSS 00010000022 +622231380104125276817575773300000000000#378301353151# Jacob Garcia 1121042880000023 +705Bowcloud bonus pay for amazing work on #OSS 00010000023 +622231380104462586786063584300000000000#755505644880# Aiden White 1121042880000024 +705Roverfield bonus pay for amazing work on #OSS 00010000024 +622231380104064842037340037300000000000#202233355708# Abigail Johnson 1121042880000025 +705Spiritbold bonus pay for amazing work on #OSS 00010000025 +622231380104585354222668444840000000000#184270205238# Zoey Martinez 1121042880000026 +705Neckjet bonus pay for amazing work on #OSS 00010000026 +622231380104215736140777287640000000000#674202406143# William Jones 1121042880000027 +705Fighterwinter bonus pay for amazing work on #OSS 00010000027 +622231380104536815584127132760000000000#760850375050# Emily Anderson 1121042880000028 +705Yaklace bonus pay for amazing work on #OSS 00010000028 +622231380104083736070165688180000000000#147036183683# Charlotte Miller 1121042880000029 +705Ripperpie bonus pay for amazing work on #OSS 00010000029 +622231380104177678373117545350000000000#447018304350# Emma Williams 1121042880000030 +705Lanternrose bonus pay for amazing work on #OSS 00010000030 +622231380104078233806824240020000000000#138236051361# David Thomas 1121042880000031 +705Bonesand bonus pay for amazing work on #OSS 00010000031 +622231380104053507202835276040000000000#032333012341# Noah Taylor 1121042880000032 +705Cloakred bonus pay for amazing work on #OSS 00010000032 +622231380104272388880335522370000000000#826212240820# Ethan Williams 1121042880000033 +705Binderbrook bonus pay for amazing work on #OSS 00010000033 +622231380104277143466532685010000000000#770850421084# Jacob White 1121042880000034 +705Deathsummer bonus pay for amazing work on #OSS 00010000034 +622231380104881854572064854330000000000#400204468380# Daniel Thomas 1121042880000035 +705Owlsouth bonus pay for amazing work on #OSS 00010000035 +622231380104681851636678808880000000000#462738644868# Andrew Johnson 1121042880000036 +705Talonpower bonus pay for amazing work on #OSS 00010000036 +622231380104202312011261744830000000000#733548775102# Addison White 1121042880000037 +705Stormwell bonus pay for amazing work on #OSS 00010000037 +622231380104056770134877107020000000000#644676100522# David Smith 1121042880000038 +705Oriolesticky bonus pay for amazing work on #OSS 00010000038 +622231380104683208354573033500000000000#784140215467# Anthony Thompson 1121042880000039 +705Oriolewind bonus pay for amazing work on #OSS 00010000039 +622231380104432161880652455860000000000#658104711836# Joshua Johnson 1121042880000040 +705Thieffierce bonus pay for amazing work on #OSS 00010000040 +622231380104142241452141051260000000000#123021642288# David Robinson 1121042880000041 +705Healerfast bonus pay for amazing work on #OSS 00010000041 +622231380104017127482734242050000000000#034225624332# Matthew Thomas 1121042880000042 +705Pixieband bonus pay for amazing work on #OSS 00010000042 +622231380104858372573403465720000000000#047118288873# William Garcia 1121042880000043 +705Crystalcrack bonus pay for amazing work on #OSS 00010000043 +622231380104518084444137866430000000000#044644356033# Mia Harris 1121042880000044 +705Heromesquite bonus pay for amazing work on #OSS 00010000044 +622231380104476224007640442360000000000#373207241665# Sophia Harris 1121042880000045 +705Viperrazor bonus pay for amazing work on #OSS 00010000045 +622231380104510850731187358850000000000#632433717717# Andrew Moore 1121042880000046 +705Glasscherry bonus pay for amazing work on #OSS 00010000046 +622231380104520564431157050300000000000#151122613601# Michael Martin 1121042880000047 +705Swoopstream bonus pay for amazing work on #OSS 00010000047 +622231380104347420588881283850000000000#107458401444# Michael Wilson 1121042880000048 +705Fingerlie bonus pay for amazing work on #OSS 00010000048 +622231380104878670641876215350000000000#551427852504# Joseph Martinez 1121042880000049 +705Faceglow bonus pay for amazing work on #OSS 00010000049 +622231380104828535665551827020000000000#605705787212# Abigail Garcia 1121042880000050 +705Dukehollow bonus pay for amazing work on #OSS 00010000050 +622231380104174547771302367240000000000#812132028250# Isabella Anderson 1121042880000051 +705Headrowan bonus pay for amazing work on #OSS 00010000051 +622231380104081046581877846270000000000#210186147104# Ava Johnson 1121042880000052 +705Scorpionspring bonus pay for amazing work on #OSS 00010000052 +622231380104210406336027225730000000000#744062207075# Emma Martin 1121042880000053 +705Trackermidnight bonus pay for amazing work on #OSS 00010000053 +622231380104061648138537063020000000000#254064461624# William Jones 1121042880000054 +705Arrowlizard bonus pay for amazing work on #OSS 00010000054 +622231380104303542224605675680000000000#165345232413# Elijah Anderson 1121042880000055 +705Terrierred bonus pay for amazing work on #OSS 00010000055 +622231380104888322614004183680000000000#887133102215# Avery Williams 1121042880000056 +705Spritehoneysuckle bonus pay for amazing work on #OSS 00010000056 +622231380104226158472628468060000000000#454320227430# Madison Garcia 1121042880000057 +705Crafteryellow bonus pay for amazing work on #OSS 00010000057 +622231380104064345675585425410000000000#607675672028# Aiden White 1121042880000058 +705Wyrmscratch bonus pay for amazing work on #OSS 00010000058 +622231380104783144414588322830000000000#353777067427# Ava Brown 1121042880000059 +705Molesolar bonus pay for amazing work on #OSS 00010000059 +622231380104314577866401485340000000000#013642431868# Liam Martin 1121042880000060 +705Butterflycitrine bonus pay for amazing work on #OSS 00010000060 +622231380104262488572151374710000000000#850104620554# Noah Martin 1121042880000061 +705Stagrogue bonus pay for amazing work on #OSS 00010000061 +622231380104408682041621282010000000000#730586526466# Ethan Thompson 1121042880000062 +705Pegasusbevel bonus pay for amazing work on #OSS 00010000062 +622231380104468403853510775540000000000#154776752461# Madison Martin 1121042880000063 +705Playerwarp bonus pay for amazing work on #OSS 00010000063 +622231380104142347785652227640000000000#413764086411# Jacob Robinson 1121042880000064 +705Knaveprong bonus pay for amazing work on #OSS 00010000064 +622231380104137768865635318100000000000#562588354732# Zoey Martin 1121042880000065 +705Ringermorning bonus pay for amazing work on #OSS 00010000065 +622231380104268466200383384720000000000#707682615611# Abigail Brown 1121042880000066 +705Roarlead bonus pay for amazing work on #OSS 00010000066 +622231380104211530226150822030000000000#725488732267# William Jackson 1121042880000067 +705Razorsulpher bonus pay for amazing work on #OSS 00010000067 +622231380104826058546278660260000000000#025608562674# Zoey Miller 1121042880000068 +705Chopperveil bonus pay for amazing work on #OSS 00010000068 +622231380104256082188627105780000000000#671627853232# Joshua Taylor 1121042880000069 +705Pawripple bonus pay for amazing work on #OSS 00010000069 +622231380104557662083421452220000000000#825647814267# Sofia Smith 1121042880000070 +705Throatsprout bonus pay for amazing work on #OSS 00010000070 +622231380104230670880681568740000000000#741620251024# James Robinson 1121042880000071 +705Snagglefootheather bonus pay for amazing work on #OSS 00010000071 +622231380104643501186845035170000000000#870631161033# Isabella Robinson 1121042880000072 +705Vulturetwisty bonus pay for amazing work on #OSS 00010000072 +622231380104372203774526038670000000000#075071332137# Matthew Jones 1121042880000073 +705Parrotsapphire bonus pay for amazing work on #OSS 00010000073 +622231380104775834736756157550000000000#826156280352# William Taylor 1121042880000074 +705Razorchestnut bonus pay for amazing work on #OSS 00010000074 +622231380104377261412268211040000000000#636767331588# Charlotte Wilson 1121042880000075 +705Flierplanet bonus pay for amazing work on #OSS 00010000075 +622231380104525661333834574800000000000#445762540365# Aubrey Jones 1121042880000076 +705Cougarwind bonus pay for amazing work on #OSS 00010000076 +622231380104351184306786685270000000000#720806736377# Elijah Brown 1121042880000077 +705Fairylinen bonus pay for amazing work on #OSS 00010000077 +622231380104271276117133483850000000000#777863361114# Abigail Taylor 1121042880000078 +705Headglitter bonus pay for amazing work on #OSS 00010000078 +622231380104024668504144622800000000000#205656421800# Ava Garcia 1121042880000079 +705Bisonspring bonus pay for amazing work on #OSS 00010000079 +622231380104366885653525486710000000000#386185602223# Ella Garcia 1121042880000080 +705Warlockcandy bonus pay for amazing work on #OSS 00010000080 +622231380104250560811442246340000000000#055700267776# Zoey Taylor 1121042880000081 +705Shakerbronze bonus pay for amazing work on #OSS 00010000081 +622231380104434180632516716580000000000#786087304050# Elijah Garcia 1121042880000082 +705Fanggrove bonus pay for amazing work on #OSS 00010000082 +622231380104742635871140500370000000000#216175401535# Isabella Moore 1121042880000083 +705Dukesour bonus pay for amazing work on #OSS 00010000083 +622231380104722308027858223240000000000#502148506773# Mason Martin 1121042880000084 +705Wyrmgrave bonus pay for amazing work on #OSS 00010000084 +622231380104116865037807106870000000000#770137567501# Jayden Brown 1121042880000085 +705Bellydeep bonus pay for amazing work on #OSS 00010000085 +622231380104853200867145063730000000000#721126100175# Sophia Taylor 1121042880000086 +705Swordflax bonus pay for amazing work on #OSS 00010000086 +622231380104207820553607042200000000000#240271484305# Ethan Smith 1121042880000087 +705Twisternavy bonus pay for amazing work on #OSS 00010000087 +622231380104684732886581230060000000000#611872305265# Ella Wilson 1121042880000088 +705Witchpetal bonus pay for amazing work on #OSS 00010000088 +622231380104345521618275471210000000000#235771553321# Benjamin Harris 1121042880000089 +705Paladinhoney bonus pay for amazing work on #OSS 00010000089 +622231380104057421882544833840000000000#668642608058# Ella Moore 1121042880000090 +705Followerrampant bonus pay for amazing work on #OSS 00010000090 +622231380104755574034461002760000000000#731023443760# William Brown 1121042880000091 +705Puppysplit bonus pay for amazing work on #OSS 00010000091 +622231380104486813862420122150000000000#701726244577# Alexander Miller 1121042880000092 +705Shirtpaper bonus pay for amazing work on #OSS 00010000092 +622231380104012210416481825150000000000#764134333522# Sofia Johnson 1121042880000093 +705Swoopfancy bonus pay for amazing work on #OSS 00010000093 +622231380104056715040447834620000000000#563744336241# Matthew Wilson 1121042880000094 +705Centaurproud bonus pay for amazing work on #OSS 00010000094 +622231380104610865064203562240000000000#255548104715# James Thomas 1121042880000095 +705Seerhail bonus pay for amazing work on #OSS 00010000095 +622231380104605452723187168470000000000#108287285220# William Taylor 1121042880000096 +705Doomkiwi bonus pay for amazing work on #OSS 00010000096 +622231380104752660361348632040000000000#833548075617# Isabella Wilson 1121042880000097 +705Snakeglitter bonus pay for amazing work on #OSS 00010000097 +622231380104644012571410853510000000000#670616875842# Joshua Martinez 1121042880000098 +705Pipersplit bonus pay for amazing work on #OSS 00010000098 +622231380104744738852760521280000000000#570424651754# Ella Miller 1121042880000099 +705Pumajelly bonus pay for amazing work on #OSS 00010000099 +622231380104623628718117805480000000000#443345462620# Addison Taylor 1121042880000100 +705Diversprinkle bonus pay for amazing work on #OSS 00010000100 +622231380104045502300846103050000000000#436827575385# Aubrey Thompson 1121042880000101 +705Princeweak bonus pay for amazing work on #OSS 00010000101 +622231380104307463665850028320000000000#834401603667# James Martinez 1121042880000102 +705Snoutfree bonus pay for amazing work on #OSS 00010000102 +622231380104143826512618526700000000000#662435508060# Jacob Martin 1121042880000103 +705Seekercord bonus pay for amazing work on #OSS 00010000103 +622231380104323700383838402780000000000#544043158868# Emily Moore 1121042880000104 +705Swisherpetal bonus pay for amazing work on #OSS 00010000104 +622231380104233745604138682220000000000#468751308254# Natalie Miller 1121042880000105 +705Swisherlake bonus pay for amazing work on #OSS 00010000105 +622231380104586217406003218440000000000#513730545452# Madison Davis 1121042880000106 +705Collardaffodil bonus pay for amazing work on #OSS 00010000106 +622231380104352053638316835810000000000#576162041745# Jayden Anderson 1121042880000107 +705Lizardquick bonus pay for amazing work on #OSS 00010000107 +622231380104672428753321037780000000000#707208137645# Madison Martinez 1121042880000108 +705Traderancient bonus pay for amazing work on #OSS 00010000108 +622231380104432144464605180010000000000#687002571410# Noah Anderson 1121042880000109 +705Heroncold bonus pay for amazing work on #OSS 00010000109 +622231380104152306164743442220000000000#061537188143# Avery Thompson 1121042880000110 +705Weedapricot bonus pay for amazing work on #OSS 00010000110 +622231380104282626638715127410000000000#462508117182# Elizabeth Wilson 1121042880000111 +705Shiftindigo bonus pay for amazing work on #OSS 00010000111 +622231380104160617502728823210000000000#356638524181# David Jackson 1121042880000112 +705Toucanclever bonus pay for amazing work on #OSS 00010000112 +622231380104405007141711851100000000000#468684026565# Ella Robinson 1121042880000113 +705Moleround bonus pay for amazing work on #OSS 00010000113 +622231380104452720402575444000000000000#315005152801# Avery Harris 1121042880000114 +705Killerfog bonus pay for amazing work on #OSS 00010000114 +622231380104058121841758845160000000000#480860763833# Emily Garcia 1121042880000115 +705Otterblack bonus pay for amazing work on #OSS 00010000115 +622231380104752631121847184680000000000#706431821582# Joseph Williams 1121042880000116 +705Liftershore bonus pay for amazing work on #OSS 00010000116 +622231380104866348260346137510000000000#261342683250# Sofia Thompson 1121042880000117 +705Faceapple bonus pay for amazing work on #OSS 00010000117 +622231380104236326085347208170000000000#584514730240# Sophia Moore 1121042880000118 +705Ocelotbrick bonus pay for amazing work on #OSS 00010000118 +622231380104850151546883670700000000000#657443770310# Matthew Smith 1121042880000119 +705Skinnersun bonus pay for amazing work on #OSS 00010000119 +622231380104767503487352637080000000000#487878256230# Natalie Williams 1121042880000120 +705Pythondew bonus pay for amazing work on #OSS 00010000120 +622231380104044667206255751410000000000#831008622187# Liam Jackson 1121042880000121 +705Jawflax bonus pay for amazing work on #OSS 00010000121 +622231380104563082577620074120000000000#418142713055# Ava Harris 1121042880000122 +705Serpenthazel bonus pay for amazing work on #OSS 00010000122 +622231380104787636271731543850000000000#552734464613# Elijah Davis 1121042880000123 +705Dogbattle bonus pay for amazing work on #OSS 00010000123 +622231380104061217241332010130000000000#867655126043# Mia Martinez 1121042880000124 +705Stonelead bonus pay for amazing work on #OSS 00010000124 +622231380104360004728611184530000000000#156132237615# Michael Jackson 1121042880000125 +705Deathblossom bonus pay for amazing work on #OSS 00010000125 +622231380104622003856350025760000000000#748455508888# Joseph Thomas 1121042880000126 +705Ladyquill bonus pay for amazing work on #OSS 00010000126 +622231380104303106511755812160000000000#275737147780# Emily Martin 1121042880000127 +705Fishsmall bonus pay for amazing work on #OSS 00010000127 +622231380104666700350616070270000000000#138581140536# Michael Taylor 1121042880000128 +705Singerpepper bonus pay for amazing work on #OSS 00010000128 +622231380104243164456508603260000000000#400333004575# Alexander Thomas 1121042880000129 +705Jestersprout bonus pay for amazing work on #OSS 00010000129 +622231380104810775517112510730000000000#480010172667# Mia Brown 1121042880000130 +705Huggersplash bonus pay for amazing work on #OSS 00010000130 +622231380104778406470473674350000000000#327262622370# Noah Jones 1121042880000131 +705Goatsilk bonus pay for amazing work on #OSS 00010000131 +622231380104117178362150635480000000000#564130578662# Jayden Jones 1121042880000132 +705Ogrerift bonus pay for amazing work on #OSS 00010000132 +622231380104425853822171052420000000000#223177734566# Madison Davis 1121042880000133 +705Kittenfield bonus pay for amazing work on #OSS 00010000133 +622231380104577557285584085250000000000#287200066704# Sofia Johnson 1121042880000134 +705Storkoasis bonus pay for amazing work on #OSS 00010000134 +622231380104546135458561568300000000000#848735361821# Liam Miller 1121042880000135 +705Koalalunar bonus pay for amazing work on #OSS 00010000135 +622231380104321213371617774370000000000#887564462853# David Jackson 1121042880000136 +705Snarlcarnation bonus pay for amazing work on #OSS 00010000136 +622231380104620770711821580330000000000#102387102160# Jacob Martin 1121042880000137 +705Dartaquamarine bonus pay for amazing work on #OSS 00010000137 +622231380104438786112852100440000000000#736152514280# Olivia Johnson 1121042880000138 +705Talonpower bonus pay for amazing work on #OSS 00010000138 +622231380104504172038136713770000000000#352808276203# Madison Taylor 1121042880000139 +705Wyrmsmall bonus pay for amazing work on #OSS 00010000139 +622231380104013331211586888740000000000#586811487672# Avery Williams 1121042880000140 +705Lynxrampant bonus pay for amazing work on #OSS 00010000140 +622231380104042437132430173800000000000#468175126201# Joseph Moore 1121042880000141 +705Hoofsunset bonus pay for amazing work on #OSS 00010000141 +622231380104622161728281373500000000000#703816107226# Ethan Thompson 1121042880000142 +705Walkerobsidian bonus pay for amazing work on #OSS 00010000142 +622231380104508362685547518430000000000#870120525380# Ella Harris 1121042880000143 +705Twistergem bonus pay for amazing work on #OSS 00010000143 +622231380104820245181762416160000000000#103667007683# Olivia Robinson 1121042880000144 +705Lizardcute bonus pay for amazing work on #OSS 00010000144 +622231380104248428118355463620000000000#503822638675# Ella Robinson 1121042880000145 +705Zebraclever bonus pay for amazing work on #OSS 00010000145 +622231380104868884444833355800000000000#342257751286# Elizabeth Wilson 1121042880000146 +705Banevine bonus pay for amazing work on #OSS 00010000146 +622231380104112724684073733070000000000#850587008655# Abigail Miller 1121042880000147 +705Knifetree bonus pay for amazing work on #OSS 00010000147 +622231380104478023127825670660000000000#777406135518# Isabella Davis 1121042880000148 +705Gemperidot bonus pay for amazing work on #OSS 00010000148 +622231380104821220281456335530000000000#367462432310# Joseph Brown 1121042880000149 +705Lionbog bonus pay for amazing work on #OSS 00010000149 +622231380104082307853523447400000000000#575271305052# Charlotte Taylor 1121042880000150 +705Stormripple bonus pay for amazing work on #OSS 00010000150 +622231380104045008486616081500000000000#010820331158# Liam Harris 1121042880000151 +705Samuraicookie bonus pay for amazing work on #OSS 00010000151 +622231380104422785857287243430000000000#121262136076# Zoey Davis 1121042880000152 +705Spidershard bonus pay for amazing work on #OSS 00010000152 +622231380104036317116817070030000000000#234818721842# Elijah Taylor 1121042880000153 +705Ladydenim bonus pay for amazing work on #OSS 00010000153 +622231380104775145001340128870000000000#045344713167# Noah Thomas 1121042880000154 +705Doglegend bonus pay for amazing work on #OSS 00010000154 +622231380104464214204171223320000000000#058450320658# Charlotte Taylor 1121042880000155 +705Grinleaf bonus pay for amazing work on #OSS 00010000155 +622231380104640238340808714800000000000#127701314355# Anthony Martin 1121042880000156 +705Waspbird bonus pay for amazing work on #OSS 00010000156 +622231380104737684503731317610000000000#761756865284# Joseph Miller 1121042880000157 +705Thornlace bonus pay for amazing work on #OSS 00010000157 +622231380104313743173686431000000000000#061584041680# Charlotte Martin 1121042880000158 +705Viperchatter bonus pay for amazing work on #OSS 00010000158 +622231380104678684646251824380000000000#616470030358# Ethan Jackson 1121042880000159 +705Puppyagate bonus pay for amazing work on #OSS 00010000159 +622231380104673302471141057660000000000#234627242078# Ella Johnson 1121042880000160 +705Legendgranite bonus pay for amazing work on #OSS 00010000160 +622231380104432446645206573860000000000#431126352757# Sophia Martin 1121042880000161 +705Graspfantasy bonus pay for amazing work on #OSS 00010000161 +622231380104880344541704107230000000000#616818326788# Emma Thompson 1121042880000162 +705Reaperchocolate bonus pay for amazing work on #OSS 00010000162 +622231380104555371321415437350000000000#186502876081# Jacob Davis 1121042880000163 +705Taildandy bonus pay for amazing work on #OSS 00010000163 +622231380104252675774286433350000000000#877780555683# Sophia Smith 1121042880000164 +705Slayergrove bonus pay for amazing work on #OSS 00010000164 +622231380104027217353030328180000000000#651771406785# Mia Williams 1121042880000165 +705Roarerchestnut bonus pay for amazing work on #OSS 00010000165 +622231380104717880327610157450000000000#147867241302# Mia Taylor 1121042880000166 +705Duckgeode bonus pay for amazing work on #OSS 00010000166 +622231380104186803084488373240000000000#761311084556# Abigail White 1121042880000167 +705Pigfree bonus pay for amazing work on #OSS 00010000167 +622231380104584047722403708850000000000#135673463386# Jayden Brown 1121042880000168 +705Parrotpond bonus pay for amazing work on #OSS 00010000168 +622231380104448888048356308020000000000#187242081788# Joseph Johnson 1121042880000169 +705Divetwilight bonus pay for amazing work on #OSS 00010000169 +622231380104120300350663468820000000000#415778237827# Avery Robinson 1121042880000170 +705Ladyripple bonus pay for amazing work on #OSS 00010000170 +622231380104845113221547148700000000000#108263541418# Elizabeth Miller 1121042880000171 +705Iguanasilent bonus pay for amazing work on #OSS 00010000171 +622231380104883341610000045250000000000#084136406124# James Johnson 1121042880000172 +705Dartgrave bonus pay for amazing work on #OSS 00010000172 +622231380104720132155666370330000000000#200230836046# Matthew Wilson 1121042880000173 +705Flyiridescent bonus pay for amazing work on #OSS 00010000173 +622231380104001843434367386610000000000#366566434285# Andrew Jackson 1121042880000174 +705Geckosky bonus pay for amazing work on #OSS 00010000174 +622231380104447858028031071120000000000#233326311506# Andrew Davis 1121042880000175 +705Cougarthread bonus pay for amazing work on #OSS 00010000175 +622231380104133727816777423380000000000#200825352226# Sophia Anderson 1121042880000176 +705Mousecookie bonus pay for amazing work on #OSS 00010000176 +622231380104072164422360024600000000000#106381480266# Aiden Wilson 1121042880000177 +705Sentryglimmer bonus pay for amazing work on #OSS 00010000177 +622231380104574841218862230620000000000#206187863268# Elizabeth Miller 1121042880000178 +705Legsflame bonus pay for amazing work on #OSS 00010000178 +622231380104341426237540426200000000000#576163070355# Benjamin Martinez 1121042880000179 +705Eaterrelic bonus pay for amazing work on #OSS 00010000179 +622231380104207612842236460430000000000#200820622036# Liam Robinson 1121042880000180 +705Slayerwool bonus pay for amazing work on #OSS 00010000180 +622231380104016846542168388720000000000#754802642582# Sofia Smith 1121042880000181 +705Ribwhite bonus pay for amazing work on #OSS 00010000181 +622231380104353844276545357640000000000#528058642216# Olivia Smith 1121042880000182 +705Stalkersun bonus pay for amazing work on #OSS 00010000182 +622231380104167824310187585170000000000#061326101842# Jacob Martinez 1121042880000183 +705Stingbranch bonus pay for amazing work on #OSS 00010000183 +622231380104603333846205584250000000000#710117661002# James Wilson 1121042880000184 +705Cloudspark bonus pay for amazing work on #OSS 00010000184 +622231380104252115568504038270000000000#173648510852# Benjamin Moore 1121042880000185 +705Oriolefoul bonus pay for amazing work on #OSS 00010000185 +622231380104420758457602204250000000000#522158133412# Isabella Martin 1121042880000186 +705Yaknimble bonus pay for amazing work on #OSS 00010000186 +622231380104034646273370112180000000000#880600133221# Elizabeth Miller 1121042880000187 +705Robinwave bonus pay for amazing work on #OSS 00010000187 +622231380104734310545741804840000000000#533687274455# Matthew Jones 1121042880000188 +705Headphase bonus pay for amazing work on #OSS 00010000188 +622231380104863371780086041010000000000#250235378362# William Miller 1121042880000189 +705Wingplump bonus pay for amazing work on #OSS 00010000189 +622231380104060400078048730180000000000#443636724656# Ella Williams 1121042880000190 +705Legendember bonus pay for amazing work on #OSS 00010000190 +622231380104423231333774465120000000000#465745037381# Ella Johnson 1121042880000191 +705Slayerjust bonus pay for amazing work on #OSS 00010000191 +622231380104053144168102382440000000000#150125230061# Matthew Harris 1121042880000192 +705Carpetiris bonus pay for amazing work on #OSS 00010000192 +622231380104146648040402341420000000000#524626083185# Aubrey Smith 1121042880000193 +705Weaselholly bonus pay for amazing work on #OSS 00010000193 +622231380104710088332683807860000000000#476566537275# Emma Jackson 1121042880000194 +705Carpethail bonus pay for amazing work on #OSS 00010000194 +622231380104080584121142433610000000000#131423237548# Emily Thompson 1121042880000195 +705Whimseyjuniper bonus pay for amazing work on #OSS 00010000195 +622231380104656164573077417230000000000#111843580246# Emily Jackson 1121042880000196 +705Fairyflame bonus pay for amazing work on #OSS 00010000196 +622231380104382258251874655480000000000#647371406485# Andrew Taylor 1121042880000197 +705Whaleplump bonus pay for amazing work on #OSS 00010000197 +622231380104656788261055730740000000000#317342544875# Jayden Davis 1121042880000198 +705Dolphinbranch bonus pay for amazing work on #OSS 00010000198 +622231380104346231673350080060000000000#206542150108# William Anderson 1121042880000199 +705Fisheremerald bonus pay for amazing work on #OSS 00010000199 +622231380104071647165521523470000000000#866354415505# Michael Taylor 1121042880000200 +705Runnerstorm bonus pay for amazing work on #OSS 00010000200 +622231380104603725081075731510000000000#757101644001# Charlotte Martin 1121042880000201 +705Wyrmflint bonus pay for amazing work on #OSS 00010000201 +622231380104537350421354250640000000000#636636337043# Natalie Brown 1121042880000202 +705Headleather bonus pay for amazing work on #OSS 00010000202 +622231380104557488832376186000000000000#254765168716# Mason Jackson 1121042880000203 +705Grintree bonus pay for amazing work on #OSS 00010000203 +622231380104038480321204347700000000000#844004588471# Elizabeth Garcia 1121042880000204 +705Pegasusfast bonus pay for amazing work on #OSS 00010000204 +622231380104432633845300478480000000000#185216223230# Mia Garcia 1121042880000205 +705Llamasun bonus pay for amazing work on #OSS 00010000205 +622231380104723126033644058140000000000#087323704340# Aiden Thomas 1121042880000206 +705Ratglory bonus pay for amazing work on #OSS 00010000206 +622231380104753865482444263240000000000#506845424728# Natalie Martinez 1121042880000207 +705Fingerswamp bonus pay for amazing work on #OSS 00010000207 +622231380104388181444137287170000000000#510868171788# Michael Garcia 1121042880000208 +705Flameseason bonus pay for amazing work on #OSS 00010000208 +622231380104028601084734238630000000000#804488415787# Ethan Anderson 1121042880000209 +705Roachspectrum bonus pay for amazing work on #OSS 00010000209 +622231380104622262405604341040000000000#382305721372# James Martin 1121042880000210 +705Roachmalachite bonus pay for amazing work on #OSS 00010000210 +622231380104732235525164836570000000000#138700607011# Avery Garcia 1121042880000211 +705Stormpatch bonus pay for amazing work on #OSS 00010000211 +622231380104305882427861633700000000000#627002487768# Lily Smith 1121042880000212 +705Painterribbon bonus pay for amazing work on #OSS 00010000212 +622231380104623767268154627570000000000#477065137643# William Garcia 1121042880000213 +705Mouseberyl bonus pay for amazing work on #OSS 00010000213 +622231380104864237074452284310000000000#668203437878# James Harris 1121042880000214 +705Spiderfossil bonus pay for amazing work on #OSS 00010000214 +622231380104840303841456753110000000000#255212780717# Aubrey Moore 1121042880000215 +705Raccoonhoney bonus pay for amazing work on #OSS 00010000215 +622231380104041345061774213030000000000#312717881334# Lily Harris 1121042880000216 +705Ridgepolar bonus pay for amazing work on #OSS 00010000216 +622231380104435042421884412880000000000#542074421624# Emma Thompson 1121042880000217 +705Falconclever bonus pay for amazing work on #OSS 00010000217 +622231380104528523200361750310000000000#506745241074# Elijah Robinson 1121042880000218 +705Footfrost bonus pay for amazing work on #OSS 00010000218 +622231380104316652127852880120000000000#460406578375# Jayden White 1121042880000219 +705Catcherwest bonus pay for amazing work on #OSS 00010000219 +622231380104762508262200035580000000000#725363354565# Madison Garcia 1121042880000220 +705Cubzenith bonus pay for amazing work on #OSS 00010000220 +622231380104503174572685551570000000000#081006721154# Sofia White 1121042880000221 +705Shieldflax bonus pay for amazing work on #OSS 00010000221 +622231380104514040423746686630000000000#203251461372# Sofia Taylor 1121042880000222 +705Spikeproud bonus pay for amazing work on #OSS 00010000222 +622231380104687367312226183030000000000#603685568674# Jayden Johnson 1121042880000223 +705Elkcopper bonus pay for amazing work on #OSS 00010000223 +622231380104552730566473188160000000000#812077245343# Madison Anderson 1121042880000224 +705Batbuttercup bonus pay for amazing work on #OSS 00010000224 +622231380104841871674824763270000000000#273102837527# Sophia Brown 1121042880000225 +705Pigneon bonus pay for amazing work on #OSS 00010000225 +622231380104303026163437474140000000000#188007080038# Anthony Garcia 1121042880000226 +705Carpsouth bonus pay for amazing work on #OSS 00010000226 +622231380104256235780154612200000000000#108238687843# Alexander Miller 1121042880000227 +705Scarcloud bonus pay for amazing work on #OSS 00010000227 +622231380104050748844285475110000000000#112457715052# Elizabeth Smith 1121042880000228 +705Tailzest bonus pay for amazing work on #OSS 00010000228 +622231380104714438062268510260000000000#268182112101# Michael Robinson 1121042880000229 +705Painterspeckle bonus pay for amazing work on #OSS 00010000229 +622231380104554470302072442870000000000#265347485814# Ava Brown 1121042880000230 +705Snakepeach bonus pay for amazing work on #OSS 00010000230 +622231380104526817380618507810000000000#852804506781# Ella Harris 1121042880000231 +705Glazermoor bonus pay for amazing work on #OSS 00010000231 +622231380104442487364500104530000000000#476576348360# Joseph Martinez 1121042880000232 +705Spritethorn bonus pay for amazing work on #OSS 00010000232 +622231380104873501328421572380000000000#710313136681# Sophia Williams 1121042880000233 +705Ridersalt bonus pay for amazing work on #OSS 00010000233 +622231380104107364751287555480000000000#023410105073# Anthony Martin 1121042880000234 +705Whimseycloud bonus pay for amazing work on #OSS 00010000234 +622231380104631132603735422150000000000#121475063362# Ava Thompson 1121042880000235 +705Snarlmagenta bonus pay for amazing work on #OSS 00010000235 +622231380104108737678035614850000000000#502107183365# Ethan Martin 1121042880000236 +705Thundergarnet bonus pay for amazing work on #OSS 00010000236 +622231380104276836628568032460000000000#761268505138# Lily Smith 1121042880000237 +705Samuraileather bonus pay for amazing work on #OSS 00010000237 +622231380104438813783481012770000000000#041387243487# Madison Jackson 1121042880000238 +705Elkbitter bonus pay for amazing work on #OSS 00010000238 +622231380104065053164064226150000000000#641101708466# Ava Davis 1121042880000239 +705Playersmall bonus pay for amazing work on #OSS 00010000239 +622231380104456683546768713140000000000#400762703766# Sofia Miller 1121042880000240 +705Hisserginger bonus pay for amazing work on #OSS 00010000240 +622231380104451453660735116640000000000#578060712805# Charlotte Wilson 1121042880000241 +705Trackergiant bonus pay for amazing work on #OSS 00010000241 +622231380104446707066478650700000000000#485077716436# Mia Jones 1121042880000242 +705Horsedenim bonus pay for amazing work on #OSS 00010000242 +622231380104575748848332124640000000000#606151405524# Charlotte Thompson 1121042880000243 +705Headphantom bonus pay for amazing work on #OSS 00010000243 +622231380104327665132231710710000000000#537140542527# Elijah Taylor 1121042880000244 +705Orioleslow bonus pay for amazing work on #OSS 00010000244 +622231380104132422622616544730000000000#060544081524# Ava Anderson 1121042880000245 +705Leggentle bonus pay for amazing work on #OSS 00010000245 +622231380104688221233034075010000000000#651730512074# Emily Jackson 1121042880000246 +705Saverbattle bonus pay for amazing work on #OSS 00010000246 +622231380104270343217107444360000000000#326306054004# Anthony Robinson 1121042880000247 +705Cranebead bonus pay for amazing work on #OSS 00010000247 +622231380104081801570855660020000000000#752084633711# Noah Wilson 1121042880000248 +705Knifenova bonus pay for amazing work on #OSS 00010000248 +622231380104581523667267557470000000000#112087660471# Andrew Davis 1121042880000249 +705Gamblerviridian bonus pay for amazing work on #OSS 00010000249 +622231380104631146604844432150000000000#858257572436# Andrew Thompson 1121042880000250 +705Servantdisco bonus pay for amazing work on #OSS 00010000250 +622231380104110140834053071120000000000#206146475737# Ava Davis 1121042880000251 +705Wingroot bonus pay for amazing work on #OSS 00010000251 +622231380104755733106110704770000000000#021661376154# Liam Williams 1121042880000252 +705Touchseason bonus pay for amazing work on #OSS 00010000252 +622231380104317778465548373330000000000#572556018604# Joseph Anderson 1121042880000253 +705Wolverinewool bonus pay for amazing work on #OSS 00010000253 +622231380104564330440210653510000000000#644156852303# Chloe Moore 1121042880000254 +705Questerstar bonus pay for amazing work on #OSS 00010000254 +622231380104530704621018606840000000000#632365670321# Isabella Smith 1121042880000255 +705Crushercrystal bonus pay for amazing work on #OSS 00010000255 +622231380104222447707605487250000000000#174274205833# William Martinez 1121042880000256 +705Dukeshort bonus pay for amazing work on #OSS 00010000256 +622231380104517864000684060110000000000#671631482884# Andrew Thompson 1121042880000257 +705Legsshine bonus pay for amazing work on #OSS 00010000257 +622231380104426772117508466520000000000#273567853174# Natalie Martin 1121042880000258 +705Foxwax bonus pay for amazing work on #OSS 00010000258 +622231380104737278205784364110000000000#320065686561# Ella Brown 1121042880000259 +705Carpetsticky bonus pay for amazing work on #OSS 00010000259 +622231380104546782384630520720000000000#136426872512# Aiden Jackson 1121042880000260 +705Riderplume bonus pay for amazing work on #OSS 00010000260 +622231380104020688610124344820000000000#515536327326# Isabella Thompson 1121042880000261 +705Llamaquiver bonus pay for amazing work on #OSS 00010000261 +622231380104854214128766671480000000000#685207573554# Joseph Brown 1121042880000262 +705Centaurchatter bonus pay for amazing work on #OSS 00010000262 +622231380104073100248726417210000000000#066583342014# Ava Robinson 1121042880000263 +705Hiderune bonus pay for amazing work on #OSS 00010000263 +622231380104585275332033367070000000000#664151310811# James Wilson 1121042880000264 +705Panthernoon bonus pay for amazing work on #OSS 00010000264 +622231380104348781012154666420000000000#440881120466# Mia Jackson 1121042880000265 +705Bootfish bonus pay for amazing work on #OSS 00010000265 +622231380104804468283873114110000000000#838817856685# Avery Johnson 1121042880000266 +705Princessvalley bonus pay for amazing work on #OSS 00010000266 +622231380104676166766082528410000000000#324874506326# Addison Williams 1121042880000267 +705Runnerlace bonus pay for amazing work on #OSS 00010000267 +622231380104514741380564743380000000000#752358043178# James Thomas 1121042880000268 +705Skullsunset bonus pay for amazing work on #OSS 00010000268 +622231380104044328831720805610000000000#580854583185# James Martin 1121042880000269 +705Apepolar bonus pay for amazing work on #OSS 00010000269 +622231380104144476817652404810000000000#531773848738# Liam Thomas 1121042880000270 +705Riderberyl bonus pay for amazing work on #OSS 00010000270 +622231380104162604442717731770000000000#546763555752# Michael Anderson 1121042880000271 +705Bootthread bonus pay for amazing work on #OSS 00010000271 +622231380104426480446807088660000000000#735741285760# Abigail Martinez 1121042880000272 +705Snakeequinox bonus pay for amazing work on #OSS 00010000272 +622231380104285767782515635880000000000#364086066250# Anthony Davis 1121042880000273 +705Harecloud bonus pay for amazing work on #OSS 00010000273 +622231380104427326000671676280000000000#701746236433# Ella Thomas 1121042880000274 +705Ravenstump bonus pay for amazing work on #OSS 00010000274 +622231380104625387867140216870000000000#712638345265# Madison Anderson 1121042880000275 +705Raventree bonus pay for amazing work on #OSS 00010000275 +622231380104011677610726734630000000000#782643345753# Matthew Smith 1121042880000276 +705Pigdiamond bonus pay for amazing work on #OSS 00010000276 +622231380104058262428757703880000000000#843727316355# Liam Davis 1121042880000277 +705Snapwax bonus pay for amazing work on #OSS 00010000277 +622231380104562446785478055660000000000#147657132043# Mia Johnson 1121042880000278 +705Musefringe bonus pay for amazing work on #OSS 00010000278 +622231380104268537720736147670000000000#021050006862# Zoey Miller 1121042880000279 +705Wolverinewhip bonus pay for amazing work on #OSS 00010000279 +622231380104571252434484806820000000000#002680337541# Benjamin Martin 1121042880000280 +705Wingalpine bonus pay for amazing work on #OSS 00010000280 +622231380104220421772014035500000000000#182260022165# Elijah Williams 1121042880000281 +705Snakezest bonus pay for amazing work on #OSS 00010000281 +622231380104375677347525412270000000000#343823178486# Olivia Garcia 1121042880000282 +705Crusherpower bonus pay for amazing work on #OSS 00010000282 +622231380104604624780783861370000000000#701555775022# Emily Thompson 1121042880000283 +705Antelopesalt bonus pay for amazing work on #OSS 00010000283 +622231380104127814022186787570000000000#750337608200# Andrew Smith 1121042880000284 +705Dancermountain bonus pay for amazing work on #OSS 00010000284 +622231380104106776757512048660000000000#508556561612# Charlotte Harris 1121042880000285 +705Princebattle bonus pay for amazing work on #OSS 00010000285 +622231380104668047414346773130000000000#252574645386# Emily Jackson 1121042880000286 +705Scalemulberry bonus pay for amazing work on #OSS 00010000286 +622231380104354774550465133130000000000#187286374010# Anthony Moore 1121042880000287 +705Antelopesun bonus pay for amazing work on #OSS 00010000287 +622231380104178636783144610830000000000#451784066376# Daniel Harris 1121042880000288 +705Lizardleather bonus pay for amazing work on #OSS 00010000288 +622231380104547201708161638360000000000#685237265340# Avery Brown 1121042880000289 +705Collarfair bonus pay for amazing work on #OSS 00010000289 +622231380104167055788061841580000000000#442763777135# Ava Thompson 1121042880000290 +705Antlerpepper bonus pay for amazing work on #OSS 00010000290 +622231380104176586356572885780000000000#870682545484# Isabella Miller 1121042880000291 +705Puppysun bonus pay for amazing work on #OSS 00010000291 +622231380104144326606518063540000000000#772182025111# Avery Thomas 1121042880000292 +705Diverlaser bonus pay for amazing work on #OSS 00010000292 +622231380104533838624207667520000000000#823760526517# Alexander Miller 1121042880000293 +705Lightninghazel bonus pay for amazing work on #OSS 00010000293 +622231380104388704585456064650000000000#488481847123# Jacob Anderson 1121042880000294 +705Pythonsnow bonus pay for amazing work on #OSS 00010000294 +622231380104506034185276422140000000000#377651173503# Ava Harris 1121042880000295 +705Wandererpsychadelic bonus pay for amazing work on #OSS 00010000295 +622231380104780402757855725560000000000#352502152207# Zoey Thompson 1121042880000296 +705Crafterfree bonus pay for amazing work on #OSS 00010000296 +622231380104326358168186264110000000000#402303042826# Ava Garcia 1121042880000297 +705Goatsavage bonus pay for amazing work on #OSS 00010000297 +622231380104577554746635438100000000000#568257018885# Matthew Wilson 1121042880000298 +705Healertitanium bonus pay for amazing work on #OSS 00010000298 +622231380104325721210005124000000000000#758104587583# Chloe Johnson 1121042880000299 +705Sentryvalley bonus pay for amazing work on #OSS 00010000299 +622231380104123261574237875410000000000#885760283203# Ava Johnson 1121042880000300 +705Doggossamer bonus pay for amazing work on #OSS 00010000300 +622231380104032411872112705610000000000#457043622454# David Johnson 1121042880000301 +705Pawpattern bonus pay for amazing work on #OSS 00010000301 +622231380104135742423605370210000000000#436412148412# Madison Thompson 1121042880000302 +705Raptorperidot bonus pay for amazing work on #OSS 00010000302 +622231380104082765416176000030000000000#260356133602# Aiden Thompson 1121042880000303 +705Spiritquill bonus pay for amazing work on #OSS 00010000303 +622231380104002018347462253840000000000#778771434028# Joshua Jones 1121042880000304 +705Maskcat bonus pay for amazing work on #OSS 00010000304 +622231380104603227088204048370000000000#283253236770# Liam Thomas 1121042880000305 +705Fangclover bonus pay for amazing work on #OSS 00010000305 +622231380104253250538533172850000000000#784525287127# Emma Jones 1121042880000306 +705Mustangbrook bonus pay for amazing work on #OSS 00010000306 +622231380104688648358150475110000000000#421528134244# Noah Davis 1121042880000307 +705Wizardlinen bonus pay for amazing work on #OSS 00010000307 +622231380104023566888133153280000000000#360666027251# Andrew White 1121042880000308 +705Eateralpine bonus pay for amazing work on #OSS 00010000308 +622231380104267843433060081570000000000#631026224384# Charlotte Jones 1121042880000309 +705Wasprhinestone bonus pay for amazing work on #OSS 00010000309 +622231380104878573800273036160000000000#050185740058# Natalie Davis 1121042880000310 +705Crystalbald bonus pay for amazing work on #OSS 00010000310 +622231380104487121021282412210000000000#213076541106# Ava Davis 1121042880000311 +705Razoratom bonus pay for amazing work on #OSS 00010000311 +622231380104467321711102266540000000000#127328331728# Michael Robinson 1121042880000312 +705Hisssour bonus pay for amazing work on #OSS 00010000312 +622231380104658883070651000650000000000#857515816745# Daniel Smith 1121042880000313 +705Whimseyheavy bonus pay for amazing work on #OSS 00010000313 +622231380104650183506674808160000000000#071663383273# Addison Martin 1121042880000314 +705Turnersugar bonus pay for amazing work on #OSS 00010000314 +622231380104367283464751354670000000000#037568233744# Natalie Smith 1121042880000315 +705Weedriver bonus pay for amazing work on #OSS 00010000315 +622231380104108045348274884060000000000#835302654703# Matthew Smith 1121042880000316 +705Daggerquasar bonus pay for amazing work on #OSS 00010000316 +622231380104430575532433423620000000000#523167338840# Lily White 1121042880000317 +705Walkergrave bonus pay for amazing work on #OSS 00010000317 +622231380104346554754458383250000000000#343256542138# Jayden Williams 1121042880000318 +705Musesun bonus pay for amazing work on #OSS 00010000318 +622231380104564144337108362480000000000#676240416023# Olivia Garcia 1121042880000319 +705Soarerstorm bonus pay for amazing work on #OSS 00010000319 +622231380104075531703533018720000000000#102017718611# Addison Brown 1121042880000320 +705Hawkcandy bonus pay for amazing work on #OSS 00010000320 +622231380104303427381055471780000000000#653236003223# Joseph Anderson 1121042880000321 +705Jayberyl bonus pay for amazing work on #OSS 00010000321 +622231380104756531780714847250000000000#176402616777# Alexander Martin 1121042880000322 +705Kingglaze bonus pay for amazing work on #OSS 00010000322 +622231380104642677580730450860000000000#308144080347# Elijah Taylor 1121042880000323 +705Jackalgravel bonus pay for amazing work on #OSS 00010000323 +622231380104077435040021448440000000000#317257655156# Ethan Jackson 1121042880000324 +705Dartdust bonus pay for amazing work on #OSS 00010000324 +622231380104126414865733336140000000000#156263782741# David Jones 1121042880000325 +705Raccoonbattle bonus pay for amazing work on #OSS 00010000325 +622231380104682475712363880400000000000#363780824061# Andrew Davis 1121042880000326 +705Chatterwalnut bonus pay for amazing work on #OSS 00010000326 +622231380104563828778142875040000000000#130102826625# Andrew Martin 1121042880000327 +705Crowgrove bonus pay for amazing work on #OSS 00010000327 +622231380104834577261432076420000000000#470481648000# Mia Harris 1121042880000328 +705Boltbig bonus pay for amazing work on #OSS 00010000328 +622231380104762584670166361430000000000#277570773585# Sofia Davis 1121042880000329 +705Beetleshag bonus pay for amazing work on #OSS 00010000329 +622231380104635118888515218070000000000#717538504153# Natalie Taylor 1121042880000330 +705Koalaround bonus pay for amazing work on #OSS 00010000330 +622231380104111006637630042800000000000#213458722662# Ava Wilson 1121042880000331 +705Reaperlime bonus pay for amazing work on #OSS 00010000331 +622231380104800878765284668050000000000#866433050221# Noah Jones 1121042880000332 +705Wizardsleet bonus pay for amazing work on #OSS 00010000332 +622231380104662470474231311080000000000#181018520258# Avery Brown 1121042880000333 +705Boarswamp bonus pay for amazing work on #OSS 00010000333 +622231380104832335857528612340000000000#263151767564# Michael Miller 1121042880000334 +705Hornblossom bonus pay for amazing work on #OSS 00010000334 +622231380104416365541246120550000000000#771186388665# Addison Williams 1121042880000335 +705Shriekmesquite bonus pay for amazing work on #OSS 00010000335 +622231380104733611163066656640000000000#545448131547# Alexander Robinson 1121042880000336 +705Voleginger bonus pay for amazing work on #OSS 00010000336 +622231380104482025126740223280000000000#243037146545# Joseph Williams 1121042880000337 +705Gazellegranite bonus pay for amazing work on #OSS 00010000337 +622231380104883230237622007840000000000#717268666834# Addison Williams 1121042880000338 +705Roachstar bonus pay for amazing work on #OSS 00010000338 +622231380104073403085152403240000000000#048455211368# Ella Thompson 1121042880000339 +705Hissrust bonus pay for amazing work on #OSS 00010000339 +622231380104523808813428431160000000000#113646460101# Addison Miller 1121042880000340 +705Crystalplume bonus pay for amazing work on #OSS 00010000340 +622231380104506322652746001210000000000#288645244411# Elijah Jackson 1121042880000341 +705Sightmesquite bonus pay for amazing work on #OSS 00010000341 +622231380104747411450838734250000000000#221740440032# James Johnson 1121042880000342 +705Ocelotnotch bonus pay for amazing work on #OSS 00010000342 +622231380104845340567847566250000000000#300586748217# Mia Miller 1121042880000343 +705Headcrimson bonus pay for amazing work on #OSS 00010000343 +622231380104603767423047603030000000000#100732867115# Sophia Martin 1121042880000344 +705Iguanapeat bonus pay for amazing work on #OSS 00010000344 +622231380104815507621836704770000000000#187281450071# Aiden Jackson 1121042880000345 +705Scowlmint bonus pay for amazing work on #OSS 00010000345 +622231380104428705888078213170000000000#622386576361# Olivia Moore 1121042880000346 +705Witchdawn bonus pay for amazing work on #OSS 00010000346 +622231380104235555326623056320000000000#167503512375# Aiden Garcia 1121042880000347 +705Legsplum bonus pay for amazing work on #OSS 00010000347 +622231380104511762715652081450000000000#633381686160# Benjamin Smith 1121042880000348 +705Twisterswift bonus pay for amazing work on #OSS 00010000348 +622231380104082447784661014030000000000#681045508868# Sophia Wilson 1121042880000349 +705Singeroil bonus pay for amazing work on #OSS 00010000349 +622231380104302323153073277850000000000#285321485125# Lily White 1121042880000350 +705Pawpewter bonus pay for amazing work on #OSS 00010000350 +622231380104654151582364708830000000000#005520750683# Charlotte Smith 1121042880000351 +705Giverspiral bonus pay for amazing work on #OSS 00010000351 +622231380104500140810118668840000000000#684754781101# Madison Brown 1121042880000352 +705Ravenglory bonus pay for amazing work on #OSS 00010000352 +622231380104665640154271610330000000000#800814604272# Aubrey Garcia 1121042880000353 +705Jaguarmorning bonus pay for amazing work on #OSS 00010000353 +622231380104406725504345523170000000000#241387826822# Andrew Garcia 1121042880000354 +705Sparrowflower bonus pay for amazing work on #OSS 00010000354 +622231380104326471805553530360000000000#152402442311# Olivia Jones 1121042880000355 +705Condorchecker bonus pay for amazing work on #OSS 00010000355 +622231380104630481211763758780000000000#356323563112# James Garcia 1121042880000356 +705Cubmeadow bonus pay for amazing work on #OSS 00010000356 +622231380104810262812082240860000000000#761250460217# Joseph Martin 1121042880000357 +705Donkeycrystal bonus pay for amazing work on #OSS 00010000357 +622231380104115020728551617340000000000#853054865564# William White 1121042880000358 +705Razorcrazy bonus pay for amazing work on #OSS 00010000358 +622231380104767350064877484340000000000#846372155140# Liam Brown 1121042880000359 +705Kangaroofire bonus pay for amazing work on #OSS 00010000359 +622231380104814063423208485650000000000#120333400208# Emily Brown 1121042880000360 +705Raptorpale bonus pay for amazing work on #OSS 00010000360 +622231380104084774468405268050000000000#235876513458# Ava Anderson 1121042880000361 +705Seedpine bonus pay for amazing work on #OSS 00010000361 +622231380104658326104384717050000000000#381526202754# Sophia Smith 1121042880000362 +705Scarerrazor bonus pay for amazing work on #OSS 00010000362 +622231380104864210217437678300000000000#861326111663# Michael Moore 1121042880000363 +705Giverfantasy bonus pay for amazing work on #OSS 00010000363 +622231380104238123420262656280000000000#562737630521# Jacob Martin 1121042880000364 +705Tigerazure bonus pay for amazing work on #OSS 00010000364 +622231380104155372655050755520000000000#336823345488# Isabella Robinson 1121042880000365 +705Catcherspeckle bonus pay for amazing work on #OSS 00010000365 +622231380104011851174877166430000000000#234266510086# Joshua Robinson 1121042880000366 +705Coyotedestiny bonus pay for amazing work on #OSS 00010000366 +622231380104745525223213312120000000000#078254648528# Jacob Jones 1121042880000367 +705Legendprong bonus pay for amazing work on #OSS 00010000367 +622231380104782185000804607730000000000#633803724432# David Martin 1121042880000368 +705Seerheavy bonus pay for amazing work on #OSS 00010000368 +622231380104758323771472471410000000000#050151007421# Elijah Martin 1121042880000369 +705Frillfate bonus pay for amazing work on #OSS 00010000369 +622231380104177457010005887240000000000#820418278854# Sophia Garcia 1121042880000370 +705Sparrowcyber bonus pay for amazing work on #OSS 00010000370 +622231380104035608046420255530000000000#863037261851# Daniel Jackson 1121042880000371 +705Sagecyan bonus pay for amazing work on #OSS 00010000371 +622231380104271303087020203270000000000#516055778226# Aiden Martin 1121042880000372 +705Fingeragate bonus pay for amazing work on #OSS 00010000372 +622231380104731326216451860670000000000#473822113582# Natalie Taylor 1121042880000373 +705Snagglefootstar bonus pay for amazing work on #OSS 00010000373 +622231380104415247258412135640000000000#412284038348# Elizabeth Johnson 1121042880000374 +705Deathcliff bonus pay for amazing work on #OSS 00010000374 +622231380104187601258661712570000000000#486406337825# Ella Moore 1121042880000375 +705Crystalneon bonus pay for amazing work on #OSS 00010000375 +622231380104515217181614434230000000000#680631707518# Joseph White 1121042880000376 +705Elfspeckle bonus pay for amazing work on #OSS 00010000376 +622231380104853026855582387660000000000#833411175722# Olivia Brown 1121042880000377 +705Lynxjelly bonus pay for amazing work on #OSS 00010000377 +622231380104582556487026648850000000000#807336115052# Matthew Jackson 1121042880000378 +705Minnowwater bonus pay for amazing work on #OSS 00010000378 +622231380104045742667217730180000000000#232015418044# Emma Garcia 1121042880000379 +705Runnerbrick bonus pay for amazing work on #OSS 00010000379 +622231380104303371716524281360000000000#805427477526# Sofia Martin 1121042880000380 +705Snapmaple bonus pay for amazing work on #OSS 00010000380 +622231380104132112281108367340000000000#606424400157# Ethan Harris 1121042880000381 +705Chargeralpine bonus pay for amazing work on #OSS 00010000381 +622231380104613811015688307020000000000#181432547600# Andrew Martinez 1121042880000382 +705Gullpale bonus pay for amazing work on #OSS 00010000382 +622231380104708688246374157410000000000#604858632887# Noah Robinson 1121042880000383 +705Bootplum bonus pay for amazing work on #OSS 00010000383 +622231380104652836707174555260000000000#102232337524# Joshua Taylor 1121042880000384 +705Snarlwell bonus pay for amazing work on #OSS 00010000384 +622231380104162465561222780680000000000#116115242814# Addison Thomas 1121042880000385 +705Swoopglacier bonus pay for amazing work on #OSS 00010000385 +622231380104346824835764073220000000000#457146673136# Liam Brown 1121042880000386 +705Mousenickel bonus pay for amazing work on #OSS 00010000386 +622231380104008870120734080580000000000#051613605275# Aubrey Robinson 1121042880000387 +705Lightningfog bonus pay for amazing work on #OSS 00010000387 +622231380104073857113876578150000000000#057115105365# Matthew Moore 1121042880000388 +705Fangsand bonus pay for amazing work on #OSS 00010000388 +622231380104640510771770813030000000000#786122747433# David Thomas 1121042880000389 +705Flamemulberry bonus pay for amazing work on #OSS 00010000389 +622231380104527706383221264150000000000#077240655055# Ella Brown 1121042880000390 +705Pythonjust bonus pay for amazing work on #OSS 00010000390 +622231380104147706571741484880000000000#710381784741# Olivia White 1121042880000391 +705Walkercanyon bonus pay for amazing work on #OSS 00010000391 +622231380104175437361378363500000000000#257327013844# Chloe Garcia 1121042880000392 +705Warlockroad bonus pay for amazing work on #OSS 00010000392 +622231380104316465832124855630000000000#330818145373# Jayden Martinez 1121042880000393 +705Horntide bonus pay for amazing work on #OSS 00010000393 +622231380104002132763452815810000000000#885181452730# Abigail Davis 1121042880000394 +705Batperidot bonus pay for amazing work on #OSS 00010000394 +622231380104214336768066030370000000000#530877302608# Zoey Robinson 1121042880000395 +705Painterrogue bonus pay for amazing work on #OSS 00010000395 +622231380104406222086637317470000000000#854702341810# Joseph Brown 1121042880000396 +705Pantherroot bonus pay for amazing work on #OSS 00010000396 +622231380104282552508738105230000000000#103836503125# Mia Johnson 1121042880000397 +705Haredour bonus pay for amazing work on #OSS 00010000397 +622231380104352044745456134400000000000#015870670107# Aubrey Garcia 1121042880000398 +705Spikethorn bonus pay for amazing work on #OSS 00010000398 +622231380104882536301818773580000000000#080833600303# Zoey Johnson 1121042880000399 +705Bellypyrite bonus pay for amazing work on #OSS 00010000399 +622231380104410805328581186800000000000#745627166670# Michael Wilson 1121042880000400 +705Witchdark bonus pay for amazing work on #OSS 00010000400 +622231380104283233088315660650000000000#355087130672# Matthew Moore 1121042880000401 +705Scarnoon bonus pay for amazing work on #OSS 00010000401 +622231380104270702175468073620000000000#745358040804# Elijah Davis 1121042880000402 +705Ravertree bonus pay for amazing work on #OSS 00010000402 +622231380104745387631208307810000000000#747634433171# Emma Harris 1121042880000403 +705Reaperlavender bonus pay for amazing work on #OSS 00010000403 +622231380104560588163540785720000000000#386252656353# Chloe Davis 1121042880000404 +705Friendfish bonus pay for amazing work on #OSS 00010000404 +622231380104851447368086870520000000000#227082760102# Jayden White 1121042880000405 +705Hairscratch bonus pay for amazing work on #OSS 00010000405 +622231380104570513472870058030000000000#087104335705# Daniel Thompson 1121042880000406 +705Goatpyrite bonus pay for amazing work on #OSS 00010000406 +622231380104113430403208442600000000000#845460676046# Noah Williams 1121042880000407 +705Bootazure bonus pay for amazing work on #OSS 00010000407 +622231380104540431121142725410000000000#110712107133# Daniel Davis 1121042880000408 +705Cloudcoal bonus pay for amazing work on #OSS 00010000408 +622231380104204483151767330770000000000#480206053076# Isabella Williams 1121042880000409 +705Reaperfire bonus pay for amazing work on #OSS 00010000409 +622231380104016106471086347620000000000#476515544186# Benjamin Harris 1121042880000410 +705Lancerdisco bonus pay for amazing work on #OSS 00010000410 +622231380104070776745433714300000000000#822262662712# Elijah Brown 1121042880000411 +705Jawplain bonus pay for amazing work on #OSS 00010000411 +622231380104384332730532416770000000000#225741442174# Alexander Johnson 1121042880000412 +705Herondawn bonus pay for amazing work on #OSS 00010000412 +622231380104827600241534157770000000000#831313778083# Jacob Anderson 1121042880000413 +705Traderlapis bonus pay for amazing work on #OSS 00010000413 +622231380104166488718223458630000000000#765717418157# Ava Smith 1121042880000414 +705Divechrome bonus pay for amazing work on #OSS 00010000414 +622231380104253560377784830040000000000#453138875308# Avery Thompson 1121042880000415 +705Stingerpuddle bonus pay for amazing work on #OSS 00010000415 +622231380104027101560421602760000000000#114048650061# Zoey Garcia 1121042880000416 +705Driftermidnight bonus pay for amazing work on #OSS 00010000416 +622231380104222556437680651100000000000#786433545166# Sofia Wilson 1121042880000417 +705Bardscythe bonus pay for amazing work on #OSS 00010000417 +622231380104031522482507013810000000000#518535687585# Mia Harris 1121042880000418 +705Goosemad bonus pay for amazing work on #OSS 00010000418 +622231380104604461868500554670000000000#658307888271# Joshua Robinson 1121042880000419 +705Skinnerlightning bonus pay for amazing work on #OSS 00010000419 +622231380104633657232344377480000000000#521287400821# Joshua Harris 1121042880000420 +705Gemoil bonus pay for amazing work on #OSS 00010000420 +622231380104483074263681130600000000000#718532330175# Olivia Thompson 1121042880000421 +705Roverchecker bonus pay for amazing work on #OSS 00010000421 +622231380104566301145321843530000000000#801523131218# Addison White 1121042880000422 +705Fingersun bonus pay for amazing work on #OSS 00010000422 +622231380104368101251105748020000000000#702566128407# Noah Williams 1121042880000423 +705Pantherwest bonus pay for amazing work on #OSS 00010000423 +622231380104712417364553865450000000000#062046128325# Emily Williams 1121042880000424 +705Legrattle bonus pay for amazing work on #OSS 00010000424 +622231380104187070458273857580000000000#615847227355# Andrew Johnson 1121042880000425 +705Stingerspring bonus pay for amazing work on #OSS 00010000425 +622231380104226244463040511350000000000#558885214112# Abigail Robinson 1121042880000426 +705Yakjungle bonus pay for amazing work on #OSS 00010000426 +622231380104423760607482058000000000000#680062123404# Jacob Moore 1121042880000427 +705Carverbig bonus pay for amazing work on #OSS 00010000427 +622231380104168440111330433200000000000#214834732006# Anthony Martinez 1121042880000428 +705Boastump bonus pay for amazing work on #OSS 00010000428 +622231380104084126620620178760000000000#815136865826# Charlotte Brown 1121042880000429 +705Jaguarstream bonus pay for amazing work on #OSS 00010000429 +622231380104558856405717176170000000000#877002814212# Sophia Harris 1121042880000430 +705Hissersavage bonus pay for amazing work on #OSS 00010000430 +622231380104812441223111752270000000000#874006251880# Olivia Harris 1121042880000431 +705Carpetjasper bonus pay for amazing work on #OSS 00010000431 +622231380104503241277116173350000000000#767335228556# Isabella Davis 1121042880000432 +705Flierpollen bonus pay for amazing work on #OSS 00010000432 +622231380104753680812768031570000000000#045440560415# Abigail Taylor 1121042880000433 +705Jesterdour bonus pay for amazing work on #OSS 00010000433 +622231380104715023328078020730000000000#602202625768# Sofia Anderson 1121042880000434 +705Songhill bonus pay for amazing work on #OSS 00010000434 +622231380104028822378307108220000000000#132140404878# Mason Martin 1121042880000435 +705Snapcherry bonus pay for amazing work on #OSS 00010000435 +622231380104276260855761048300000000000#421062312014# Mia Thomas 1121042880000436 +705Ratfoul bonus pay for amazing work on #OSS 00010000436 +622231380104382074018452164260000000000#237545055575# Emily Smith 1121042880000437 +705Stonepaint bonus pay for amazing work on #OSS 00010000437 +622231380104605247408558386760000000000#338320623462# Mason Thomas 1121042880000438 +705Scalealder bonus pay for amazing work on #OSS 00010000438 +622231380104461113860843207560000000000#446323751540# Olivia Jackson 1121042880000439 +705Snapglitter bonus pay for amazing work on #OSS 00010000439 +622231380104388101841714584040000000000#020628853203# David Harris 1121042880000440 +705Swisheralabaster bonus pay for amazing work on #OSS 00010000440 +622231380104381046882081423210000000000#673165771887# Anthony Williams 1121042880000441 +705Fisherpickle bonus pay for amazing work on #OSS 00010000441 +622231380104772521155068747170000000000#100738324088# Benjamin Moore 1121042880000442 +705Huggerwalnut bonus pay for amazing work on #OSS 00010000442 +622231380104758644445151285830000000000#404881818844# Chloe Garcia 1121042880000443 +705Jackalrift bonus pay for amazing work on #OSS 00010000443 +622231380104154785127776217340000000000#833077547477# Sofia Martinez 1121042880000444 +705Ottercerulean bonus pay for amazing work on #OSS 00010000444 +622231380104078815365865336570000000000#246685530356# Olivia Moore 1121042880000445 +705Thiefstream bonus pay for amazing work on #OSS 00010000445 +622231380104421236832787866320000000000#386721108760# Ava Thompson 1121042880000446 +705Huggerskitter bonus pay for amazing work on #OSS 00010000446 +622231380104386606350330285280000000000#172337714156# David Wilson 1121042880000447 +705Snoutclever bonus pay for amazing work on #OSS 00010000447 +622231380104164874467060533300000000000#315646038225# Charlotte Martinez 1121042880000448 +705Trackpond bonus pay for amazing work on #OSS 00010000448 +622231380104237876175453176220000000000#347265106220# Benjamin White 1121042880000449 +705Witchcomet bonus pay for amazing work on #OSS 00010000449 +622231380104242143212838370560000000000#444283806308# Michael Smith 1121042880000450 +705Batink bonus pay for amazing work on #OSS 00010000450 +622231380104066245020263651450000000000#701218276860# William Williams 1121042880000451 +705Fishvivid bonus pay for amazing work on #OSS 00010000451 +622231380104375865683038277880000000000#626650407817# Sofia Smith 1121042880000452 +705Trackersponge bonus pay for amazing work on #OSS 00010000452 +622231380104372388730570057620000000000#533540851241# Matthew Jones 1121042880000453 +705Skinnerpepper bonus pay for amazing work on #OSS 00010000453 +622231380104815274845870767110000000000#571438054188# Aubrey Anderson 1121042880000454 +705Scowlindigo bonus pay for amazing work on #OSS 00010000454 +622231380104812425335788608600000000000#511200031146# Abigail Wilson 1121042880000455 +705Pythonmesquite bonus pay for amazing work on #OSS 00010000455 +622231380104773084440201413450000000000#535603726621# Matthew Taylor 1121042880000456 +705Ocelottrail bonus pay for amazing work on #OSS 00010000456 +622231380104380441404100040850000000000#425561508763# Emily White 1121042880000457 +705Slayersilent bonus pay for amazing work on #OSS 00010000457 +622231380104346160267521406270000000000#137436817388# Sophia Miller 1121042880000458 +705Heronbird bonus pay for amazing work on #OSS 00010000458 +622231380104553811634646014500000000000#582043741831# Ava Williams 1121042880000459 +705Spikevaliant bonus pay for amazing work on #OSS 00010000459 +622231380104314623401138476500000000000#782550800735# Jacob Jackson 1121042880000460 +705Chillcurse bonus pay for amazing work on #OSS 00010000460 +622231380104041566772200787610000000000#684477365864# Matthew Jones 1121042880000461 +705Hornetdot bonus pay for amazing work on #OSS 00010000461 +622231380104724733168580464670000000000#676170747286# Emily Martinez 1121042880000462 +705Thunderglimmer bonus pay for amazing work on #OSS 00010000462 +622231380104171360148551427170000000000#848512484118# Madison Garcia 1121042880000463 +705Oriolehurricane bonus pay for amazing work on #OSS 00010000463 +622231380104208812571066518780000000000#474018738044# William Robinson 1121042880000464 +705Boartopaz bonus pay for amazing work on #OSS 00010000464 +622231380104280710104607750610000000000#052432310430# Alexander Martin 1121042880000465 +705Scourgefoul bonus pay for amazing work on #OSS 00010000465 +622231380104662843138543227630000000000#140748342411# Abigail Brown 1121042880000466 +705Antelopeglen bonus pay for amazing work on #OSS 00010000466 +622231380104011153577261168380000000000#546854860448# Mason Williams 1121042880000467 +705Bindersilent bonus pay for amazing work on #OSS 00010000467 +622231380104206607244412023470000000000#113245611254# Mason Thomas 1121042880000468 +705Swisherink bonus pay for amazing work on #OSS 00010000468 +622231380104153484406147737420000000000#308113540267# Abigail White 1121042880000469 +705Koalalavender bonus pay for amazing work on #OSS 00010000469 +622231380104553802752487861430000000000#777616448207# Emily Anderson 1121042880000470 +705Coyotekeen bonus pay for amazing work on #OSS 00010000470 +622231380104372067040673773100000000000#682623408587# Ava Martin 1121042880000471 +705Grinfantasy bonus pay for amazing work on #OSS 00010000471 +622231380104113188226178463830000000000#012106571751# Ethan White 1121042880000472 +705Loonbubble bonus pay for amazing work on #OSS 00010000472 +622231380104055558744687400800000000000#781012281833# Matthew Jones 1121042880000473 +705Roverscythe bonus pay for amazing work on #OSS 00010000473 +622231380104662604827750147370000000000#241202145738# Avery Williams 1121042880000474 +705Duckcandy bonus pay for amazing work on #OSS 00010000474 +622231380104423224174383836720000000000#666822246584# Aubrey Jones 1121042880000475 +705Footeast bonus pay for amazing work on #OSS 00010000475 +622231380104623067880663255100000000000#888817800671# Ella Jackson 1121042880000476 +705Wingoasis bonus pay for amazing work on #OSS 00010000476 +622231380104664607775471723120000000000#610708157837# Avery Martinez 1121042880000477 +705Scaleautumn bonus pay for amazing work on #OSS 00010000477 +622231380104282247447642861070000000000#335366034352# Madison Anderson 1121042880000478 +705Shoulderblue bonus pay for amazing work on #OSS 00010000478 +622231380104401416324730366240000000000#125482644876# Alexander Jones 1121042880000479 +705Terrierivy bonus pay for amazing work on #OSS 00010000479 +622231380104623350331007505370000000000#613718410074# Alexander Moore 1121042880000480 +705Jackalblue bonus pay for amazing work on #OSS 00010000480 +622231380104045536320858287130000000000#571654484802# Joseph Johnson 1121042880000481 +705Lordglow bonus pay for amazing work on #OSS 00010000481 +622231380104384632371008165440000000000#351074818482# Emily Harris 1121042880000482 +705Thornisland bonus pay for amazing work on #OSS 00010000482 +622231380104227027777466673150000000000#265021002543# Zoey White 1121042880000483 +705Mindblue bonus pay for amazing work on #OSS 00010000483 +622231380104347707476701435270000000000#813133642715# Lily Jackson 1121042880000484 +705Beetlesand bonus pay for amazing work on #OSS 00010000484 +622231380104055818241125765320000000000#281577658350# Alexander Brown 1121042880000485 +705Lashernavy bonus pay for amazing work on #OSS 00010000485 +622231380104305253787360073040000000000#460870738645# Addison White 1121042880000486 +705Hisserpine bonus pay for amazing work on #OSS 00010000486 +622231380104200737776502857230000000000#875433142448# Andrew Anderson 1121042880000487 +705Skullnorth bonus pay for amazing work on #OSS 00010000487 +622231380104055471546488303550000000000#776032047705# Olivia Johnson 1121042880000488 +705Jawmalachite bonus pay for amazing work on #OSS 00010000488 +622231380104748400515447388210000000000#514516047137# Noah Robinson 1121042880000489 +705Servantglen bonus pay for amazing work on #OSS 00010000489 +622231380104125667475206124700000000000#280458138136# Daniel Anderson 1121042880000490 +705Horseholy bonus pay for amazing work on #OSS 00010000490 +622231380104451387030147526510000000000#702655432545# Madison Wilson 1121042880000491 +705Wolffree bonus pay for amazing work on #OSS 00010000491 +622231380104560727483883672520000000000#884728717771# Lily Wilson 1121042880000492 +705Maskmirage bonus pay for amazing work on #OSS 00010000492 +622231380104182785844756885370000000000#822604082144# Anthony Anderson 1121042880000493 +705Terrierlava bonus pay for amazing work on #OSS 00010000493 +622231380104837655507111042560000000000#021850473028# Liam Jackson 1121042880000494 +705Fangfish bonus pay for amazing work on #OSS 00010000494 +622231380104776041863658071120000000000#066352430203# William Moore 1121042880000495 +705Koalarust bonus pay for amazing work on #OSS 00010000495 +622231380104165781183125554550000000000#740473643207# Zoey Brown 1121042880000496 +705Parrotspot bonus pay for amazing work on #OSS 00010000496 +622231380104221318000166745230000000000#215580142814# Jacob Smith 1121042880000497 +705Fairypetal bonus pay for amazing work on #OSS 00010000497 +622231380104337013147560512170000000000#032361310560# Sophia Martinez 1121042880000498 +705Sharkcookie bonus pay for amazing work on #OSS 00010000498 +622231380104644672864781072100000000000#206821247045# Olivia Anderson 1121042880000499 +705Banerazor bonus pay for amazing work on #OSS 00010000499 +622231380104414767634567732000000000000#018114527617# Emily Harris 1121042880000500 +705Musecandy bonus pay for amazing work on #OSS 00010000500 +622231380104821448714886737040000000000#520625446145# Sofia Thomas 1121042880000501 +705Lasherfate bonus pay for amazing work on #OSS 00010000501 +622231380104404662686625021250000000000#645224735705# Aiden Johnson 1121042880000502 +705Catchersharp bonus pay for amazing work on #OSS 00010000502 +622231380104517862512138235760000000000#428874473345# Jayden Williams 1121042880000503 +705Elfpatch bonus pay for amazing work on #OSS 00010000503 +622231380104327310141741503080000000000#417313605724# Olivia Martinez 1121042880000504 +705Ravenhelix bonus pay for amazing work on #OSS 00010000504 +622231380104200584348012147700000000000#475781862325# Andrew Thompson 1121042880000505 +705Songfoil bonus pay for amazing work on #OSS 00010000505 +622231380104130161643246654870000000000#820312556126# Madison Smith 1121042880000506 +705Spikepeppermint bonus pay for amazing work on #OSS 00010000506 +622231380104022567810243726520000000000#030378157038# Andrew Garcia 1121042880000507 +705Swallowquick bonus pay for amazing work on #OSS 00010000507 +622231380104580645034552720760000000000#665520704346# Emily Johnson 1121042880000508 +705Facetundra bonus pay for amazing work on #OSS 00010000508 +622231380104664751228324246720000000000#718212147241# Addison Smith 1121042880000509 +705Houndbead bonus pay for amazing work on #OSS 00010000509 +622231380104756120431138752330000000000#568621543017# Benjamin Martin 1121042880000510 +705Tracksapphire bonus pay for amazing work on #OSS 00010000510 +622231380104123844557043402530000000000#020511357172# Ethan Taylor 1121042880000511 +705Elklunar bonus pay for amazing work on #OSS 00010000511 +622231380104874742148754073830000000000#057661524205# Matthew Harris 1121042880000512 +705Whimseyhate bonus pay for amazing work on #OSS 00010000512 +622231380104806628102735051310000000000#646814046566# Emma Miller 1121042880000513 +705Thumbemerald bonus pay for amazing work on #OSS 00010000513 +622231380104760031275434110430000000000#030741681845# Anthony Brown 1121042880000514 +705Pixiemetal bonus pay for amazing work on #OSS 00010000514 +622231380104612014663055670470000000000#037205224243# Andrew Martin 1121042880000515 +705Scarwell bonus pay for amazing work on #OSS 00010000515 +622231380104054130472860603450000000000#852126656535# Mia Thomas 1121042880000516 +705Mustangcharm bonus pay for amazing work on #OSS 00010000516 +622231380104475534354808184810000000000#032155267286# Liam Davis 1121042880000517 +705Cloudpollen bonus pay for amazing work on #OSS 00010000517 +622231380104018200220576582510000000000#672841811627# Michael Harris 1121042880000518 +705Fancierbog bonus pay for amazing work on #OSS 00010000518 +622231380104682488212074402280000000000#102012703035# Joseph Harris 1121042880000519 +705Gamblerash bonus pay for amazing work on #OSS 00010000519 +622231380104004140418341272000000000000#204526636456# Avery Miller 1121042880000520 +705Spearblue bonus pay for amazing work on #OSS 00010000520 +622231380104113888633880341500000000000#067783038521# Lily Anderson 1121042880000521 +705Diverebony bonus pay for amazing work on #OSS 00010000521 +622231380104646405027755610740000000000#314388061484# Mia Martinez 1121042880000522 +705Reaperleather bonus pay for amazing work on #OSS 00010000522 +622231380104336417757318424880000000000#786806247418# Natalie Martin 1121042880000523 +705Boarmidnight bonus pay for amazing work on #OSS 00010000523 +622231380104056500067752188540000000000#772856500470# Isabella Smith 1121042880000524 +705Thumbgiant bonus pay for amazing work on #OSS 00010000524 +622231380104386643365182172850000000000#072336001418# Anthony Jackson 1121042880000525 +705Pumafree bonus pay for amazing work on #OSS 00010000525 +622231380104417528681057206800000000000#226787038411# Isabella Martin 1121042880000526 +705Hissershore bonus pay for amazing work on #OSS 00010000526 +622231380104117650866342751640000000000#886513683423# Jayden Jackson 1121042880000527 +705Roverthorn bonus pay for amazing work on #OSS 00010000527 +622231380104267575078504146220000000000#375515424666# Jayden White 1121042880000528 +705Weaverrag bonus pay for amazing work on #OSS 00010000528 +622231380104614802173160686500000000000#334460763231# David Miller 1121042880000529 +705Jaguarcrimson bonus pay for amazing work on #OSS 00010000529 +622231380104747248224657078620000000000#870613227754# Andrew Anderson 1121042880000530 +705Orioletulip bonus pay for amazing work on #OSS 00010000530 +622231380104780211461511874120000000000#432863184343# Ethan Smith 1121042880000531 +705Wolverineribbon bonus pay for amazing work on #OSS 00010000531 +622231380104888212723042868360000000000#467738116776# Chloe Jones 1121042880000532 +705Wizardshy bonus pay for amazing work on #OSS 00010000532 +622231380104784623145457247520000000000#720038222038# Jayden Garcia 1121042880000533 +705Lanternboulder bonus pay for amazing work on #OSS 00010000533 +622231380104236115518058821640000000000#574338436245# William Harris 1121042880000534 +705Apemesquite bonus pay for amazing work on #OSS 00010000534 +622231380104641183637680147060000000000#574712331642# Natalie Harris 1121042880000535 +705Liftermeadow bonus pay for amazing work on #OSS 00010000535 +622231380104055882577557606140000000000#236425480688# James Wilson 1121042880000536 +705Foebramble bonus pay for amazing work on #OSS 00010000536 +622231380104622247458724354480000000000#026826857402# Natalie Anderson 1121042880000537 +705Grinbog bonus pay for amazing work on #OSS 00010000537 +622231380104377120503211625450000000000#203057662854# Aiden Anderson 1121042880000538 +705Owlautumn bonus pay for amazing work on #OSS 00010000538 +622231380104702830183272135700000000000#867056584754# Addison White 1121042880000539 +705Weedgrove bonus pay for amazing work on #OSS 00010000539 +622231380104022048300343157570000000000#442082183616# Matthew Johnson 1121042880000540 +705Jawwalnut bonus pay for amazing work on #OSS 00010000540 +622231380104606041768035201820000000000#800211236884# Aiden Thomas 1121042880000541 +705Witchsavage bonus pay for amazing work on #OSS 00010000541 +622231380104056753666427568630000000000#082058233416# Benjamin Smith 1121042880000542 +705Tigernarrow bonus pay for amazing work on #OSS 00010000542 +622231380104626161378151274630000000000#150866871763# Natalie Martinez 1121042880000543 +705Cloudpeat bonus pay for amazing work on #OSS 00010000543 +622231380104242258176525443770000000000#601704021482# Daniel Garcia 1121042880000544 +705Palmnimble bonus pay for amazing work on #OSS 00010000544 +622231380104183156414124354450000000000#103652556718# Aubrey Johnson 1121042880000545 +705Arrowscarlet bonus pay for amazing work on #OSS 00010000545 +622231380104763380670045027510000000000#803633003446# Michael Jones 1121042880000546 +705Stagtrail bonus pay for amazing work on #OSS 00010000546 +622231380104417867473675232510000000000#632268570202# Sophia Smith 1121042880000547 +705Beardesert bonus pay for amazing work on #OSS 00010000547 +622231380104622414031428224720000000000#158851147258# Sofia Davis 1121042880000548 +705Elkhurricane bonus pay for amazing work on #OSS 00010000548 +622231380104280560765847736430000000000#357185240068# David Garcia 1121042880000549 +705Riderbrown bonus pay for amazing work on #OSS 00010000549 +622231380104036627702045760800000000000#171087630636# Andrew Jones 1121042880000550 +705Roarerevening bonus pay for amazing work on #OSS 00010000550 +622231380104304245617052421530000000000#356103356083# Andrew Anderson 1121042880000551 +705Doomcosmic bonus pay for amazing work on #OSS 00010000551 +622231380104041234654757208600000000000#043646323306# Isabella Miller 1121042880000552 +705Sargentnimble bonus pay for amazing work on #OSS 00010000552 +622231380104720665080080817340000000000#373855780160# Isabella Anderson 1121042880000553 +705Storklead bonus pay for amazing work on #OSS 00010000553 +622231380104134407023802100020000000000#872414183847# Emma Smith 1121042880000554 +705Thumbgrave bonus pay for amazing work on #OSS 00010000554 +622231380104472326015752455350000000000#868224527864# Mason Jones 1121042880000555 +705Sargentlunar bonus pay for amazing work on #OSS 00010000555 +622231380104722242428563626050000000000#465082188288# Liam Thompson 1121042880000556 +705Carversand bonus pay for amazing work on #OSS 00010000556 +622231380104462082541666751160000000000#484648616162# Mason Thompson 1121042880000557 +705Monkeychatter bonus pay for amazing work on #OSS 00010000557 +622231380104174663648302700630000000000#265025363462# Abigail Taylor 1121042880000558 +705Whaleproud bonus pay for amazing work on #OSS 00010000558 +622231380104607646472374653580000000000#116407330377# Joshua Martinez 1121042880000559 +705Glassfossil bonus pay for amazing work on #OSS 00010000559 +622231380104543523102748866210000000000#773647050240# Elijah Martin 1121042880000560 +705Pumahoney bonus pay for amazing work on #OSS 00010000560 +622231380104160722283653124360000000000#857217228204# Matthew Johnson 1121042880000561 +705Skinnerheather bonus pay for amazing work on #OSS 00010000561 +622231380104744402103407688340000000000#876030143377# Liam Miller 1121042880000562 +705Turnerwheat bonus pay for amazing work on #OSS 00010000562 +622231380104430872666773327320000000000#611567735255# Mason Anderson 1121042880000563 +705Ridervoid bonus pay for amazing work on #OSS 00010000563 +622231380104603047367568447230000000000#458756563270# Ethan Jones 1121042880000564 +705Legenddog bonus pay for amazing work on #OSS 00010000564 +622231380104443551361825362330000000000#741542312007# Aubrey Martinez 1121042880000565 +705Vulturealpine bonus pay for amazing work on #OSS 00010000565 +622231380104707478078056631380000000000#472358612571# Avery Smith 1121042880000566 +705Wyrmmica bonus pay for amazing work on #OSS 00010000566 +622231380104476206308447313740000000000#288174712684# Andrew Thomas 1121042880000567 +705Goatwhite bonus pay for amazing work on #OSS 00010000567 +622231380104771645330425723460000000000#741601005551# Isabella Miller 1121042880000568 +705Seekerdeep bonus pay for amazing work on #OSS 00010000568 +622231380104837114723582385610000000000#066232566576# Sophia Martin 1121042880000569 +705Chargersapphire bonus pay for amazing work on #OSS 00010000569 +622231380104033730150528228300000000000#657613701877# Ella White 1121042880000570 +705Kickerarrow bonus pay for amazing work on #OSS 00010000570 +622231380104125431360166372680000000000#347676321247# Madison Martinez 1121042880000571 +705Questerlace bonus pay for amazing work on #OSS 00010000571 +622231380104027587360222744850000000000#606281015164# David Robinson 1121042880000572 +705Walkerdull bonus pay for amazing work on #OSS 00010000572 +622231380104580510755661581250000000000#081417682020# Ava Wilson 1121042880000573 +705Hareshadow bonus pay for amazing work on #OSS 00010000573 +622231380104653744123320181110000000000#541557340446# Alexander Williams 1121042880000574 +705Chopperpink bonus pay for amazing work on #OSS 00010000574 +622231380104202543578081548800000000000#132701302666# Matthew Anderson 1121042880000575 +705Twistertwisty bonus pay for amazing work on #OSS 00010000575 +622231380104012508420537717600000000000#004065152364# Aiden Thompson 1121042880000576 +705Fairylake bonus pay for amazing work on #OSS 00010000576 +622231380104728284802142361500000000000#242334524350# Sofia White 1121042880000577 +705Bearspring bonus pay for amazing work on #OSS 00010000577 +622231380104338758877308563000000000000#867032875206# Emma Johnson 1121042880000578 +705Ridgecoconut bonus pay for amazing work on #OSS 00010000578 +622231380104611327572611316210000000000#377281277437# Joseph Martin 1121042880000579 +705Fingerivory bonus pay for amazing work on #OSS 00010000579 +622231380104012363102251515620000000000#657631831328# Emma Brown 1121042880000580 +705Spiritgravel bonus pay for amazing work on #OSS 00010000580 +622231380104178063442873472200000000000#073578155753# Jayden Harris 1121042880000581 +705Yakpower bonus pay for amazing work on #OSS 00010000581 +622231380104157152560280501570000000000#046515435783# Addison Johnson 1121042880000582 +705Fairytreasure bonus pay for amazing work on #OSS 00010000582 +622231380104787742386327205010000000000#867140418735# Addison Anderson 1121042880000583 +705Weaverslow bonus pay for amazing work on #OSS 00010000583 +622231380104116246341688456580000000000#773186408072# Emma Miller 1121042880000584 +705Mindleather bonus pay for amazing work on #OSS 00010000584 +622231380104147231816360808730000000000#206145610614# Noah Thompson 1121042880000585 +705Lighterwhip bonus pay for amazing work on #OSS 00010000585 +622231380104377568325142088430000000000#160844262047# Alexander Martinez 1121042880000586 +705Handsly bonus pay for amazing work on #OSS 00010000586 +622231380104327668337756268000000000000#583516878710# Alexander Martinez 1121042880000587 +705Hyenadestiny bonus pay for amazing work on #OSS 00010000587 +622231380104738276883820413820000000000#412538285561# Jacob Jackson 1121042880000588 +705Sparrowblue bonus pay for amazing work on #OSS 00010000588 +622231380104711637182852464140000000000#461241076263# Chloe Robinson 1121042880000589 +705Lioncrack bonus pay for amazing work on #OSS 00010000589 +622231380104457442751687873230000000000#655415812736# Sofia Davis 1121042880000590 +705Heronbrook bonus pay for amazing work on #OSS 00010000590 +622231380104532620313816147850000000000#085587155184# Mia Williams 1121042880000591 +705Singerpyrite bonus pay for amazing work on #OSS 00010000591 +622231380104055227420170333700000000000#501384540608# Zoey Robinson 1121042880000592 +705Storktorch bonus pay for amazing work on #OSS 00010000592 +622231380104685340181463302420000000000#271727710150# David Garcia 1121042880000593 +705Raptortiny bonus pay for amazing work on #OSS 00010000593 +622231380104010768322154855130000000000#510217168678# Aubrey Thompson 1121042880000594 +705Cloakgrove bonus pay for amazing work on #OSS 00010000594 +622231380104806158024541388880000000000#571308640884# Sophia Davis 1121042880000595 +705Wolverinepaper bonus pay for amazing work on #OSS 00010000595 +622231380104562413760684183280000000000#124815570437# Mia Brown 1121042880000596 +705Catchersouth bonus pay for amazing work on #OSS 00010000596 +622231380104262405807464344830000000000#757467381737# Jayden Thompson 1121042880000597 +705Turnerrose bonus pay for amazing work on #OSS 00010000597 +622231380104563476281562838060000000000#010877464000# Aiden Johnson 1121042880000598 +705Ravensolar bonus pay for amazing work on #OSS 00010000598 +622231380104106641538406103160000000000#004752406482# Zoey Jones 1121042880000599 +705Flamepolar bonus pay for amazing work on #OSS 00010000599 +622231380104848375767641374710000000000#883382542651# Michael Garcia 1121042880000600 +705Runnershard bonus pay for amazing work on #OSS 00010000600 +622231380104854463063640503740000000000#527502537475# David Anderson 1121042880000601 +705Snarlcerulean bonus pay for amazing work on #OSS 00010000601 +622231380104316710322713145510000000000#543532510770# Noah Harris 1121042880000602 +705Finfortune bonus pay for amazing work on #OSS 00010000602 +622231380104503331265081722470000000000#221111455046# Noah Martin 1121042880000603 +705Puppydiamond bonus pay for amazing work on #OSS 00010000603 +622231380104008506246272478860000000000#481011058473# Joseph Anderson 1121042880000604 +705Sightcoal bonus pay for amazing work on #OSS 00010000604 +622231380104402128605847032350000000000#663236045527# Michael Anderson 1121042880000605 +705Roarerchestnut bonus pay for amazing work on #OSS 00010000605 +622231380104682587157583864270000000000#254141745237# Zoey Anderson 1121042880000606 +705Horseindigo bonus pay for amazing work on #OSS 00010000606 +622231380104134742585717868720000000000#852722142743# Mason Miller 1121042880000607 +705Friendsaber bonus pay for amazing work on #OSS 00010000607 +622231380104848127513125226520000000000#330127244251# Elizabeth Anderson 1121042880000608 +705Bitedawn bonus pay for amazing work on #OSS 00010000608 +622231380104574722706820007140000000000#300051265624# Joseph Wilson 1121042880000609 +705Shriekcold bonus pay for amazing work on #OSS 00010000609 +622231380104601800673386213450000000000#882225553608# Noah Moore 1121042880000610 +705Faceglory bonus pay for amazing work on #OSS 00010000610 +622231380104202457408422642530000000000#613041366720# Matthew Jones 1121042880000611 +705Dragonmint bonus pay for amazing work on #OSS 00010000611 +622231380104548150158787155440000000000#786363583740# Sofia Taylor 1121042880000612 +705Spikequill bonus pay for amazing work on #OSS 00010000612 +622231380104443834153440577700000000000#351625163071# Chloe Robinson 1121042880000613 +705Chillerhail bonus pay for amazing work on #OSS 00010000613 +622231380104674261861686557380000000000#854310544423# Michael Jackson 1121042880000614 +705Frightred bonus pay for amazing work on #OSS 00010000614 +622231380104253787070288544660000000000#015586856467# Sophia Miller 1121042880000615 +705Snakegreen bonus pay for amazing work on #OSS 00010000615 +622231380104468706553408503720000000000#065774045128# Joshua Robinson 1121042880000616 +705Razorzircon bonus pay for amazing work on #OSS 00010000616 +622231380104738345268286160370000000000#341373645726# Madison Thompson 1121042880000617 +705Boardenim bonus pay for amazing work on #OSS 00010000617 +622231380104555706750307704050000000000#077212574634# Ella Moore 1121042880000618 +705Boltnarrow bonus pay for amazing work on #OSS 00010000618 +622231380104361801275643024000000000000#641658542572# David Thomas 1121042880000619 +705Eyelava bonus pay for amazing work on #OSS 00010000619 +622231380104548743468655372500000000000#554636373505# Ella Johnson 1121042880000620 +705Whalescarlet bonus pay for amazing work on #OSS 00010000620 +622231380104317757052857266880000000000#635466060376# Joshua Jones 1121042880000621 +705Seekerflame bonus pay for amazing work on #OSS 00010000621 +622231380104050003400746630010000000000#331522503365# Benjamin Martinez 1121042880000622 +705Glazerbuttercup bonus pay for amazing work on #OSS 00010000622 +622231380104387288008085036880000000000#762606361201# Anthony White 1121042880000623 +705Raccoonquartz bonus pay for amazing work on #OSS 00010000623 +622231380104773628503231848610000000000#415514847687# Noah Brown 1121042880000624 +705Foepear bonus pay for amazing work on #OSS 00010000624 +622231380104138524147867740560000000000#162474515450# Joseph Brown 1121042880000625 +705Forgerrag bonus pay for amazing work on #OSS 00010000625 +622231380104134636108446405570000000000#726574124128# Mason Robinson 1121042880000626 +705Leaderfish bonus pay for amazing work on #OSS 00010000626 +622231380104151545675582710420000000000#021885364855# Elizabeth Thompson 1121042880000627 +705Storkribbon bonus pay for amazing work on #OSS 00010000627 +622231380104323215831736300340000000000#205240421201# Charlotte Wilson 1121042880000628 +705Binderflower bonus pay for amazing work on #OSS 00010000628 +622231380104016722466208804560000000000#750327138088# Matthew Anderson 1121042880000629 +705Thumbsnapdragon bonus pay for amazing work on #OSS 00010000629 +622231380104533270468610662220000000000#286388823313# Liam Davis 1121042880000630 +705Whalecitrine bonus pay for amazing work on #OSS 00010000630 +622231380104174283222870457380000000000#501648155642# Chloe Miller 1121042880000631 +705Diverheather bonus pay for amazing work on #OSS 00010000631 +622231380104320448073808827820000000000#788675041826# Emily Williams 1121042880000632 +705Heronjuniper bonus pay for amazing work on #OSS 00010000632 +622231380104110381676407854650000000000#650872618071# Jacob Wilson 1121042880000633 +705Eatercake bonus pay for amazing work on #OSS 00010000633 +622231380104872122478683241660000000000#868726502453# Elizabeth Martinez 1121042880000634 +705Skinnercyan bonus pay for amazing work on #OSS 00010000634 +622231380104081081281473051880000000000#208311500645# David Martinez 1121042880000635 +705Beakbird bonus pay for amazing work on #OSS 00010000635 +622231380104273128810702670500000000000#701351730446# Ella Brown 1121042880000636 +705Owlbrick bonus pay for amazing work on #OSS 00010000636 +622231380104710317121002348240000000000#748852113423# Isabella Smith 1121042880000637 +705Twistergossamer bonus pay for amazing work on #OSS 00010000637 +622231380104824283843416774080000000000#340040870644# Mia Moore 1121042880000638 +705Eagleflash bonus pay for amazing work on #OSS 00010000638 +622231380104631383213273507110000000000#242536340366# James Jackson 1121042880000639 +705Sparrowjet bonus pay for amazing work on #OSS 00010000639 +622231380104737013461654326270000000000#610117628651# Ava Martin 1121042880000640 +705Ridermuck bonus pay for amazing work on #OSS 00010000640 +622231380104684235751747333110000000000#313884557852# Elizabeth Williams 1121042880000641 +705Warlockrelic bonus pay for amazing work on #OSS 00010000641 +622231380104058073251847147130000000000#160435758045# William Martinez 1121042880000642 +705Slayerfluff bonus pay for amazing work on #OSS 00010000642 +622231380104374201041486860240000000000#022712103286# Ava Brown 1121042880000643 +705Raccoonweed bonus pay for amazing work on #OSS 00010000643 +622231380104043717652876671180000000000#448444707225# Mia Harris 1121042880000644 +705Queenshadow bonus pay for amazing work on #OSS 00010000644 +622231380104635532305011865880000000000#302130407184# Emma Harris 1121042880000645 +705Gememerald bonus pay for amazing work on #OSS 00010000645 +622231380104033237325231432040000000000#106622864150# Joseph Robinson 1121042880000646 +705Legdirt bonus pay for amazing work on #OSS 00010000646 +622231380104133304408565514650000000000#050573768418# Jacob Moore 1121042880000647 +705Snoutpale bonus pay for amazing work on #OSS 00010000647 +622231380104408232016720826630000000000#745623826772# Abigail Thompson 1121042880000648 +705Bugpatch bonus pay for amazing work on #OSS 00010000648 +622231380104181475702070255550000000000#275635120581# Chloe Williams 1121042880000649 +705Chilleroil bonus pay for amazing work on #OSS 00010000649 +622231380104105740583306135160000000000#021827632750# James White 1121042880000650 +705Cubpale bonus pay for amazing work on #OSS 00010000650 +622231380104488514337425650860000000000#888123007856# William Brown 1121042880000651 +705Lightningcord bonus pay for amazing work on #OSS 00010000651 +622231380104222806857034817450000000000#305275531381# Chloe Johnson 1121042880000652 +705Dropsage bonus pay for amazing work on #OSS 00010000652 +622231380104583240685353685660000000000#315405080115# Jacob Anderson 1121042880000653 +705Weaversand bonus pay for amazing work on #OSS 00010000653 +622231380104735721124466352470000000000#622474122581# Aubrey Martin 1121042880000654 +705Keepersouth bonus pay for amazing work on #OSS 00010000654 +622231380104752741373335027680000000000#861182416727# Charlotte Martinez 1121042880000655 +705Craftersnapdragon bonus pay for amazing work on #OSS 00010000655 +622231380104840541325472615700000000000#436674556438# Elijah Taylor 1121042880000656 +705Ninjashag bonus pay for amazing work on #OSS 00010000656 +622231380104788874040378234450000000000#628370061463# Daniel Thompson 1121042880000657 +705Slicerregal bonus pay for amazing work on #OSS 00010000657 +622231380104757876843483162460000000000#448026685223# Mason Thomas 1121042880000658 +705Boltsplit bonus pay for amazing work on #OSS 00010000658 +622231380104202384114534180140000000000#685043883534# Mason Harris 1121042880000659 +705Bitedisco bonus pay for amazing work on #OSS 00010000659 +622231380104631508426443621630000000000#831603554551# Mason Brown 1121042880000660 +705Mastergranite bonus pay for amazing work on #OSS 00010000660 +622231380104443425406560875760000000000#727114713370# Joseph Jones 1121042880000661 +705Paintercrocus bonus pay for amazing work on #OSS 00010000661 +622231380104402883750616535160000000000#476012056373# Abigail Harris 1121042880000662 +705Beaksurf bonus pay for amazing work on #OSS 00010000662 +622231380104225530147413323660000000000#681738256058# Elizabeth Anderson 1121042880000663 +705Warriorshag bonus pay for amazing work on #OSS 00010000663 +622231380104121348875843205610000000000#245468331372# Benjamin White 1121042880000664 +705Bootdull bonus pay for amazing work on #OSS 00010000664 +622231380104635777334388718440000000000#651336501701# Charlotte Thomas 1121042880000665 +705Saverplanet bonus pay for amazing work on #OSS 00010000665 +622231380104857217340348848450000000000#414707111457# Liam White 1121042880000666 +705Ringersnow bonus pay for amazing work on #OSS 00010000666 +622231380104016574252012568350000000000#276068356015# Addison Wilson 1121042880000667 +705Friendheather bonus pay for amazing work on #OSS 00010000667 +622231380104062223156857076580000000000#524821636600# Joseph Brown 1121042880000668 +705Spikeiris bonus pay for amazing work on #OSS 00010000668 +622231380104330858673365025580000000000#663064664647# Madison Davis 1121042880000669 +705Kittenmeadow bonus pay for amazing work on #OSS 00010000669 +622231380104230468326055736680000000000#835481734748# Jacob Jackson 1121042880000670 +705Ravenfate bonus pay for amazing work on #OSS 00010000670 +622231380104574157773400834860000000000#706646004275# Noah Robinson 1121042880000671 +705Kangaroobattle bonus pay for amazing work on #OSS 00010000671 +622231380104647672137226334420000000000#506465877648# Ethan Jones 1121042880000672 +705Pipersticky bonus pay for amazing work on #OSS 00010000672 +622231380104555221146413826420000000000#737725248275# Alexander Thomas 1121042880000673 +705Raptorcurly bonus pay for amazing work on #OSS 00010000673 +622231380104666331165833220670000000000#860888667784# Jacob Brown 1121042880000674 +705Swordflicker bonus pay for amazing work on #OSS 00010000674 +622231380104147803023032634610000000000#260633013580# William Davis 1121042880000675 +705Thunderglaze bonus pay for amazing work on #OSS 00010000675 +622231380104186200071507688710000000000#776647070308# Avery Garcia 1121042880000676 +705Llamaspeckle bonus pay for amazing work on #OSS 00010000676 +622231380104443773248260214400000000000#437258115771# Aiden Wilson 1121042880000677 +705Weavertopaz bonus pay for amazing work on #OSS 00010000677 +622231380104038732282104587000000000000#010804821301# Zoey Martinez 1121042880000678 +705Puppywhip bonus pay for amazing work on #OSS 00010000678 +622231380104225237433160774620000000000#460558483206# Daniel Robinson 1121042880000679 +705Spiderplain bonus pay for amazing work on #OSS 00010000679 +622231380104420516701703658880000000000#265806408267# Liam Robinson 1121042880000680 +705Spriteflicker bonus pay for amazing work on #OSS 00010000680 +622231380104211867612231578050000000000#614248733078# Addison Moore 1121042880000681 +705Razorsage bonus pay for amazing work on #OSS 00010000681 +622231380104583375576711236160000000000#017021437274# Matthew Martinez 1121042880000682 +705Pantherfrost bonus pay for amazing work on #OSS 00010000682 +622231380104228063082680511500000000000#653254560064# Ava Miller 1121042880000683 +705Lancerpeat bonus pay for amazing work on #OSS 00010000683 +622231380104220024844310151210000000000#342314823454# James Thompson 1121042880000684 +705Snarlpewter bonus pay for amazing work on #OSS 00010000684 +622231380104717828722553386230000000000#262763775384# Elizabeth Jackson 1121042880000685 +705Ogredandy bonus pay for amazing work on #OSS 00010000685 +622231380104868300381648764740000000000#706536250635# Anthony Smith 1121042880000686 +705Bowgiant bonus pay for amazing work on #OSS 00010000686 +622231380104562012137704756860000000000#378623682500# Jayden Wilson 1121042880000687 +705Lightersticky bonus pay for amazing work on #OSS 00010000687 +622231380104557160611786443500000000000#806884657053# Charlotte Wilson 1121042880000688 +705Huggerjasper bonus pay for amazing work on #OSS 00010000688 +622231380104242620183075620850000000000#210620607758# Natalie Taylor 1121042880000689 +705Diverrune bonus pay for amazing work on #OSS 00010000689 +622231380104370570661444470370000000000#522636542653# Daniel Taylor 1121042880000690 +705Monkeypitch bonus pay for amazing work on #OSS 00010000690 +622231380104718625105171541850000000000#157726788136# Jacob Garcia 1121042880000691 +705Spritesatin bonus pay for amazing work on #OSS 00010000691 +622231380104613815377500785370000000000#260647127527# Zoey Anderson 1121042880000692 +705Sparrowhail bonus pay for amazing work on #OSS 00010000692 +622231380104342363704866560700000000000#372711871741# Abigail Johnson 1121042880000693 +705Carverbrave bonus pay for amazing work on #OSS 00010000693 +622231380104251586880073118570000000000#320670254167# Liam Martin 1121042880000694 +705Paladinclever bonus pay for amazing work on #OSS 00010000694 +622231380104064151765772464720000000000#042537340071# Natalie Taylor 1121042880000695 +705Divercoconut bonus pay for amazing work on #OSS 00010000695 +622231380104435102547152517450000000000#256530336055# Andrew Davis 1121042880000696 +705Stalkerpale bonus pay for amazing work on #OSS 00010000696 +622231380104518721306740654750000000000#325712353646# Ella Thomas 1121042880000697 +705Ringergranite bonus pay for amazing work on #OSS 00010000697 +622231380104045624628260654080000000000#840646740207# Anthony Martin 1121042880000698 +705Gazellemuck bonus pay for amazing work on #OSS 00010000698 +622231380104285613220300164730000000000#152888120135# Sophia Martinez 1121042880000699 +705Cloaksquare bonus pay for amazing work on #OSS 00010000699 +622231380104767288067002845020000000000#057625474786# Chloe Jackson 1121042880000700 +705Scalebow bonus pay for amazing work on #OSS 00010000700 +622231380104100548044412241380000000000#108123313626# James Jones 1121042880000701 +705Lifterspring bonus pay for amazing work on #OSS 00010000701 +622231380104568028462672425820000000000#726135280473# Jacob Harris 1121042880000702 +705Flygray bonus pay for amazing work on #OSS 00010000702 +622231380104547235552736411180000000000#755106081067# Isabella Wilson 1121042880000703 +705Tailbrown bonus pay for amazing work on #OSS 00010000703 +622231380104471723002357737630000000000#266541023734# Avery Brown 1121042880000704 +705Giverbog bonus pay for amazing work on #OSS 00010000704 +622231380104717685886728317130000000000#633131328756# Lily Smith 1121042880000705 +705Bellyregal bonus pay for amazing work on #OSS 00010000705 +622231380104088652366857855830000000000#607027848168# Aiden Robinson 1121042880000706 +705Oriolepurple bonus pay for amazing work on #OSS 00010000706 +622231380104611865034014483150000000000#427836814625# Charlotte Harris 1121042880000707 +705Killerpinto bonus pay for amazing work on #OSS 00010000707 +622231380104548125751737041670000000000#664202872526# Elijah White 1121042880000708 +705Pythonemerald bonus pay for amazing work on #OSS 00010000708 +622231380104002283278348841360000000000#410045314174# Chloe Williams 1121042880000709 +705Hidegiant bonus pay for amazing work on #OSS 00010000709 +622231380104386643347773130740000000000#648237538435# Zoey Jones 1121042880000710 +705Lightningplatinum bonus pay for amazing work on #OSS 00010000710 +622231380104864010886457207380000000000#511526552352# Isabella Smith 1121042880000711 +705Lionnotch bonus pay for amazing work on #OSS 00010000711 +622231380104586715864127088340000000000#800837025408# Emma White 1121042880000712 +705Seerdandelion bonus pay for amazing work on #OSS 00010000712 +622231380104188230002230837830000000000#818108231088# Elijah Martinez 1121042880000713 +705Stalkersponge bonus pay for amazing work on #OSS 00010000713 +622231380104742636344175424730000000000#155015785260# Avery Martin 1121042880000714 +705Thunderluminous bonus pay for amazing work on #OSS 00010000714 +622231380104317203015283606860000000000#863258354453# Charlotte Brown 1121042880000715 +705Yaksaber bonus pay for amazing work on #OSS 00010000715 +622231380104118156073812657330000000000#632844145055# Michael Brown 1121042880000716 +705Songvalley bonus pay for amazing work on #OSS 00010000716 +622231380104370004123614672070000000000#215012046010# Aubrey Thomas 1121042880000717 +705Jesterstorm bonus pay for amazing work on #OSS 00010000717 +622231380104534441167141286460000000000#061452454852# James Thomas 1121042880000718 +705Thiefiris bonus pay for amazing work on #OSS 00010000718 +622231380104016151304552381800000000000#467086844806# Lily Wilson 1121042880000719 +705Boaglitter bonus pay for amazing work on #OSS 00010000719 +622231380104877740621882103500000000000#561577816857# Liam Garcia 1121042880000720 +705Musesnow bonus pay for amazing work on #OSS 00010000720 +622231380104236304064772648850000000000#608446701362# David White 1121042880000721 +705Braidblossom bonus pay for amazing work on #OSS 00010000721 +622231380104118641544182621020000000000#085713577724# Liam Martin 1121042880000722 +705Thumbwinter bonus pay for amazing work on #OSS 00010000722 +622231380104875561883826013770000000000#685376760535# Noah Wilson 1121042880000723 +705Crystalsalt bonus pay for amazing work on #OSS 00010000723 +622231380104646533113578274300000000000#272412735604# Michael Garcia 1121042880000724 +705Bitegiant bonus pay for amazing work on #OSS 00010000724 +622231380104552007223560036510000000000#541771483485# Abigail Davis 1121042880000725 +705Dancerflicker bonus pay for amazing work on #OSS 00010000725 +622231380104680462788182584770000000000#701001386711# Sofia Thompson 1121042880000726 +705Knifefluff bonus pay for amazing work on #OSS 00010000726 +622231380104135152141662886460000000000#631440765784# Ethan Jones 1121042880000727 +705Boltseed bonus pay for amazing work on #OSS 00010000727 +622231380104676014866777661000000000000#028173672481# Joshua Davis 1121042880000728 +705Arrowgrass bonus pay for amazing work on #OSS 00010000728 +622231380104350674870578857710000000000#660845358113# Joseph Williams 1121042880000729 +705Trackdandelion bonus pay for amazing work on #OSS 00010000729 +622231380104712311581362004700000000000#758815525722# Daniel Jackson 1121042880000730 +705Ocelotchisel bonus pay for amazing work on #OSS 00010000730 +622231380104427658788110771020000000000#724665070200# William Harris 1121042880000731 +705Soarerzircon bonus pay for amazing work on #OSS 00010000731 +622231380104418244808636162150000000000#336537437588# James Johnson 1121042880000732 +705Dartquark bonus pay for amazing work on #OSS 00010000732 +622231380104325125600011452710000000000#416314558074# David Taylor 1121042880000733 +705Shoulderproud bonus pay for amazing work on #OSS 00010000733 +622231380104370350726187742140000000000#366322410253# Liam Thompson 1121042880000734 +705Whalelong bonus pay for amazing work on #OSS 00010000734 +622231380104652716273871333240000000000#723533648882# Andrew Miller 1121042880000735 +705Wolverineroad bonus pay for amazing work on #OSS 00010000735 +622231380104811241440187517610000000000#281230286756# Emma Wilson 1121042880000736 +705Friendjewel bonus pay for amazing work on #OSS 00010000736 +622231380104877341584152361620000000000#704678502764# Mason Wilson 1121042880000737 +705Liftervine bonus pay for amazing work on #OSS 00010000737 +622231380104285141041667877820000000000#655513062014# Daniel Martinez 1121042880000738 +705Traderring bonus pay for amazing work on #OSS 00010000738 +622231380104070483135657816870000000000#251238447256# Emily Martinez 1121042880000739 +705Robinquark bonus pay for amazing work on #OSS 00010000739 +622231380104136266535830165760000000000#815028413840# David Brown 1121042880000740 +705Orioleebony bonus pay for amazing work on #OSS 00010000740 +622231380104828183146242227630000000000#087858624227# Jacob Robinson 1121042880000741 +705Paladinsalt bonus pay for amazing work on #OSS 00010000741 +622231380104052448148382813760000000000#764502334483# Joshua Wilson 1121042880000742 +705Warlocklemon bonus pay for amazing work on #OSS 00010000742 +622231380104534803310822284820000000000#348284355180# Avery Taylor 1121042880000743 +705Followernavy bonus pay for amazing work on #OSS 00010000743 +622231380104708056306771428810000000000#641763113100# Addison Smith 1121042880000744 +705Dancerbutton bonus pay for amazing work on #OSS 00010000744 +622231380104802537456656058620000000000#036430624126# Zoey Williams 1121042880000745 +705Birdgranite bonus pay for amazing work on #OSS 00010000745 +622231380104385287660333610450000000000#227541613381# Sophia Thompson 1121042880000746 +705Boaslow bonus pay for amazing work on #OSS 00010000746 +622231380104735178723583711110000000000#715358414806# Madison Martin 1121042880000747 +705Roversurf bonus pay for amazing work on #OSS 00010000747 +622231380104733583740715448770000000000#878038253710# Noah Moore 1121042880000748 +705Neckroan bonus pay for amazing work on #OSS 00010000748 +622231380104028151026880688810000000000#175257224145# Avery Davis 1121042880000749 +705Coyoterune bonus pay for amazing work on #OSS 00010000749 +622231380104240500710512836840000000000#513720572570# Emma Harris 1121042880000750 +705Knifedesert bonus pay for amazing work on #OSS 00010000750 +622231380104087575506831643720000000000#110608477247# Emily Smith 1121042880000751 +705Frilloil bonus pay for amazing work on #OSS 00010000751 +622231380104448313177212111140000000000#101262532471# Aubrey Anderson 1121042880000752 +705Jackalperidot bonus pay for amazing work on #OSS 00010000752 +622231380104861514742223241420000000000#768553236002# Daniel Thomas 1121042880000753 +705Antelopeswamp bonus pay for amazing work on #OSS 00010000753 +622231380104385341556015666380000000000#110164658202# James Garcia 1121042880000754 +705Watcherautumn bonus pay for amazing work on #OSS 00010000754 +622231380104014354068325664460000000000#411138752112# Matthew Garcia 1121042880000755 +705Scowlcypress bonus pay for amazing work on #OSS 00010000755 +622231380104275687458462730430000000000#836426766142# Aiden Garcia 1121042880000756 +705Slothsky bonus pay for amazing work on #OSS 00010000756 +622231380104575308132211280580000000000#346180800128# Madison Taylor 1121042880000757 +705Condorvaliant bonus pay for amazing work on #OSS 00010000757 +622231380104136134661135158100000000000#546256500240# Lily Brown 1121042880000758 +705Legenddenim bonus pay for amazing work on #OSS 00010000758 +622231380104735315313324888680000000000#535561223636# Benjamin Williams 1121042880000759 +705Lighterviolet bonus pay for amazing work on #OSS 00010000759 +622231380104557166317386761630000000000#838637457740# Jayden Wilson 1121042880000760 +705Bonebutton bonus pay for amazing work on #OSS 00010000760 +622231380104033122608835515020000000000#244806488130# Noah Johnson 1121042880000761 +705Skinnerviridian bonus pay for amazing work on #OSS 00010000761 +622231380104021648815876137220000000000#825042230330# Natalie Jackson 1121042880000762 +705Hidefuschia bonus pay for amazing work on #OSS 00010000762 +622231380104212320081148566730000000000#817182512334# Lily Williams 1121042880000763 +705Lightningjust bonus pay for amazing work on #OSS 00010000763 +622231380104416814452631687530000000000#783451142643# Alexander Miller 1121042880000764 +705Flyvolcano bonus pay for amazing work on #OSS 00010000764 +622231380104865633867505004040000000000#205738773350# Olivia Martinez 1121042880000765 +705Leopardscarlet bonus pay for amazing work on #OSS 00010000765 +622231380104016455632527151730000000000#083346645476# Isabella Anderson 1121042880000766 +705Ocelotquartz bonus pay for amazing work on #OSS 00010000766 +622231380104642151544338435320000000000#518732210655# Abigail Davis 1121042880000767 +705Lordthorn bonus pay for amazing work on #OSS 00010000767 +622231380104187756567135085380000000000#170852655884# Matthew Smith 1121042880000768 +705Serpentpine bonus pay for amazing work on #OSS 00010000768 +622231380104243162124385335420000000000#450445312520# William Moore 1121042880000769 +705Palmdaffodil bonus pay for amazing work on #OSS 00010000769 +622231380104405681524534577510000000000#545707244581# Sophia Davis 1121042880000770 +705Griffinwhite bonus pay for amazing work on #OSS 00010000770 +622231380104676020857538751360000000000#424855812201# Matthew Wilson 1121042880000771 +705Fangstellar bonus pay for amazing work on #OSS 00010000771 +622231380104816280326882823840000000000#331437752033# Alexander Garcia 1121042880000772 +705Marklapis bonus pay for amazing work on #OSS 00010000772 +622231380104705150581536103740000000000#540340522665# Abigail Martin 1121042880000773 +705Leopardcanyon bonus pay for amazing work on #OSS 00010000773 +622231380104438827025403620870000000000#482306277321# Addison Brown 1121042880000774 +705Heronglaze bonus pay for amazing work on #OSS 00010000774 +622231380104876748145682618610000000000#448681447076# Charlotte Wilson 1121042880000775 +705Keeperchecker bonus pay for amazing work on #OSS 00010000775 +622231380104805573276654387630000000000#653782842388# Joseph Martinez 1121042880000776 +705Sparrowlight bonus pay for amazing work on #OSS 00010000776 +622231380104844660322825645680000000000#613687210388# Mason White 1121042880000777 +705Pawfringe bonus pay for amazing work on #OSS 00010000777 +622231380104750433283881348540000000000#408203315388# Elijah Moore 1121042880000778 +705Oxsmall bonus pay for amazing work on #OSS 00010000778 +622231380104357074453117537140000000000#274746231248# Benjamin Harris 1121042880000779 +705Mooserift bonus pay for amazing work on #OSS 00010000779 +622231380104628514088705710150000000000#002540123566# James Jones 1121042880000780 +705Speakerdisco bonus pay for amazing work on #OSS 00010000780 +622231380104101471224665067040000000000#573234482003# Elizabeth Miller 1121042880000781 +705Grabberslow bonus pay for amazing work on #OSS 00010000781 +622231380104364088248445467180000000000#623637310033# Joseph Thomas 1121042880000782 +705Stageast bonus pay for amazing work on #OSS 00010000782 +622231380104063130531121515710000000000#518480504383# Ella White 1121042880000783 +705Rippersummer bonus pay for amazing work on #OSS 00010000783 +622231380104778113587780225780000000000#475578615133# Elijah Smith 1121042880000784 +705Snappergrave bonus pay for amazing work on #OSS 00010000784 +622231380104326088260785244310000000000#860811256856# Olivia Taylor 1121042880000785 +705Weaseltruth bonus pay for amazing work on #OSS 00010000785 +622231380104731850153560623510000000000#280077618462# Elizabeth Robinson 1121042880000786 +705Bisoncloud bonus pay for amazing work on #OSS 00010000786 +622231380104272723675334668180000000000#880507868535# Liam Anderson 1121042880000787 +705Hairregal bonus pay for amazing work on #OSS 00010000787 +622231380104025186275401652830000000000#373013258644# Mia Harris 1121042880000788 +705Venomfog bonus pay for amazing work on #OSS 00010000788 +622231380104084104231507281220000000000#323863846031# Michael Martinez 1121042880000789 +705Ribisland bonus pay for amazing work on #OSS 00010000789 +622231380104252250563447142880000000000#463650883645# Ethan Martinez 1121042880000790 +705Bowtulip bonus pay for amazing work on #OSS 00010000790 +622231380104408111534036772470000000000#316768136577# Addison Davis 1121042880000791 +705Chargerlove bonus pay for amazing work on #OSS 00010000791 +622231380104788477516227068680000000000#124580414766# Abigail Martinez 1121042880000792 +705Crowtar bonus pay for amazing work on #OSS 00010000792 +622231380104646035604552383660000000000#584538386552# Joseph Jackson 1121042880000793 +705Maredandelion bonus pay for amazing work on #OSS 00010000793 +622231380104543502364576776410000000000#336273523016# Chloe White 1121042880000794 +705Turnerfork bonus pay for amazing work on #OSS 00010000794 +622231380104385560634682738010000000000#500302768254# Olivia Brown 1121042880000795 +705Sentrysage bonus pay for amazing work on #OSS 00010000795 +622231380104385421371106143070000000000#443264031788# Lily Wilson 1121042880000796 +705Fishfortune bonus pay for amazing work on #OSS 00010000796 +622231380104412456324271527060000000000#344842306560# Joseph Taylor 1121042880000797 +705Hairfrill bonus pay for amazing work on #OSS 00010000797 +622231380104810812122250321850000000000#417306604523# William Brown 1121042880000798 +705Houndsaber bonus pay for amazing work on #OSS 00010000798 +622231380104176060376303222360000000000#726856506241# Benjamin Garcia 1121042880000799 +705Scalecookie bonus pay for amazing work on #OSS 00010000799 +622231380104532657072838576400000000000#320764347283# Ava Taylor 1121042880000800 +705Seedquilt bonus pay for amazing work on #OSS 00010000800 +622231380104020875361142420670000000000#207544885075# Sofia Martinez 1121042880000801 +705Crusheroasis bonus pay for amazing work on #OSS 00010000801 +622231380104240011646238206220000000000#182201707557# Emily Williams 1121042880000802 +705Runnermorning bonus pay for amazing work on #OSS 00010000802 +622231380104032027811282416160000000000#623258026322# Aubrey Davis 1121042880000803 +705Wolfgeode bonus pay for amazing work on #OSS 00010000803 +622231380104423575782023141210000000000#818426733640# Noah Robinson 1121042880000804 +705Daggertranslucent bonus pay for amazing work on #OSS 00010000804 +622231380104702536601757210810000000000#224764737575# Emily Robinson 1121042880000805 +705Rabbitebony bonus pay for amazing work on #OSS 00010000805 +622231380104450460451642020130000000000#235187725365# Matthew Brown 1121042880000806 +705Whalebrass bonus pay for amazing work on #OSS 00010000806 +622231380104200060765754504150000000000#403625485684# Liam Garcia 1121042880000807 +705Arrowfoam bonus pay for amazing work on #OSS 00010000807 +622231380104463185117304545100000000000#101421208124# Elijah Smith 1121042880000808 +705Llamabutter bonus pay for amazing work on #OSS 00010000808 +622231380104512444007412360180000000000#228561251818# Madison Brown 1121042880000809 +705Dragonshy bonus pay for amazing work on #OSS 00010000809 +622231380104577663745566267580000000000#237724151357# Elizabeth Smith 1121042880000810 +705Chantergreat bonus pay for amazing work on #OSS 00010000810 +622231380104173676748847205810000000000#506248621537# Aiden Harris 1121042880000811 +705Crusherpitch bonus pay for amazing work on #OSS 00010000811 +622231380104168104744824310650000000000#073380358078# Joseph Smith 1121042880000812 +705Huntersatin bonus pay for amazing work on #OSS 00010000812 +622231380104856026545673285000000000000#117553417832# Elijah Harris 1121042880000813 +705Raydull bonus pay for amazing work on #OSS 00010000813 +622231380104646605856324074280000000000#017445436263# Mason Martinez 1121042880000814 +705Elfleather bonus pay for amazing work on #OSS 00010000814 +622231380104443801253567268330000000000#172111323211# Alexander Anderson 1121042880000815 +705Headplanet bonus pay for amazing work on #OSS 00010000815 +622231380104230703820168512510000000000#665143036654# Abigail Harris 1121042880000816 +705Thornlaser bonus pay for amazing work on #OSS 00010000816 +622231380104608801767675182520000000000#281344640771# David Williams 1121042880000817 +705Cowlchisel bonus pay for amazing work on #OSS 00010000817 +622231380104220741256657387450000000000#313245872702# Abigail Miller 1121042880000818 +705Owlcrocus bonus pay for amazing work on #OSS 00010000818 +622231380104852310888427673870000000000#778672258163# Emma Wilson 1121042880000819 +705Questersolstice bonus pay for amazing work on #OSS 00010000819 +622231380104240775857825657700000000000#551877481056# Ella Anderson 1121042880000820 +705Browevening bonus pay for amazing work on #OSS 00010000820 +622231380104671055303787204220000000000#137123688720# David Davis 1121042880000821 +705Neckglaze bonus pay for amazing work on #OSS 00010000821 +622231380104032048333626433140000000000#800577057120# Matthew Brown 1121042880000822 +705Lanternprickle bonus pay for amazing work on #OSS 00010000822 +622231380104375603501115670000000000000#081446737863# Sofia Thompson 1121042880000823 +705Catfantasy bonus pay for amazing work on #OSS 00010000823 +622231380104303112524838464270000000000#417384253303# Elizabeth Davis 1121042880000824 +705Napeflower bonus pay for amazing work on #OSS 00010000824 +622231380104114717364554688320000000000#406222745804# Charlotte Moore 1121042880000825 +705Elklava bonus pay for amazing work on #OSS 00010000825 +622231380104644453577424657270000000000#646361772632# Emily Thomas 1121042880000826 +705Hyenaglitter bonus pay for amazing work on #OSS 00010000826 +622231380104335055673536562620000000000#714702134348# Ella Smith 1121042880000827 +705Paintercalico bonus pay for amazing work on #OSS 00010000827 +622231380104387668175480316510000000000#663685835022# Mia Taylor 1121042880000828 +705Heronroad bonus pay for amazing work on #OSS 00010000828 +622231380104485510235868252640000000000#433271057138# Ava Brown 1121042880000829 +705Carpplum bonus pay for amazing work on #OSS 00010000829 +622231380104878108386665000110000000000#286837034703# Madison Miller 1121042880000830 +705Yakprairie bonus pay for amazing work on #OSS 00010000830 +622231380104300023476373768340000000000#352650443635# Jacob Robinson 1121042880000831 +705Stingerhail bonus pay for amazing work on #OSS 00010000831 +622231380104484606466542478370000000000#181415776566# Anthony Thompson 1121042880000832 +705Fighterjewel bonus pay for amazing work on #OSS 00010000832 +622231380104480756172074837280000000000#846272076854# Mia Jackson 1121042880000833 +705Sagefate bonus pay for amazing work on #OSS 00010000833 +622231380104230148061860487870000000000#715534681503# Emily Martin 1121042880000834 +705Hissgentle bonus pay for amazing work on #OSS 00010000834 +622231380104723223321204674370000000000#364742832555# Addison Williams 1121042880000835 +705Chargerruby bonus pay for amazing work on #OSS 00010000835 +622231380104388116614657633400000000000#251118631830# Zoey Wilson 1121042880000836 +705Neckshard bonus pay for amazing work on #OSS 00010000836 +622231380104327442128278263140000000000#447800471140# Mason Johnson 1121042880000837 +705Gargoylepeppermint bonus pay for amazing work on #OSS 00010000837 +622231380104432360328113836050000000000#348086880577# Ava Brown 1121042880000838 +705Legsrapid bonus pay for amazing work on #OSS 00010000838 +622231380104181422657487787460000000000#642766760880# Sophia Jones 1121042880000839 +705Carpetfern bonus pay for amazing work on #OSS 00010000839 +622231380104420133784821816750000000000#582348085020# Mia Davis 1121042880000840 +705Mistresspie bonus pay for amazing work on #OSS 00010000840 +622231380104327455812036052660000000000#606628138800# Michael Taylor 1121042880000841 +705Gorillavalley bonus pay for amazing work on #OSS 00010000841 +622231380104611435140374260800000000000#506731635780# Olivia Jones 1121042880000842 +705Fanglapis bonus pay for amazing work on #OSS 00010000842 +622231380104544678270000388100000000000#078318403603# Emily Thompson 1121042880000843 +705Stalkerbranch bonus pay for amazing work on #OSS 00010000843 +622231380104066222053272628020000000000#377663286057# William Thomas 1121042880000844 +705Boarolive bonus pay for amazing work on #OSS 00010000844 +622231380104833061374348086400000000000#615115373432# Aiden White 1121042880000845 +705Otteralder bonus pay for amazing work on #OSS 00010000845 +622231380104181104248064052020000000000#780127411565# Olivia Johnson 1121042880000846 +705Wolverinecat bonus pay for amazing work on #OSS 00010000846 +622231380104480146773177875140000000000#085586267526# Ava Davis 1121042880000847 +705Snapcold bonus pay for amazing work on #OSS 00010000847 +622231380104882801226158635680000000000#307710785853# Alexander Wilson 1121042880000848 +705Lancerplanet bonus pay for amazing work on #OSS 00010000848 +622231380104485165226308015130000000000#761817052323# Isabella Jackson 1121042880000849 +705Voiceglacier bonus pay for amazing work on #OSS 00010000849 +622231380104602415332381650850000000000#455485256321# Jacob Taylor 1121042880000850 +705Fishercute bonus pay for amazing work on #OSS 00010000850 +622231380104076074854438345330000000000#038470776448# Ethan Martinez 1121042880000851 +705Lancercharm bonus pay for amazing work on #OSS 00010000851 +622231380104462106506312102170000000000#302768477870# Sophia Wilson 1121042880000852 +705Eagleshell bonus pay for amazing work on #OSS 00010000852 +622231380104058074843630624460000000000#764024438046# Emily Jackson 1121042880000853 +705Horsecopper bonus pay for amazing work on #OSS 00010000853 +622231380104323711704214401420000000000#820352625001# Zoey Taylor 1121042880000854 +705Jestermeteor bonus pay for amazing work on #OSS 00010000854 +622231380104272887060265610300000000000#525158723676# Jayden Anderson 1121042880000855 +705Ratmaze bonus pay for amazing work on #OSS 00010000855 +622231380104841504787528824680000000000#628622363587# David Jackson 1121042880000856 +705Gemgiant bonus pay for amazing work on #OSS 00010000856 +622231380104288112746765322510000000000#024635344044# Olivia Martinez 1121042880000857 +705Spritedaisy bonus pay for amazing work on #OSS 00010000857 +622231380104347575050604344530000000000#752626372812# Olivia Martin 1121042880000858 +705Toothwind bonus pay for amazing work on #OSS 00010000858 +622231380104842322318036328460000000000#702424053665# Joseph Wilson 1121042880000859 +705Serpentblue bonus pay for amazing work on #OSS 00010000859 +622231380104177385850085816370000000000#438128775371# Alexander Jackson 1121042880000860 +705Boarcarnation bonus pay for amazing work on #OSS 00010000860 +622231380104570114058305558520000000000#635512861777# Anthony Robinson 1121042880000861 +705Turnerflower bonus pay for amazing work on #OSS 00010000861 +622231380104358380370566045630000000000#456324888548# Lily Smith 1121042880000862 +705Zebrapond bonus pay for amazing work on #OSS 00010000862 +622231380104838165136828771660000000000#244082460677# David Taylor 1121042880000863 +705Slayerglen bonus pay for amazing work on #OSS 00010000863 +622231380104166035758367134020000000000#528688286446# Daniel Jackson 1121042880000864 +705Princesaber bonus pay for amazing work on #OSS 00010000864 +622231380104728554854731218700000000000#354224807446# Olivia White 1121042880000865 +705Kickercarnation bonus pay for amazing work on #OSS 00010000865 +622231380104840471776873611450000000000#316435354874# Isabella Williams 1121042880000866 +705Heronindigo bonus pay for amazing work on #OSS 00010000866 +622231380104872144643118134300000000000#188572682833# David Thompson 1121042880000867 +705Herondaisy bonus pay for amazing work on #OSS 00010000867 +622231380104662822856455614030000000000#686703225408# Natalie Robinson 1121042880000868 +705Razorgravel bonus pay for amazing work on #OSS 00010000868 +622231380104156815174770015670000000000#320472774628# Alexander Smith 1121042880000869 +705Fangholy bonus pay for amazing work on #OSS 00010000869 +622231380104022716285167865540000000000#823646755588# Matthew Jackson 1121042880000870 +705Dancermangrove bonus pay for amazing work on #OSS 00010000870 +622231380104735828442531230600000000000#743665584741# Elizabeth Garcia 1121042880000871 +705Banehurricane bonus pay for amazing work on #OSS 00010000871 +622231380104635767711618125430000000000#877004236152# Emily Moore 1121042880000872 +705Ferretprism bonus pay for amazing work on #OSS 00010000872 +622231380104064205136062340730000000000#430146475035# Zoey Martin 1121042880000873 +705Sparrowchecker bonus pay for amazing work on #OSS 00010000873 +622231380104722658343883240170000000000#444650725428# Aiden Moore 1121042880000874 +705Giverpinto bonus pay for amazing work on #OSS 00010000874 +622231380104886873133308852370000000000#267511828388# Elizabeth Harris 1121042880000875 +705Craneboulder bonus pay for amazing work on #OSS 00010000875 +622231380104065413180614023120000000000#875306105055# Lily Brown 1121042880000876 +705Viperlavender bonus pay for amazing work on #OSS 00010000876 +622231380104067685603685815560000000000#083777088266# Lily Thompson 1121042880000877 +705Palmgrave bonus pay for amazing work on #OSS 00010000877 +622231380104835481547026534720000000000#526253115662# Abigail White 1121042880000878 +705Bunnyebony bonus pay for amazing work on #OSS 00010000878 +622231380104523160476620304770000000000#535420815100# Anthony Robinson 1121042880000879 +705Crusherlavender bonus pay for amazing work on #OSS 00010000879 +622231380104151155063744062410000000000#778100187881# Daniel Williams 1121042880000880 +705Yakquilt bonus pay for amazing work on #OSS 00010000880 +622231380104382543845405424700000000000#823036141662# Elizabeth Miller 1121042880000881 +705Winglegend bonus pay for amazing work on #OSS 00010000881 +622231380104344076545444518700000000000#137553152020# Elijah Jackson 1121042880000882 +705Pawshy bonus pay for amazing work on #OSS 00010000882 +622231380104267417385885767320000000000#188407354033# Andrew Miller 1121042880000883 +705Slothcrystal bonus pay for amazing work on #OSS 00010000883 +622231380104671182110771611680000000000#733838331610# Isabella Anderson 1121042880000884 +705Foetitanium bonus pay for amazing work on #OSS 00010000884 +622231380104742548468551230040000000000#675356428213# Anthony Jackson 1121042880000885 +705Wandererpond bonus pay for amazing work on #OSS 00010000885 +622231380104231565741063387060000000000#208845500213# Joseph Garcia 1121042880000886 +705Spriteroot bonus pay for amazing work on #OSS 00010000886 +622231380104406368527206236480000000000#205086681273# Noah Brown 1121042880000887 +705Voleflame bonus pay for amazing work on #OSS 00010000887 +622231380104250781108071310140000000000#483011533767# Anthony Moore 1121042880000888 +705Knightbristle bonus pay for amazing work on #OSS 00010000888 +622231380104461642103757558180000000000#042480714511# William Brown 1121042880000889 +705Fingerequinox bonus pay for amazing work on #OSS 00010000889 +622231380104187017258826387810000000000#323567461478# Natalie Taylor 1121042880000890 +705Sagegossamer bonus pay for amazing work on #OSS 00010000890 +622231380104364076641858485840000000000#772264778403# Daniel Smith 1121042880000891 +705Volemorning bonus pay for amazing work on #OSS 00010000891 +622231380104782646378432826160000000000#516423321667# Michael Moore 1121042880000892 +705Cubagate bonus pay for amazing work on #OSS 00010000892 +622231380104340673277646321140000000000#013063050580# Sophia Brown 1121042880000893 +705Vipershore bonus pay for amazing work on #OSS 00010000893 +622231380104438422628320862330000000000#571400015007# Elizabeth Taylor 1121042880000894 +705Frightchestnut bonus pay for amazing work on #OSS 00010000894 +622231380104555841861762176800000000000#626265147435# Avery Martin 1121042880000895 +705Princessmorning bonus pay for amazing work on #OSS 00010000895 +622231380104804031668568118650000000000#786712730415# Daniel Jones 1121042880000896 +705Giverthread bonus pay for amazing work on #OSS 00010000896 +622231380104461228888478421150000000000#066842630345# Elizabeth Jackson 1121042880000897 +705Saverribbon bonus pay for amazing work on #OSS 00010000897 +622231380104372531162516512080000000000#142721463726# James Thomas 1121042880000898 +705Walkertruth bonus pay for amazing work on #OSS 00010000898 +622231380104671187740015780770000000000#213676503565# Joseph Thomas 1121042880000899 +705Headpond bonus pay for amazing work on #OSS 00010000899 +622231380104680336326453321810000000000#231144873033# Anthony Taylor 1121042880000900 +705Wizardhazel bonus pay for amazing work on #OSS 00010000900 +622231380104360034351573037720000000000#138186287141# Zoey Smith 1121042880000901 +705Devourertulip bonus pay for amazing work on #OSS 00010000901 +622231380104176037360224262770000000000#171120133843# Mason Smith 1121042880000902 +705Gemtime bonus pay for amazing work on #OSS 00010000902 +622231380104574770081423430750000000000#370238020275# Jacob Anderson 1121042880000903 +705Glazervoid bonus pay for amazing work on #OSS 00010000903 +622231380104625742472120285880000000000#478285237824# Addison Thomas 1121042880000904 +705Kingjewel bonus pay for amazing work on #OSS 00010000904 +622231380104241508118470718600000000000#870768415635# Aiden Miller 1121042880000905 +705Killeraquamarine bonus pay for amazing work on #OSS 00010000905 +622231380104204234257181522720000000000#562127328043# Andrew Miller 1121042880000906 +705Robinsnow bonus pay for amazing work on #OSS 00010000906 +622231380104615424850048478220000000000#458318805886# Jayden Martinez 1121042880000907 +705Spikegreen bonus pay for amazing work on #OSS 00010000907 +622231380104663435272608142610000000000#857302014420# Ava Johnson 1121042880000908 +705Scowlpie bonus pay for amazing work on #OSS 00010000908 +622231380104242117715505775760000000000#110520262472# Benjamin Miller 1121042880000909 +705Falconpuddle bonus pay for amazing work on #OSS 00010000909 +622231380104183388427237180780000000000#051860850651# Ella Davis 1121042880000910 +705Elfmetal bonus pay for amazing work on #OSS 00010000910 +622231380104035336335134810030000000000#138132485666# Elijah Smith 1121042880000911 +705Thornblack bonus pay for amazing work on #OSS 00010000911 +622231380104870788365410664170000000000#121512708770# Olivia Martin 1121042880000912 +705Rathorn bonus pay for amazing work on #OSS 00010000912 +622231380104774210187842864630000000000#116174133266# Sophia Robinson 1121042880000913 +705Fishseed bonus pay for amazing work on #OSS 00010000913 +622231380104402235207435504000000000000#126164231644# William Williams 1121042880000914 +705Vulturecrazy bonus pay for amazing work on #OSS 00010000914 +622231380104125845814237484540000000000#752444407640# Madison Brown 1121042880000915 +705Weedhazel bonus pay for amazing work on #OSS 00010000915 +622231380104366030255585265680000000000#178425611727# Mason Wilson 1121042880000916 +705Shoulderneon bonus pay for amazing work on #OSS 00010000916 +622231380104482815531232671270000000000#301287671421# Matthew Anderson 1121042880000917 +705Curtainmetal bonus pay for amazing work on #OSS 00010000917 +622231380104238526177067412430000000000#762158345535# Emma White 1121042880000918 +705Racerrampant bonus pay for amazing work on #OSS 00010000918 +622231380104657473737288581040000000000#618067766462# Jacob Wilson 1121042880000919 +705Snoutbush bonus pay for amazing work on #OSS 00010000919 +622231380104268612233144600300000000000#532206544823# Jacob Garcia 1121042880000920 +705Jaguardenim bonus pay for amazing work on #OSS 00010000920 +622231380104065566421214644450000000000#323845363307# Aubrey Miller 1121042880000921 +705Otterveil bonus pay for amazing work on #OSS 00010000921 +622231380104605806073587358400000000000#811701436567# David Garcia 1121042880000922 +705Reaperprism bonus pay for amazing work on #OSS 00010000922 +622231380104075743836226558580000000000#573217737647# Isabella Davis 1121042880000923 +705Marevanilla bonus pay for amazing work on #OSS 00010000923 +622231380104060211436852330480000000000#808734730088# Ella Davis 1121042880000924 +705Headcarnation bonus pay for amazing work on #OSS 00010000924 +622231380104816060525267761120000000000#374412861726# Emily Jackson 1121042880000925 +705Riderthunder bonus pay for amazing work on #OSS 00010000925 +622231380104026712353673743470000000000#680775500170# Isabella Moore 1121042880000926 +705Bowwheat bonus pay for amazing work on #OSS 00010000926 +622231380104868113880785813530000000000#460806521470# Madison Martin 1121042880000927 +705Walkerjewel bonus pay for amazing work on #OSS 00010000927 +622231380104842286444112807510000000000#674138861017# Daniel Jones 1121042880000928 +705Glazerhoneysuckle bonus pay for amazing work on #OSS 00010000928 +622231380104276744500501383320000000000#318627836665# Charlotte Miller 1121042880000929 +705Batwhite bonus pay for amazing work on #OSS 00010000929 +622231380104283013886354876100000000000#161765104446# Emma Smith 1121042880000930 +705Guardianflower bonus pay for amazing work on #OSS 00010000930 +622231380104818605088510876460000000000#103170612658# Aubrey Thompson 1121042880000931 +705Palmmica bonus pay for amazing work on #OSS 00010000931 +622231380104768624318235566670000000000#307547304054# Lily Davis 1121042880000932 +705Mustangalmond bonus pay for amazing work on #OSS 00010000932 +622231380104156283632007517660000000000#538276243138# Emma Brown 1121042880000933 +705Reaperdirt bonus pay for amazing work on #OSS 00010000933 +622231380104824821225116430050000000000#547160860176# Joseph Williams 1121042880000934 +705Fangfeather bonus pay for amazing work on #OSS 00010000934 +622231380104235445688021477020000000000#137515143775# James Martinez 1121042880000935 +705Herondisco bonus pay for amazing work on #OSS 00010000935 +622231380104687525037316505100000000000#543872026351# Andrew White 1121042880000936 +705Drifterdust bonus pay for amazing work on #OSS 00010000936 +622231380104521263271783552060000000000#682600001584# Sophia Wilson 1121042880000937 +705Vipermad bonus pay for amazing work on #OSS 00010000937 +622231380104521888162631537000000000000#534207730502# Michael Brown 1121042880000938 +705Geckobow bonus pay for amazing work on #OSS 00010000938 +622231380104242018524035472050000000000#728780887228# Matthew Anderson 1121042880000939 +705Hidesplash bonus pay for amazing work on #OSS 00010000939 +622231380104842250875784884740000000000#224478216143# Natalie Williams 1121042880000940 +705Thumbfish bonus pay for amazing work on #OSS 00010000940 +622231380104516120485745244450000000000#721382164052# Liam Taylor 1121042880000941 +705Paintervine bonus pay for amazing work on #OSS 00010000941 +622231380104082327438807078400000000000#351330402311# James Moore 1121042880000942 +705Walkerancient bonus pay for amazing work on #OSS 00010000942 +622231380104160810608086866160000000000#523148150743# Ava Harris 1121042880000943 +705Spikerelic bonus pay for amazing work on #OSS 00010000943 +622231380104385412334751766700000000000#843668530757# Emma Williams 1121042880000944 +705Lynxcurse bonus pay for amazing work on #OSS 00010000944 +622231380104505202456102451770000000000#276773440582# Mason Harris 1121042880000945 +705Crownsummer bonus pay for amazing work on #OSS 00010000945 +622231380104237051180550730810000000000#183841801658# William Harris 1121042880000946 +705Catcheraquamarine bonus pay for amazing work on #OSS 00010000946 +622231380104252665070586865680000000000#010834418636# William Jackson 1121042880000947 +705Mythplume bonus pay for amazing work on #OSS 00010000947 +622231380104328054210102318680000000000#230267661121# Abigail Robinson 1121042880000948 +705Jesterstone bonus pay for amazing work on #OSS 00010000948 +622231380104762532817684160310000000000#733560654134# Sofia Smith 1121042880000949 +705Pythonrattle bonus pay for amazing work on #OSS 00010000949 +622231380104744677104070283740000000000#113511814581# Abigail Thompson 1121042880000950 +705Kangaroogeode bonus pay for amazing work on #OSS 00010000950 +622231380104005021005300005040000000000#650028070621# Ava Martin 1121042880000951 +705Llamadour bonus pay for amazing work on #OSS 00010000951 +622231380104357635530170327430000000000#646382865620# Zoey Jones 1121042880000952 +705Thiefpuzzle bonus pay for amazing work on #OSS 00010000952 +622231380104377873487277085430000000000#370226838236# Elijah Brown 1121042880000953 +705Hornetcrystal bonus pay for amazing work on #OSS 00010000953 +622231380104138411664660360070000000000#534706015488# Natalie Williams 1121042880000954 +705Deathvoid bonus pay for amazing work on #OSS 00010000954 +622231380104480721455873121080000000000#143311473275# Elizabeth White 1121042880000955 +705Thumbwild bonus pay for amazing work on #OSS 00010000955 +622231380104437833758213158360000000000#785221137702# Elizabeth Thomas 1121042880000956 +705Lanterniridescent bonus pay for amazing work on #OSS 00010000956 +622231380104315652753648075120000000000#346506457406# Aubrey Anderson 1121042880000957 +705Heronquilt bonus pay for amazing work on #OSS 00010000957 +622231380104775603535584851370000000000#065714158102# Anthony Harris 1121042880000958 +705Waspthunder bonus pay for amazing work on #OSS 00010000958 +622231380104680763673042836780000000000#537367358852# Noah Williams 1121042880000959 +705Crushermesquite bonus pay for amazing work on #OSS 00010000959 +622231380104215874142225647780000000000#502728055156# Aubrey Moore 1121042880000960 +705Paladinorange bonus pay for amazing work on #OSS 00010000960 +622231380104764724813511586060000000000#157260346117# Zoey Brown 1121042880000961 +705Gemfrost bonus pay for amazing work on #OSS 00010000961 +622231380104016100488337347820000000000#022152558046# Natalie Johnson 1121042880000962 +705Slothspark bonus pay for amazing work on #OSS 00010000962 +622231380104076751268431260330000000000#540212275164# Natalie Johnson 1121042880000963 +705Flydeep bonus pay for amazing work on #OSS 00010000963 +622231380104656220846068368610000000000#588262381528# William Martin 1121042880000964 +705Flystellar bonus pay for amazing work on #OSS 00010000964 +622231380104335842053616268560000000000#523128341421# Chloe Smith 1121042880000965 +705Weedvanilla bonus pay for amazing work on #OSS 00010000965 +622231380104343010023511215250000000000#761051326371# Zoey Martinez 1121042880000966 +705Runnerink bonus pay for amazing work on #OSS 00010000966 +622231380104516027450010447760000000000#028001652130# Daniel Martinez 1121042880000967 +705Hisserbead bonus pay for amazing work on #OSS 00010000967 +622231380104074631650666673280000000000#456282155273# James Moore 1121042880000968 +705Slaveshy bonus pay for amazing work on #OSS 00010000968 +622231380104314268025641530260000000000#438555784632# Alexander Martin 1121042880000969 +705Gamblerclover bonus pay for amazing work on #OSS 00010000969 +622231380104046126802838310070000000000#460215410552# Lily Martin 1121042880000970 +705Whalecandle bonus pay for amazing work on #OSS 00010000970 +622231380104203773061737284530000000000#372756143272# Andrew Martinez 1121042880000971 +705Daggersequoia bonus pay for amazing work on #OSS 00010000971 +622231380104642642670715015670000000000#414825025510# Andrew Martin 1121042880000972 +705Lasherseason bonus pay for amazing work on #OSS 00010000972 +622231380104825786478127733470000000000#424767218226# Lily Thompson 1121042880000973 +705Oxtreasure bonus pay for amazing work on #OSS 00010000973 +622231380104225635173687585810000000000#704803184777# Natalie White 1121042880000974 +705Houndvalley bonus pay for amazing work on #OSS 00010000974 +622231380104808661678126560260000000000#263335633446# Avery Harris 1121042880000975 +705Haredog bonus pay for amazing work on #OSS 00010000975 +622231380104658430138108867760000000000#766473455701# Alexander Taylor 1121042880000976 +705Takertopaz bonus pay for amazing work on #OSS 00010000976 +622231380104673573385614088510000000000#467864562147# Mason Martinez 1121042880000977 +705Slavetree bonus pay for amazing work on #OSS 00010000977 +622231380104378776213436836120000000000#860248486280# Alexander Jackson 1121042880000978 +705Wolfflash bonus pay for amazing work on #OSS 00010000978 +622231380104350403240155664220000000000#471312413135# Avery Anderson 1121042880000979 +705Singernarrow bonus pay for amazing work on #OSS 00010000979 +622231380104511854678416524130000000000#454362103735# Joshua Smith 1121042880000980 +705Slayeremerald bonus pay for amazing work on #OSS 00010000980 +622231380104571681658128466270000000000#485414514308# Anthony Anderson 1121042880000981 +705Griffincream bonus pay for amazing work on #OSS 00010000981 +622231380104185037304146307080000000000#700867724600# Matthew Wilson 1121042880000982 +705Dartmetal bonus pay for amazing work on #OSS 00010000982 +622231380104345024137636400430000000000#524120374474# Zoey Brown 1121042880000983 +705Mothvalley bonus pay for amazing work on #OSS 00010000983 +622231380104507511021223756700000000000#078845884825# Aubrey Martinez 1121042880000984 +705Warlockmotley bonus pay for amazing work on #OSS 00010000984 +622231380104784121713215750700000000000#073160252472# Sophia Smith 1121042880000985 +705Salmoncypress bonus pay for amazing work on #OSS 00010000985 +622231380104027356851751674700000000000#151286426614# Jayden White 1121042880000986 +705Scourgebrindle bonus pay for amazing work on #OSS 00010000986 +622231380104724725077128880000000000000#550370884447# Madison Thompson 1121042880000987 +705Musechatter bonus pay for amazing work on #OSS 00010000987 +622231380104510405575880376340000000000#438113822816# William Davis 1121042880000988 +705Swoopdog bonus pay for amazing work on #OSS 00010000988 +622231380104424424238807672560000000000#553613782207# Andrew Davis 1121042880000989 +705Ravercarnelian bonus pay for amazing work on #OSS 00010000989 +622231380104443736064424408830000000000#378042146688# Zoey Jackson 1121042880000990 +705Weedbronze bonus pay for amazing work on #OSS 00010000990 +622231380104758508680803714110000000000#373476186147# Avery Moore 1121042880000991 +705Huggermaze bonus pay for amazing work on #OSS 00010000991 +622231380104854406035018165860000000000#484331668624# Sophia Smith 1121042880000992 +705Jestertiny bonus pay for amazing work on #OSS 00010000992 +622231380104621850175864257450000000000#506837403008# Sofia Brown 1121042880000993 +705Dukechrome bonus pay for amazing work on #OSS 00010000993 +622231380104220737415080454770000000000#602554450675# Avery Taylor 1121042880000994 +705Wandererjust bonus pay for amazing work on #OSS 00010000994 +622231380104535143330630330740000000000#054572541252# Olivia Harris 1121042880000995 +705Crowngrove bonus pay for amazing work on #OSS 00010000995 +622231380104577258321314038200000000000#575344878677# Ella White 1121042880000996 +705Eaterdew bonus pay for amazing work on #OSS 00010000996 +622231380104741747188583810410000000000#648752076605# Jacob Wilson 1121042880000997 +705Kingorchid bonus pay for amazing work on #OSS 00010000997 +622231380104778120482521231730000000000#737226868014# Elijah Smith 1121042880000998 +705Tigerdeep bonus pay for amazing work on #OSS 00010000998 +622231380104635710245126103320000000000#334431216346# Benjamin Johnson 1121042880000999 +705Carpetroot bonus pay for amazing work on #OSS 00010000999 +622231380104367532678287400750000000000#055847276328# Emily Garcia 1121042880001000 +705Gemcrystal bonus pay for amazing work on #OSS 00010001000 +622231380104447848844473401860000000000#867775413767# Zoey Moore 1121042880001001 +705Chillerbrass bonus pay for amazing work on #OSS 00010001001 +622231380104035576236113260150000000000#267481650211# Emily Smith 1121042880001002 +705Samuraibrown bonus pay for amazing work on #OSS 00010001002 +622231380104426478152016370700000000000#212043411065# David Miller 1121042880001003 +705Toothdandy bonus pay for amazing work on #OSS 00010001003 +622231380104713146324432166820000000000#206606776540# Natalie Thompson 1121042880001004 +705Raygray bonus pay for amazing work on #OSS 00010001004 +622231380104151707081300524140000000000#151466346534# James Thompson 1121042880001005 +705Toucansequoia bonus pay for amazing work on #OSS 00010001005 +622231380104145203648850205350000000000#624230325722# Mia Davis 1121042880001006 +705Roarercoffee bonus pay for amazing work on #OSS 00010001006 +622231380104126844651217332410000000000#861863442521# Benjamin Harris 1121042880001007 +705Jestercrystal bonus pay for amazing work on #OSS 00010001007 +622231380104526532154803247280000000000#188283148470# Elijah Wilson 1121042880001008 +705Jawiris bonus pay for amazing work on #OSS 00010001008 +622231380104552761202262846850000000000#410633855743# Matthew Martin 1121042880001009 +705Lionforest bonus pay for amazing work on #OSS 00010001009 +622231380104874755552572664600000000000#024008423182# Avery Davis 1121042880001010 +705Antberyl bonus pay for amazing work on #OSS 00010001010 +622231380104624205254772766740000000000#356222801065# Ethan Smith 1121042880001011 +705Swisherfoam bonus pay for amazing work on #OSS 00010001011 +622231380104157012554445705040000000000#760283106573# Aiden Garcia 1121042880001012 +705Tradergrove bonus pay for amazing work on #OSS 00010001012 +622231380104857765527877177200000000000#820704144421# Joshua Williams 1121042880001013 +705Shieldshimmer bonus pay for amazing work on #OSS 00010001013 +622231380104054761244574372480000000000#357186257760# Emily Johnson 1121042880001014 +705Servantring bonus pay for amazing work on #OSS 00010001014 +622231380104265807711177574540000000000#063132145775# Daniel Johnson 1121042880001015 +705Oxkeen bonus pay for amazing work on #OSS 00010001015 +622231380104380334007141456180000000000#620447678887# Sofia White 1121042880001016 +705Horsecandle bonus pay for amazing work on #OSS 00010001016 +622231380104005266862136140860000000000#366168231352# Abigail Harris 1121042880001017 +705Arrowfuschia bonus pay for amazing work on #OSS 00010001017 +622231380104561471153464782340000000000#703230303652# Sophia Harris 1121042880001018 +705Wizardchecker bonus pay for amazing work on #OSS 00010001018 +622231380104555114650528625610000000000#553146408775# Isabella Miller 1121042880001019 +705Thundercord bonus pay for amazing work on #OSS 00010001019 +622231380104037413404106880370000000000#037542778463# Mason Jackson 1121042880001020 +705Hornetfortune bonus pay for amazing work on #OSS 00010001020 +622231380104740568680773031280000000000#024354413605# Abigail Jones 1121042880001021 +705Storkapple bonus pay for amazing work on #OSS 00010001021 +622231380104376411587111320480000000000#103732404630# Anthony Williams 1121042880001022 +705Biterspring bonus pay for amazing work on #OSS 00010001022 +622231380104084217515657705740000000000#033800276320# Andrew Martinez 1121042880001023 +705Lordlapis bonus pay for amazing work on #OSS 00010001023 +622231380104438464475276412450000000000#316721860807# Ethan Johnson 1121042880001024 +705Howlerebony bonus pay for amazing work on #OSS 00010001024 +622231380104068281860334304140000000000#106768334108# Sofia Williams 1121042880001025 +705Birdharvest bonus pay for amazing work on #OSS 00010001025 +622231380104578047452826064280000000000#045528747241# Michael Jones 1121042880001026 +705Mustangcopper bonus pay for amazing work on #OSS 00010001026 +622231380104062401120863321350000000000#327452085402# Elijah Harris 1121042880001027 +705Gullfog bonus pay for amazing work on #OSS 00010001027 +622231380104814836378440526240000000000#473571264867# Liam Garcia 1121042880001028 +705Howlerboom bonus pay for amazing work on #OSS 00010001028 +622231380104327251581140762550000000000#164762100778# Elizabeth Martinez 1121042880001029 +705Molewinter bonus pay for amazing work on #OSS 00010001029 +622231380104048436335271050010000000000#457426117532# Addison Miller 1121042880001030 +705Edgeballistic bonus pay for amazing work on #OSS 00010001030 +622231380104050385828637774780000000000#188438685728# Noah Thomas 1121042880001031 +705Sagespectrum bonus pay for amazing work on #OSS 00010001031 +622231380104038326576246030050000000000#886374318513# Jayden Brown 1121042880001032 +705Walkerroot bonus pay for amazing work on #OSS 00010001032 +622231380104347824435363870080000000000#347172321066# Chloe Brown 1121042880001033 +705Paladinsapphire bonus pay for amazing work on #OSS 00010001033 +622231380104078312417547242840000000000#655826336212# Alexander Thomas 1121042880001034 +705Lightningzest bonus pay for amazing work on #OSS 00010001034 +622231380104540080380767171660000000000#286440775751# Benjamin Jones 1121042880001035 +705Wolfsour bonus pay for amazing work on #OSS 00010001035 +622231380104517076488718042650000000000#731402543436# Joshua Robinson 1121042880001036 +705Pigmercury bonus pay for amazing work on #OSS 00010001036 +622231380104077364513485033710000000000#541181623441# Olivia Anderson 1121042880001037 +705Zebrarag bonus pay for amazing work on #OSS 00010001037 +622231380104050070128650013360000000000#122337530553# Daniel Brown 1121042880001038 +705Gulleast bonus pay for amazing work on #OSS 00010001038 +622231380104033120528662543460000000000#543546586464# Liam Robinson 1121042880001039 +705Bellybristle bonus pay for amazing work on #OSS 00010001039 +622231380104727700148001025720000000000#386066180176# Alexander Davis 1121042880001040 +705Stormsolstice bonus pay for amazing work on #OSS 00010001040 +622231380104343570465831254260000000000#760002688452# Mia Davis 1121042880001041 +705Gemdirt bonus pay for amazing work on #OSS 00010001041 +622231380104823553084116235750000000000#444383865168# Elizabeth Thompson 1121042880001042 +705Flasherwhite bonus pay for amazing work on #OSS 00010001042 +622231380104301333733161021450000000000#855388175161# James Martin 1121042880001043 +705Thumbivory bonus pay for amazing work on #OSS 00010001043 +622231380104864256214383628630000000000#173165155874# Abigail Garcia 1121042880001044 +705Leopardmotley bonus pay for amazing work on #OSS 00010001044 +622231380104524784315743245520000000000#635068827835# Olivia Martinez 1121042880001045 +705Ninjaspectrum bonus pay for amazing work on #OSS 00010001045 +622231380104311510468576445130000000000#438274872554# Emily Martin 1121042880001046 +705Llamahoneysuckle bonus pay for amazing work on #OSS 00010001046 +622231380104817223357453868820000000000#687552731867# Ethan Martin 1121042880001047 +705Salmonmalachite bonus pay for amazing work on #OSS 00010001047 +622231380104202208185266070130000000000#313486531350# Aiden Thomas 1121042880001048 +705Weedglaze bonus pay for amazing work on #OSS 00010001048 +622231380104717363767806800260000000000#667382337308# Sofia Jackson 1121042880001049 +705Speakerpepper bonus pay for amazing work on #OSS 00010001049 +622231380104012653420228872710000000000#325435732577# Joshua Johnson 1121042880001050 +705Daggerrose bonus pay for amazing work on #OSS 00010001050 +622231380104574226518814561670000000000#274271204287# William Smith 1121042880001051 +705Scowlplume bonus pay for amazing work on #OSS 00010001051 +622231380104444553316364028600000000000#336537156037# Ava Smith 1121042880001052 +705Fliercliff bonus pay for amazing work on #OSS 00010001052 +622231380104376683656772117770000000000#147435488632# Andrew Miller 1121042880001053 +705Dolphinquartz bonus pay for amazing work on #OSS 00010001053 +622231380104307210146216104280000000000#235475478570# Jayden Garcia 1121042880001054 +705Chargerplanet bonus pay for amazing work on #OSS 00010001054 +622231380104621507848870422630000000000#766137184403# Aubrey Moore 1121042880001055 +705Swallowtyphoon bonus pay for amazing work on #OSS 00010001055 +622231380104806346783785123730000000000#740252845765# Joshua Wilson 1121042880001056 +705Ribdenim bonus pay for amazing work on #OSS 00010001056 +622231380104057804550100260350000000000#546640687611# Isabella Jackson 1121042880001057 +705Knavecold bonus pay for amazing work on #OSS 00010001057 +622231380104835745146758531350000000000#700008248160# Ethan Davis 1121042880001058 +705Ferretforest bonus pay for amazing work on #OSS 00010001058 +622231380104850238632072222030000000000#831137155738# Isabella Williams 1121042880001059 +705Glazerforest bonus pay for amazing work on #OSS 00010001059 +622231380104872686014788262040000000000#563431741575# Charlotte Thomas 1121042880001060 +705Fanciermalachite bonus pay for amazing work on #OSS 00010001060 +622231380104270208487407106310000000000#405686087506# Olivia White 1121042880001061 +705Fingersolstice bonus pay for amazing work on #OSS 00010001061 +622231380104638050027118108120000000000#088550136116# Noah Robinson 1121042880001062 +705Dropmaze bonus pay for amazing work on #OSS 00010001062 +622231380104456705122335404500000000000#361711211856# Joseph Thomas 1121042880001063 +705Minnowmeadow bonus pay for amazing work on #OSS 00010001063 +622231380104255146478018277650000000000#840231326203# Elijah Williams 1121042880001064 +705Heroneon bonus pay for amazing work on #OSS 00010001064 +622231380104173201262468406710000000000#018676473371# Addison Thompson 1121042880001065 +705Hawkmoor bonus pay for amazing work on #OSS 00010001065 +622231380104441328170106236130000000000#516026046634# Daniel Anderson 1121042880001066 +705Toothsand bonus pay for amazing work on #OSS 00010001066 +622231380104151184260404621780000000000#301826816544# Olivia Johnson 1121042880001067 +705Lightningsnow bonus pay for amazing work on #OSS 00010001067 +622231380104411050403400337040000000000#032324783304# Sofia Martin 1121042880001068 +705Weaselroot bonus pay for amazing work on #OSS 00010001068 +622231380104725516422328726630000000000#816171113267# Addison Wilson 1121042880001069 +705Legendgem bonus pay for amazing work on #OSS 00010001069 +622231380104360702410421088820000000000#185624314752# Emily Davis 1121042880001070 +705Antlerhorn bonus pay for amazing work on #OSS 00010001070 +622231380104510205335471168850000000000#188412543502# Alexander Wilson 1121042880001071 +705Giverlemon bonus pay for amazing work on #OSS 00010001071 +622231380104446160545286716360000000000#348563888472# Zoey Anderson 1121042880001072 +705Lanternribbon bonus pay for amazing work on #OSS 00010001072 +622231380104303432202332677530000000000#706857380107# Abigail White 1121042880001073 +705Scalepepper bonus pay for amazing work on #OSS 00010001073 +622231380104677500767438073220000000000#370681523672# Aubrey Taylor 1121042880001074 +705Frillsaber bonus pay for amazing work on #OSS 00010001074 +622231380104478786784018124500000000000#744280062854# Matthew Johnson 1121042880001075 +705Razordisco bonus pay for amazing work on #OSS 00010001075 +622231380104022441103415385360000000000#060557884878# Charlotte Johnson 1121042880001076 +705Carversponge bonus pay for amazing work on #OSS 00010001076 +622231380104123318715013301040000000000#851053526301# Andrew Jackson 1121042880001077 +705Mythfreckle bonus pay for amazing work on #OSS 00010001077 +622231380104870177418211828460000000000#808513025321# Elizabeth Smith 1121042880001078 +705Armscarlet bonus pay for amazing work on #OSS 00010001078 +622231380104048401730634646770000000000#385215814224# William Jones 1121042880001079 +705Handmeadow bonus pay for amazing work on #OSS 00010001079 +622231380104658351782426646220000000000#833305568571# Emily Thomas 1121042880001080 +705Ringerzenith bonus pay for amazing work on #OSS 00010001080 +622231380104370357688251064240000000000#005626083351# Liam Taylor 1121042880001081 +705Chestflint bonus pay for amazing work on #OSS 00010001081 +622231380104454078186721548840000000000#343305806523# Lily Johnson 1121042880001082 +705Sentryjet bonus pay for amazing work on #OSS 00010001082 +622231380104410674076043256150000000000#836082222384# Matthew Johnson 1121042880001083 +705Shakerfir bonus pay for amazing work on #OSS 00010001083 +622231380104685618347032308800000000000#022621633643# Emily Harris 1121042880001084 +705Swallowshadow bonus pay for amazing work on #OSS 00010001084 +622231380104060651530711076020000000000#057815533624# Aiden Brown 1121042880001085 +705Carpetrhinestone bonus pay for amazing work on #OSS 00010001085 +622231380104120474351422568420000000000#676223844726# Benjamin Jackson 1121042880001086 +705Raptorheavy bonus pay for amazing work on #OSS 00010001086 +622231380104730063038424622110000000000#802762208557# Charlotte Garcia 1121042880001087 +705Thorncrocus bonus pay for amazing work on #OSS 00010001087 +622231380104532266318036224840000000000#858646826847# Zoey Williams 1121042880001088 +705Throatquilt bonus pay for amazing work on #OSS 00010001088 +622231380104372815381386001150000000000#737437888628# Mia Thompson 1121042880001089 +705Ocelotclever bonus pay for amazing work on #OSS 00010001089 +622231380104854604312862135650000000000#044541665108# Elijah Thomas 1121042880001090 +705Cubslash bonus pay for amazing work on #OSS 00010001090 +622231380104216251078032005470000000000#088450745803# Aiden Thomas 1121042880001091 +705Ratplaid bonus pay for amazing work on #OSS 00010001091 +622231380104476004124722532600000000000#682512545122# Joshua White 1121042880001092 +705Whipalpine bonus pay for amazing work on #OSS 00010001092 +622231380104235605333417320450000000000#101138287258# Ethan Jackson 1121042880001093 +705Cloaklily bonus pay for amazing work on #OSS 00010001093 +622231380104284815286625325540000000000#430267412815# Avery Jones 1121042880001094 +705Elkfrill bonus pay for amazing work on #OSS 00010001094 +622231380104062864652224835800000000000#705315053118# Jacob Wilson 1121042880001095 +705Kangaroospring bonus pay for amazing work on #OSS 00010001095 +622231380104818762768642504750000000000#228674188610# Ella Jones 1121042880001096 +705Playeragate bonus pay for amazing work on #OSS 00010001096 +622231380104028627547453651780000000000#211012022081# Noah Johnson 1121042880001097 +705Bunnyskitter bonus pay for amazing work on #OSS 00010001097 +622231380104436116178086014170000000000#658633084554# Sophia Jones 1121042880001098 +705Chattermica bonus pay for amazing work on #OSS 00010001098 +622231380104170771854818503600000000000#250711562545# Olivia Jackson 1121042880001099 +705Handlight bonus pay for amazing work on #OSS 00010001099 +622231380104088423074854285320000000000#686063614746# Natalie Miller 1121042880001100 +705Crowflint bonus pay for amazing work on #OSS 00010001100 +622231380104275441628872608060000000000#083305271124# Chloe Thomas 1121042880001101 +705Legsflower bonus pay for amazing work on #OSS 00010001101 +622231380104700186421430643460000000000#052718764247# Jacob Thompson 1121042880001102 +705Ogreband bonus pay for amazing work on #OSS 00010001102 +622231380104128511737263618600000000000#483877102333# David Miller 1121042880001103 +705Jesterdew bonus pay for amazing work on #OSS 00010001103 +622231380104862352863260730210000000000#287242026276# Avery White 1121042880001104 +705Tailpond bonus pay for amazing work on #OSS 00010001104 +622231380104321886643604080730000000000#164843550152# Isabella Martin 1121042880001105 +705Seerjelly bonus pay for amazing work on #OSS 00010001105 +622231380104628772765176340150000000000#606458228151# Matthew Smith 1121042880001106 +705Stingjewel bonus pay for amazing work on #OSS 00010001106 +622231380104234886510425804440000000000#160830624567# Ava Jones 1121042880001107 +705Burnamber bonus pay for amazing work on #OSS 00010001107 +622231380104317301248043471250000000000#740077445373# Isabella Martinez 1121042880001108 +705Raptorfast bonus pay for amazing work on #OSS 00010001108 +622231380104358285432318611060000000000#154564414511# Benjamin Jones 1121042880001109 +705Giverquartz bonus pay for amazing work on #OSS 00010001109 +622231380104714327736114042070000000000#114712162638# Mason Moore 1121042880001110 +705Hornetcitrine bonus pay for amazing work on #OSS 00010001110 +622231380104115206882731622760000000000#553607348647# James Garcia 1121042880001111 +705Spearapricot bonus pay for amazing work on #OSS 00010001111 +622231380104483874811103546550000000000#624386280850# Jayden Anderson 1121042880001112 +705Turneragate bonus pay for amazing work on #OSS 00010001112 +622231380104785411054840215340000000000#715660506781# Elizabeth Thomas 1121042880001113 +705Sparrowwhip bonus pay for amazing work on #OSS 00010001113 +622231380104028213636614811380000000000#340844174684# Anthony Thomas 1121042880001114 +705Wingmidnight bonus pay for amazing work on #OSS 00010001114 +622231380104231403085472156560000000000#551378602625# Mason Jones 1121042880001115 +705Goatzinc bonus pay for amazing work on #OSS 00010001115 +622231380104227233814157112360000000000#648026884886# Avery Williams 1121042880001116 +705Lordpewter bonus pay for amazing work on #OSS 00010001116 +622231380104365608813178468250000000000#413761142324# Lily White 1121042880001117 +705Bellbrook bonus pay for amazing work on #OSS 00010001117 +622231380104856534581458818580000000000#688171107805# Daniel Garcia 1121042880001118 +705Talongranite bonus pay for amazing work on #OSS 00010001118 +622231380104317843201717033560000000000#815073853154# Sofia White 1121042880001119 +705Browroad bonus pay for amazing work on #OSS 00010001119 +622231380104425444433216372420000000000#465221836343# Joshua Brown 1121042880001120 +705Gamblerzinc bonus pay for amazing work on #OSS 00010001120 +622231380104615677233678535130000000000#474833135250# Elijah White 1121042880001121 +705Paintersugar bonus pay for amazing work on #OSS 00010001121 +622231380104840332322875263140000000000#771146248327# Natalie Wilson 1121042880001122 +705Sightmaple bonus pay for amazing work on #OSS 00010001122 +622231380104741111271853336780000000000#182142383803# Joshua Moore 1121042880001123 +705Ribbrindle bonus pay for amazing work on #OSS 00010001123 +622231380104720726842444051110000000000#720325068180# Joseph Johnson 1121042880001124 +705Volebead bonus pay for amazing work on #OSS 00010001124 +622231380104481055878410488430000000000#217854181026# Noah Moore 1121042880001125 +705Barddot bonus pay for amazing work on #OSS 00010001125 +622231380104214021820005031250000000000#617873810805# Sofia Jackson 1121042880001126 +705Crownmeadow bonus pay for amazing work on #OSS 00010001126 +622231380104326126866503752650000000000#552615018078# Joshua Garcia 1121042880001127 +705Princeglimmer bonus pay for amazing work on #OSS 00010001127 +622231380104187786820807543670000000000#762634132278# Charlotte Davis 1121042880001128 +705Hairdew bonus pay for amazing work on #OSS 00010001128 +622231380104622001583228475450000000000#133200212437# Sophia Williams 1121042880001129 +705Heronsheer bonus pay for amazing work on #OSS 00010001129 +622231380104044237578427352170000000000#268802610331# Matthew Williams 1121042880001130 +705Collartree bonus pay for amazing work on #OSS 00010001130 +622231380104843160080322035500000000000#514548021488# Emily Robinson 1121042880001131 +705Moosebrown bonus pay for amazing work on #OSS 00010001131 +622231380104228810766500646360000000000#777621410301# Chloe Harris 1121042880001132 +705Ridertitanium bonus pay for amazing work on #OSS 00010001132 +622231380104801180312002307630000000000#628606053215# Aiden Jackson 1121042880001133 +705Turnerstorm bonus pay for amazing work on #OSS 00010001133 +622231380104031481788702006120000000000#384381251784# Liam Brown 1121042880001134 +705Watcherquiver bonus pay for amazing work on #OSS 00010001134 +622231380104124480426107781100000000000#177458638372# Abigail Jackson 1121042880001135 +705Scarbloom bonus pay for amazing work on #OSS 00010001135 +622231380104808846488330863660000000000#415132228721# Addison Moore 1121042880001136 +705Footfringe bonus pay for amazing work on #OSS 00010001136 +622231380104872233431650871520000000000#426821223766# Emily Robinson 1121042880001137 +705Molestone bonus pay for amazing work on #OSS 00010001137 +622231380104674157188381258260000000000#525133085168# Aubrey Davis 1121042880001138 +705Queenmidnight bonus pay for amazing work on #OSS 00010001138 +622231380104526064488547777500000000000#683130500546# Alexander Jackson 1121042880001139 +705Knaverapid bonus pay for amazing work on #OSS 00010001139 +622231380104401685282240111080000000000#348384317633# Ava Williams 1121042880001140 +705Bindershine bonus pay for amazing work on #OSS 00010001140 +622231380104303250648762308400000000000#611536755211# Benjamin Martinez 1121042880001141 +705Skinnerfreckle bonus pay for amazing work on #OSS 00010001141 +622231380104611223665382103550000000000#543085688200# Ava Jackson 1121042880001142 +705Bugrose bonus pay for amazing work on #OSS 00010001142 +622231380104365663288750762520000000000#278232221505# Michael Jackson 1121042880001143 +705Scourgewhip bonus pay for amazing work on #OSS 00010001143 +622231380104330630535580766030000000000#224331087504# David Johnson 1121042880001144 +705Racerthunder bonus pay for amazing work on #OSS 00010001144 +622231380104088105805738808730000000000#363787824323# Madison Moore 1121042880001145 +705Gazellekeen bonus pay for amazing work on #OSS 00010001145 +622231380104426187111858481770000000000#178867461211# Sophia Moore 1121042880001146 +705Ripperstream bonus pay for amazing work on #OSS 00010001146 +622231380104133134375408838180000000000#087086061422# Jacob Jones 1121042880001147 +705Spurdour bonus pay for amazing work on #OSS 00010001147 +622231380104100732415367608370000000000#813556370633# Mia Taylor 1121042880001148 +705Edgepepper bonus pay for amazing work on #OSS 00010001148 +622231380104452324243578035730000000000#163075752710# Sophia Moore 1121042880001149 +705Shieldbuttercup bonus pay for amazing work on #OSS 00010001149 +622231380104571883764240461620000000000#450131377538# Mason Harris 1121042880001150 +705Snarlshadow bonus pay for amazing work on #OSS 00010001150 +622231380104502175725334550320000000000#312373651711# Isabella Moore 1121042880001151 +705Keeperquartz bonus pay for amazing work on #OSS 00010001151 +622231380104856245770334465810000000000#555074163166# Matthew Williams 1121042880001152 +705Centaurflax bonus pay for amazing work on #OSS 00010001152 +622231380104373267821643177640000000000#382631128556# James Smith 1121042880001153 +705Centaursheer bonus pay for amazing work on #OSS 00010001153 +622231380104201242405830451610000000000#264628553543# Matthew Martin 1121042880001154 +705Catbuttercup bonus pay for amazing work on #OSS 00010001154 +622231380104674065213515373450000000000#175842111204# Ethan Taylor 1121042880001155 +705Falconfair bonus pay for amazing work on #OSS 00010001155 +622231380104626248457103286020000000000#453105864362# Addison Thompson 1121042880001156 +705Queenboulder bonus pay for amazing work on #OSS 00010001156 +622231380104136062505405654370000000000#048411810615# James Thompson 1121042880001157 +705Parrotpattern bonus pay for amazing work on #OSS 00010001157 +622231380104600034484053702260000000000#686357446844# Joseph Thomas 1121042880001158 +705Howlerchestnut bonus pay for amazing work on #OSS 00010001158 +622231380104680057221483333230000000000#240465875681# Aubrey Williams 1121042880001159 +705Gamblerlizard bonus pay for amazing work on #OSS 00010001159 +622231380104016006443730055650000000000#134638684671# William Garcia 1121042880001160 +705Dropfringe bonus pay for amazing work on #OSS 00010001160 +622231380104708135714875657440000000000#208072386363# Emma Johnson 1121042880001161 +705Deerforest bonus pay for amazing work on #OSS 00010001161 +622231380104084732125456288740000000000#610865463368# Andrew White 1121042880001162 +705Carpfluff bonus pay for amazing work on #OSS 00010001162 +622231380104067455266642721750000000000#886405771166# Matthew Martinez 1121042880001163 +705Crafteralder bonus pay for amazing work on #OSS 00010001163 +622231380104456658330026287140000000000#755657363136# Ethan Anderson 1121042880001164 +705Warriorcold bonus pay for amazing work on #OSS 00010001164 +622231380104636741063786821540000000000#546215038867# Lily Robinson 1121042880001165 +705Collarrainbow bonus pay for amazing work on #OSS 00010001165 +622231380104774073652100326540000000000#553843218383# Aiden Thomas 1121042880001166 +705Shriekerhazel bonus pay for amazing work on #OSS 00010001166 +622231380104411157541880113380000000000#315636237870# Chloe Wilson 1121042880001167 +705Devourerviolet bonus pay for amazing work on #OSS 00010001167 +622231380104023008048405200280000000000#447246153622# Joseph Moore 1121042880001168 +705Chestchestnut bonus pay for amazing work on #OSS 00010001168 +622231380104806077566580248870000000000#455516616835# Sofia Moore 1121042880001169 +705Koalashine bonus pay for amazing work on #OSS 00010001169 +622231380104538745777561177110000000000#576387388436# Michael Garcia 1121042880001170 +705Stealergrove bonus pay for amazing work on #OSS 00010001170 +622231380104181566215612002640000000000#303648535754# Michael Moore 1121042880001171 +705Scourgenimble bonus pay for amazing work on #OSS 00010001171 +622231380104654334404711564040000000000#360428821573# Madison White 1121042880001172 +705Kickerregal bonus pay for amazing work on #OSS 00010001172 +622231380104264322271424414730000000000#327351716218# Olivia Harris 1121042880001173 +705Ratbitter bonus pay for amazing work on #OSS 00010001173 +622231380104706541702522850150000000000#171657063572# Jayden Robinson 1121042880001174 +705Chillercypress bonus pay for amazing work on #OSS 00010001174 +622231380104511016408707685360000000000#752784054632# Mia Harris 1121042880001175 +705Cockatooolive bonus pay for amazing work on #OSS 00010001175 +622231380104812283667858160430000000000#648325686727# Aubrey Garcia 1121042880001176 +705Riderscratch bonus pay for amazing work on #OSS 00010001176 +622231380104615800528807607440000000000#156636718328# Michael White 1121042880001177 +705Chilllace bonus pay for amazing work on #OSS 00010001177 +622231380104780204714252424380000000000#122280436014# Chloe Robinson 1121042880001178 +705Geckoshade bonus pay for amazing work on #OSS 00010001178 +622231380104737482058236541520000000000#871462848154# Addison Thomas 1121042880001179 +705Fangrune bonus pay for amazing work on #OSS 00010001179 +622231380104562167218016888480000000000#300127324688# Aiden Davis 1121042880001180 +705Shriekerdune bonus pay for amazing work on #OSS 00010001180 +622231380104645254361547676710000000000#673828652663# Ella Taylor 1121042880001181 +705Spiderfield bonus pay for amazing work on #OSS 00010001181 +622231380104365055100000445540000000000#584731808558# Daniel Jackson 1121042880001182 +705Ogreriver bonus pay for amazing work on #OSS 00010001182 +622231380104718103486811775570000000000#553576763080# Aubrey Brown 1121042880001183 +705Lanceralmond bonus pay for amazing work on #OSS 00010001183 +622231380104208554185250828710000000000#773664341177# Charlotte Anderson 1121042880001184 +705Hidepie bonus pay for amazing work on #OSS 00010001184 +622231380104230562136500613350000000000#516280383320# Michael Williams 1121042880001185 +705Antelopenoble bonus pay for amazing work on #OSS 00010001185 +622231380104031252527214315460000000000#756503077003# Zoey Brown 1121042880001186 +705Cowlnorth bonus pay for amazing work on #OSS 00010001186 +622231380104015340686637255530000000000#251334624050# Lily Garcia 1121042880001187 +705Markfringe bonus pay for amazing work on #OSS 00010001187 +622231380104015667530286636120000000000#426827078032# Anthony Robinson 1121042880001188 +705Scalequick bonus pay for amazing work on #OSS 00010001188 +622231380104167740325586504780000000000#351406234346# Mason Robinson 1121042880001189 +705Swordquasar bonus pay for amazing work on #OSS 00010001189 +622231380104571712187764134300000000000#373534701015# Ethan Thompson 1121042880001190 +705Fairydune bonus pay for amazing work on #OSS 00010001190 +622231380104636881628173442840000000000#560680876287# William Thompson 1121042880001191 +705Storkrattle bonus pay for amazing work on #OSS 00010001191 +622231380104353654526872722450000000000#781264357820# Joseph Moore 1121042880001192 +705Jesteralder bonus pay for amazing work on #OSS 00010001192 +622231380104720037048560274660000000000#051106810647# Emma Jones 1121042880001193 +705Venombrass bonus pay for amazing work on #OSS 00010001193 +622231380104238811754282882870000000000#843116745841# Aubrey Martinez 1121042880001194 +705Deathmarsh bonus pay for amazing work on #OSS 00010001194 +622231380104783466356371833780000000000#288175566775# Olivia Jackson 1121042880001195 +705Hawkluck bonus pay for amazing work on #OSS 00010001195 +622231380104326687485535844230000000000#671208434325# Aiden Wilson 1121042880001196 +705Jawbone bonus pay for amazing work on #OSS 00010001196 +622231380104307706133108131340000000000#528677734104# Lily Taylor 1121042880001197 +705Dartcord bonus pay for amazing work on #OSS 00010001197 +622231380104724117074043214140000000000#343181414070# Olivia Robinson 1121042880001198 +705Shouldermoor bonus pay for amazing work on #OSS 00010001198 +622231380104230037572633455670000000000#506400241437# Mia Martinez 1121042880001199 +705Parrotnickel bonus pay for amazing work on #OSS 00010001199 +622231380104686884066685117600000000000#875487537881# Madison Garcia 1121042880001200 +705Bellytar bonus pay for amazing work on #OSS 00010001200 +622231380104276017168702177440000000000#531306131056# Sophia Johnson 1121042880001201 +705Seekergranite bonus pay for amazing work on #OSS 00010001201 +622231380104183422133627106700000000000#054804572647# Abigail White 1121042880001202 +705Noseplum bonus pay for amazing work on #OSS 00010001202 +622231380104056066030363256570000000000#464765261373# Joshua White 1121042880001203 +705Griffinsouth bonus pay for amazing work on #OSS 00010001203 +622231380104072474603661070450000000000#448277165787# Avery Moore 1121042880001204 +705Stealerwind bonus pay for amazing work on #OSS 00010001204 +622231380104110555325367873880000000000#040874846501# William Wilson 1121042880001205 +705Lynxhelix bonus pay for amazing work on #OSS 00010001205 +622231380104857484562565731840000000000#121711463605# Charlotte Thomas 1121042880001206 +705Bellcrimson bonus pay for amazing work on #OSS 00010001206 +622231380104674503042871722360000000000#336732085386# Abigail Jones 1121042880001207 +705Hisserbattle bonus pay for amazing work on #OSS 00010001207 +622231380104776145743722648750000000000#866501586152# Avery Smith 1121042880001208 +705Dartcrystal bonus pay for amazing work on #OSS 00010001208 +622231380104786110333454243200000000000#258781140011# Zoey Moore 1121042880001209 +705Weaselswift bonus pay for amazing work on #OSS 00010001209 +622231380104683884623766206600000000000#258534217042# Chloe Thomas 1121042880001210 +705Chillerpeach bonus pay for amazing work on #OSS 00010001210 +622231380104448465436564383250000000000#587441182481# Emily Jackson 1121042880001211 +705Chatterbitter bonus pay for amazing work on #OSS 00010001211 +622231380104160636716004351330000000000#762341525885# Ella Davis 1121042880001212 +705Kangaroomeadow bonus pay for amazing work on #OSS 00010001212 +622231380104542150477418565720000000000#537167343145# Jayden Johnson 1121042880001213 +705Crowcrack bonus pay for amazing work on #OSS 00010001213 +622231380104543640034304721250000000000#537531066433# Ella Moore 1121042880001214 +705Swoopobsidian bonus pay for amazing work on #OSS 00010001214 +622231380104273641262265703320000000000#622854245566# Matthew Robinson 1121042880001215 +705Hairbush bonus pay for amazing work on #OSS 00010001215 +622231380104500826220018512260000000000#666357067336# Sophia Robinson 1121042880001216 +705Edgestripe bonus pay for amazing work on #OSS 00010001216 +622231380104518116216750738720000000000#683745284657# Elizabeth Williams 1121042880001217 +705Edgeplatinum bonus pay for amazing work on #OSS 00010001217 +622231380104301228112786863010000000000#816658103624# Natalie Davis 1121042880001218 +705Chestshadow bonus pay for amazing work on #OSS 00010001218 +622231380104365141767533021010000000000#233531710808# Charlotte Thompson 1121042880001219 +705Scribevalley bonus pay for amazing work on #OSS 00010001219 +622231380104133815270060556310000000000#725543141877# Mia Harris 1121042880001220 +705Buffalosky bonus pay for amazing work on #OSS 00010001220 +622231380104305036650642124480000000000#252186832847# Aubrey Garcia 1121042880001221 +705Minnowtulip bonus pay for amazing work on #OSS 00010001221 +622231380104806220350823258230000000000#212270701424# Chloe Martin 1121042880001222 +705Scalepinto bonus pay for amazing work on #OSS 00010001222 +622231380104878284843378460660000000000#154441528840# Mason Jackson 1121042880001223 +705Centaurbig bonus pay for amazing work on #OSS 00010001223 +622231380104364575658662124420000000000#168710853355# Natalie Martinez 1121042880001224 +705Tailshell bonus pay for amazing work on #OSS 00010001224 +622231380104303116632037702750000000000#251815452633# Ella Harris 1121042880001225 +705Fancierclever bonus pay for amazing work on #OSS 00010001225 +622231380104824765786776244440000000000#875306644724# Elizabeth Martinez 1121042880001226 +705Sightsnapdragon bonus pay for amazing work on #OSS 00010001226 +622231380104451180524712537080000000000#837736835225# Anthony Jones 1121042880001227 +705Hornholy bonus pay for amazing work on #OSS 00010001227 +622231380104647741512760564820000000000#047326772500# Joshua Smith 1121042880001228 +705Binderrogue bonus pay for amazing work on #OSS 00010001228 +622231380104258602740130550150000000000#814783830754# Ava White 1121042880001229 +705Stealercypress bonus pay for amazing work on #OSS 00010001229 +622231380104168457777607545420000000000#417253133238# Jayden Taylor 1121042880001230 +705Neckthunder bonus pay for amazing work on #OSS 00010001230 +622231380104746067168551523020000000000#723288837783# Noah Robinson 1121042880001231 +705Stingneon bonus pay for amazing work on #OSS 00010001231 +622231380104544324307324188060000000000#246422853678# William Smith 1121042880001232 +705Doomscarlet bonus pay for amazing work on #OSS 00010001232 +622231380104564363174487574030000000000#023582677358# Ethan Thomas 1121042880001233 +705Swoopemerald bonus pay for amazing work on #OSS 00010001233 +622231380104165787858313566810000000000#138362134534# Emily Harris 1121042880001234 +705Iguanarain bonus pay for amazing work on #OSS 00010001234 +622231380104882711057282516330000000000#468588661052# Ethan Miller 1121042880001235 +705Pythonplume bonus pay for amazing work on #OSS 00010001235 +622231380104175117084831452540000000000#667706338346# Joseph Jones 1121042880001236 +705Hornetsky bonus pay for amazing work on #OSS 00010001236 +622231380104577076057880738610000000000#788573478161# Andrew Johnson 1121042880001237 +705Samuraiwarp bonus pay for amazing work on #OSS 00010001237 +622231380104071687633072180430000000000#164063352380# William Moore 1121042880001238 +705Horsequill bonus pay for amazing work on #OSS 00010001238 +622231380104154040818150734650000000000#521385212811# Madison Davis 1121042880001239 +705Ogreglimmer bonus pay for amazing work on #OSS 00010001239 +622231380104257887562526548130000000000#716415685347# Sofia Thomas 1121042880001240 +705Catcherfuschia bonus pay for amazing work on #OSS 00010001240 +622231380104485762514778747510000000000#744205835217# Matthew Martin 1121042880001241 +705Singerhickory bonus pay for amazing work on #OSS 00010001241 +622231380104588437402121347400000000000#273251755750# Aiden Johnson 1121042880001242 +705Chopperring bonus pay for amazing work on #OSS 00010001242 +622231380104712235740303048280000000000#535561347655# Michael Moore 1121042880001243 +705Coyotecold bonus pay for amazing work on #OSS 00010001243 +622231380104413883818476785860000000000#364263261623# Noah Wilson 1121042880001244 +705Antlerband bonus pay for amazing work on #OSS 00010001244 +622231380104873665446147576670000000000#475117416050# William Thomas 1121042880001245 +705Slothbranch bonus pay for amazing work on #OSS 00010001245 +622231380104710456562283355150000000000#723364334765# Ethan Brown 1121042880001246 +705Mothflicker bonus pay for amazing work on #OSS 00010001246 +622231380104527077114760324530000000000#070080428713# Lily Robinson 1121042880001247 +705Footfrill bonus pay for amazing work on #OSS 00010001247 +622231380104824184634743582320000000000#521402233443# Aubrey Smith 1121042880001248 +705Roverlong bonus pay for amazing work on #OSS 00010001248 +622231380104755063371117858700000000000#881553813578# Olivia White 1121042880001249 +705Snapperfate bonus pay for amazing work on #OSS 00010001249 +622231380104881176453510254310000000000#188003348085# Madison Martinez 1121042880001250 +705Flyskitter bonus pay for amazing work on #OSS 00010001250 +82000025008922512500000000000000000000000000121042882 121042880000003 +5200Wells Fargo 121042882 PPDTrans. Des 200118 1121042880000004 +622231380104881760320257837330000000000#240162402531# Emily Thompson 1121042880000001 +705Shriekzenith bonus pay for amazing work on #OSS 00010000001 +622231380104156624387250670050000000000#472630738362# Sophia Harris 1121042880000002 +705Hairmad bonus pay for amazing work on #OSS 00010000002 +622231380104365066261554772840000000000#726407440582# Ella Harris 1121042880000003 +705Backnoble bonus pay for amazing work on #OSS 00010000003 +622231380104645058833175660220000000000#618507175673# Liam Taylor 1121042880000004 +705Ripperchisel bonus pay for amazing work on #OSS 00010000004 +622231380104117586616152466310000000000#577876471377# Addison Smith 1121042880000005 +705Hisserviolet bonus pay for amazing work on #OSS 00010000005 +622231380104211836246404264160000000000#171025830225# Sofia Jones 1121042880000006 +705Stormround bonus pay for amazing work on #OSS 00010000006 +622231380104458263333776822030000000000#551287446468# Liam Jones 1121042880000007 +705Twisterquark bonus pay for amazing work on #OSS 00010000007 +622231380104553476504730888200000000000#410332748158# Madison Thomas 1121042880000008 +705Raptorrattle bonus pay for amazing work on #OSS 00010000008 +622231380104065843157664634020000000000#640836734286# Emily Jackson 1121042880000009 +705Raysmall bonus pay for amazing work on #OSS 00010000009 +622231380104642828843101446010000000000#765318167768# Aubrey Robinson 1121042880000010 +705Monkeymoor bonus pay for amazing work on #OSS 00010000010 +622231380104042263258225035850000000000#755630733740# Liam Johnson 1121042880000011 +705Chatterpuzzle bonus pay for amazing work on #OSS 00010000011 +622231380104640057017305255860000000000#726553800142# Joseph Thompson 1121042880000012 +705Cloudfoam bonus pay for amazing work on #OSS 00010000012 +622231380104165003802112343120000000000#432528035350# James Robinson 1121042880000013 +705Thundergem bonus pay for amazing work on #OSS 00010000013 +622231380104061838738436881880000000000#415425753764# Addison Anderson 1121042880000014 +705Boarstone bonus pay for amazing work on #OSS 00010000014 +622231380104215300185348058760000000000#867200316254# Ella Smith 1121042880000015 +705Crowplump bonus pay for amazing work on #OSS 00010000015 +622231380104533077524683406370000000000#850580240346# Alexander Thompson 1121042880000016 +705Dancerlizard bonus pay for amazing work on #OSS 00010000016 +622231380104563038571503165370000000000#634067747703# Noah Brown 1121042880000017 +705Servantwell bonus pay for amazing work on #OSS 00010000017 +622231380104022448447243468870000000000#553666447187# Mason Davis 1121042880000018 +705Handglitter bonus pay for amazing work on #OSS 00010000018 +622231380104460480224650542120000000000#363415150316# Joshua Wilson 1121042880000019 +705Tonguetundra bonus pay for amazing work on #OSS 00010000019 +622231380104577107658721523470000000000#275066062766# Emily Johnson 1121042880000020 +705Fighterthread bonus pay for amazing work on #OSS 00010000020 +622231380104086326310183546750000000000#214853556385# Lily Brown 1121042880000021 +705Cowljasper bonus pay for amazing work on #OSS 00010000021 +622231380104661130568273443620000000000#688146637418# Anthony Martin 1121042880000022 +705Spikeheather bonus pay for amazing work on #OSS 00010000022 +622231380104284684613237855870000000000#601507753472# Avery Jackson 1121042880000023 +705Elkchrome bonus pay for amazing work on #OSS 00010000023 +622231380104526852176251216510000000000#275723437385# Addison White 1121042880000024 +705Thornquiver bonus pay for amazing work on #OSS 00010000024 +622231380104011666351126168470000000000#510480363411# Elizabeth Brown 1121042880000025 +705Weaselbead bonus pay for amazing work on #OSS 00010000025 +622231380104201016106777142670000000000#368402738025# William Johnson 1121042880000026 +705Warlocktwisty bonus pay for amazing work on #OSS 00010000026 +622231380104818062251121578320000000000#485564527246# Ella Moore 1121042880000027 +705Trackercosmic bonus pay for amazing work on #OSS 00010000027 +622231380104514216312870766500000000000#826527058574# Matthew Jackson 1121042880000028 +705Gamblerstar bonus pay for amazing work on #OSS 00010000028 +622231380104541687254873457150000000000#182127002670# Matthew Jackson 1121042880000029 +705Venommire bonus pay for amazing work on #OSS 00010000029 +622231380104788330183336152420000000000#837262001228# Ava Garcia 1121042880000030 +705Lancernotch bonus pay for amazing work on #OSS 00010000030 +622231380104830244468350830760000000000#524876128518# Emma Jones 1121042880000031 +705Flypalm bonus pay for amazing work on #OSS 00010000031 +622231380104515668302238347170000000000#235160186882# Emily Jackson 1121042880000032 +705Ripperalabaster bonus pay for amazing work on #OSS 00010000032 +622231380104345875508455182780000000000#743805720124# Jacob Smith 1121042880000033 +705Songmeteor bonus pay for amazing work on #OSS 00010000033 +622231380104320853833458323350000000000#774300005148# Michael Robinson 1121042880000034 +705Batequinox bonus pay for amazing work on #OSS 00010000034 +622231380104161087806544884780000000000#507080613076# Andrew Wilson 1121042880000035 +705Vulturenight bonus pay for amazing work on #OSS 00010000035 +622231380104405563457655261140000000000#030701200200# Olivia Harris 1121042880000036 +705Beakpine bonus pay for amazing work on #OSS 00010000036 +622231380104721674340358818640000000000#670466673156# Isabella Smith 1121042880000037 +705Fightervenom bonus pay for amazing work on #OSS 00010000037 +622231380104154163627403305880000000000#638182320050# Emma White 1121042880000038 +705Healerjust bonus pay for amazing work on #OSS 00010000038 +622231380104620483017538722850000000000#820222072687# Ava Thompson 1121042880000039 +705Elfrowan bonus pay for amazing work on #OSS 00010000039 +622231380104657307618215428850000000000#112004126175# Avery Martinez 1121042880000040 +705Bunnytiny bonus pay for amazing work on #OSS 00010000040 +622231380104886341305742285230000000000#541268655613# Michael Harris 1121042880000041 +705Jaguarcookie bonus pay for amazing work on #OSS 00010000041 +622231380104873175172488332240000000000#555037015721# Benjamin Thomas 1121042880000042 +705Viperluck bonus pay for amazing work on #OSS 00010000042 +622231380104408031872744261650000000000#240446120552# Elijah Harris 1121042880000043 +705Jaguarstream bonus pay for amazing work on #OSS 00010000043 +622231380104345177768720815160000000000#558455841002# David Brown 1121042880000044 +705Banebasalt bonus pay for amazing work on #OSS 00010000044 +622231380104282880524515078150000000000#444403705454# Sofia Smith 1121042880000045 +705Daggerslash bonus pay for amazing work on #OSS 00010000045 +622231380104417566780614703320000000000#874818840267# Mason Robinson 1121042880000046 +705Paladinsprinkle bonus pay for amazing work on #OSS 00010000046 +622231380104055060575364702080000000000#772206020182# Zoey Martinez 1121042880000047 +705Legshadow bonus pay for amazing work on #OSS 00010000047 +622231380104284005304155558100000000000#553473784127# Aiden Martin 1121042880000048 +705Cranecrazy bonus pay for amazing work on #OSS 00010000048 +622231380104866116360633463150000000000#528220168026# Charlotte Miller 1121042880000049 +705Falconglimmer bonus pay for amazing work on #OSS 00010000049 +622231380104770522353851535370000000000#440111155673# David Williams 1121042880000050 +705Hoofglen bonus pay for amazing work on #OSS 00010000050 +622231380104833582245177486380000000000#352202480271# Benjamin Davis 1121042880000051 +705Singerseed bonus pay for amazing work on #OSS 00010000051 +622231380104844474643203300410000000000#670101817564# Charlotte Garcia 1121042880000052 +705Wyrmequinox bonus pay for amazing work on #OSS 00010000052 +622231380104278785758452367080000000000#173588072857# David Miller 1121042880000053 +705Seedsavage bonus pay for amazing work on #OSS 00010000053 +622231380104730867670246783260000000000#008800435612# Lily White 1121042880000054 +705Grasparrow bonus pay for amazing work on #OSS 00010000054 +622231380104368705516706271150000000000#153768153153# Aubrey Johnson 1121042880000055 +705Seerrowan bonus pay for amazing work on #OSS 00010000055 +622231380104471560427333850200000000000#040422372783# Charlotte Jones 1121042880000056 +705Wandererclever bonus pay for amazing work on #OSS 00010000056 +622231380104701350715706723030000000000#118640275470# Ethan Wilson 1121042880000057 +705Carpetsage bonus pay for amazing work on #OSS 00010000057 +622231380104308068674444705710000000000#001473530465# Ella Robinson 1121042880000058 +705Mistresssnapdragon bonus pay for amazing work on #OSS 00010000058 +622231380104712105147224117600000000000#244407488331# Charlotte Jackson 1121042880000059 +705Spritezinc bonus pay for amazing work on #OSS 00010000059 +622231380104602381405863137410000000000#317013542758# Aubrey Thompson 1121042880000060 +705Bugrainbow bonus pay for amazing work on #OSS 00010000060 +622231380104862723410764470210000000000#801674721638# Ethan Brown 1121042880000061 +705Ridernickel bonus pay for amazing work on #OSS 00010000061 +622231380104013771817705645580000000000#758300401568# James Jones 1121042880000062 +705Chopperspring bonus pay for amazing work on #OSS 00010000062 +622231380104513774384516468680000000000#617401028264# Chloe Martinez 1121042880000063 +705Glassdew bonus pay for amazing work on #OSS 00010000063 +622231380104147561753021753000000000000#706616308250# David Brown 1121042880000064 +705Geckolemon bonus pay for amazing work on #OSS 00010000064 +622231380104463253065585287100000000000#376216585482# Addison Anderson 1121042880000065 +705Songbitter bonus pay for amazing work on #OSS 00010000065 +622231380104558638735241317180000000000#818861251838# Avery Martinez 1121042880000066 +705Eaglefrost bonus pay for amazing work on #OSS 00010000066 +622231380104847760088068877170000000000#230280302552# Mia Jones 1121042880000067 +705Neckbald bonus pay for amazing work on #OSS 00010000067 +622231380104730234381211540810000000000#775251221680# Lily Davis 1121042880000068 +705Followerforest bonus pay for amazing work on #OSS 00010000068 +622231380104664553610820881030000000000#230645408124# Joshua Jones 1121042880000069 +705Ridersulpher bonus pay for amazing work on #OSS 00010000069 +622231380104248406210448624180000000000#033130505704# Chloe Brown 1121042880000070 +705Scowllake bonus pay for amazing work on #OSS 00010000070 +622231380104331841138244054650000000000#416378837455# Aiden Williams 1121042880000071 +705Snagglefootroad bonus pay for amazing work on #OSS 00010000071 +622231380104335685378754883410000000000#527646141212# Avery Wilson 1121042880000072 +705Guardianmarsh bonus pay for amazing work on #OSS 00010000072 +622231380104826215231620644160000000000#834644647225# Joseph White 1121042880000073 +705Voicebronze bonus pay for amazing work on #OSS 00010000073 +622231380104164253553370487750000000000#476038717088# Sophia Harris 1121042880000074 +705Friendspark bonus pay for amazing work on #OSS 00010000074 +622231380104024825172580206700000000000#566460305805# Joshua Brown 1121042880000075 +705Cloakember bonus pay for amazing work on #OSS 00010000075 +622231380104414887276375368580000000000#404267081682# Aiden Robinson 1121042880000076 +705Harespeckle bonus pay for amazing work on #OSS 00010000076 +622231380104268046214804221750000000000#556347866247# Olivia Jones 1121042880000077 +705Crusherflint bonus pay for amazing work on #OSS 00010000077 +622231380104410752632243003810000000000#701552052454# Joseph Anderson 1121042880000078 +705Heronfoam bonus pay for amazing work on #OSS 00010000078 +622231380104546275584086348140000000000#376811736162# Avery Miller 1121042880000079 +705Warriorshadow bonus pay for amazing work on #OSS 00010000079 +622231380104653002677523215680000000000#254080653124# Emma Thomas 1121042880000080 +705Ridgeplum bonus pay for amazing work on #OSS 00010000080 +622231380104325003766545866380000000000#163532345046# Alexander Robinson 1121042880000081 +705Weedevening bonus pay for amazing work on #OSS 00010000081 +622231380104250413557201241280000000000#077707041368# William Jones 1121042880000082 +705Swordnarrow bonus pay for amazing work on #OSS 00010000082 +622231380104650685777336128670000000000#243776750500# Ethan Harris 1121042880000083 +705Hawkquiver bonus pay for amazing work on #OSS 00010000083 +622231380104378847615233745230000000000#718725224782# Joshua Moore 1121042880000084 +705Serpentspring bonus pay for amazing work on #OSS 00010000084 +622231380104004405120614501220000000000#227586118075# Aubrey Miller 1121042880000085 +705Bearluminous bonus pay for amazing work on #OSS 00010000085 +622231380104278141078357143220000000000#113767456021# Zoey Miller 1121042880000086 +705Shieldorange bonus pay for amazing work on #OSS 00010000086 +622231380104764137280208058860000000000#488617120561# Andrew Moore 1121042880000087 +705Storkgossamer bonus pay for amazing work on #OSS 00010000087 +622231380104570152230044528030000000000#221764277872# Aubrey Davis 1121042880000088 +705Slayerprong bonus pay for amazing work on #OSS 00010000088 +622231380104814573475475787600000000000#018257843620# Jayden Jones 1121042880000089 +705Eaterpine bonus pay for amazing work on #OSS 00010000089 +622231380104821675863105752200000000000#141767415314# Jacob Taylor 1121042880000090 +705Diverbone bonus pay for amazing work on #OSS 00010000090 +622231380104713155744161887470000000000#661273825450# Elijah Anderson 1121042880000091 +705Trackerwest bonus pay for amazing work on #OSS 00010000091 +622231380104820468072351238320000000000#773321747658# Madison Smith 1121042880000092 +705Hissebony bonus pay for amazing work on #OSS 00010000092 +622231380104848066447615647070000000000#661586700641# Madison Moore 1121042880000093 +705Whiprhinestone bonus pay for amazing work on #OSS 00010000093 +622231380104028405511443371700000000000#336806617578# Aiden Thompson 1121042880000094 +705Parrottwilight bonus pay for amazing work on #OSS 00010000094 +622231380104110413248230647250000000000#187546820183# Natalie Martinez 1121042880000095 +705Songforest bonus pay for amazing work on #OSS 00010000095 +622231380104385140737161816110000000000#007358116581# Chloe Garcia 1121042880000096 +705Mothplanet bonus pay for amazing work on #OSS 00010000096 +622231380104676540011373516840000000000#072072001282# James Martinez 1121042880000097 +705Swoopeast bonus pay for amazing work on #OSS 00010000097 +622231380104470760500683382120000000000#374252851850# Sophia Thomas 1121042880000098 +705Slothfast bonus pay for amazing work on #OSS 00010000098 +622231380104548684608025233840000000000#188826702160# Avery Jones 1121042880000099 +705Lightercrimson bonus pay for amazing work on #OSS 00010000099 +622231380104011680277078056460000000000#864603481235# Zoey White 1121042880000100 +705Lordlong bonus pay for amazing work on #OSS 00010000100 +622231380104532884122780003260000000000#816242427517# Emma Thomas 1121042880000101 +705Lancercrimson bonus pay for amazing work on #OSS 00010000101 +622231380104318323574712516430000000000#816737030563# Matthew Jackson 1121042880000102 +705Flystar bonus pay for amazing work on #OSS 00010000102 +622231380104341617034875355600000000000#504807551363# Ethan Garcia 1121042880000103 +705Graspplume bonus pay for amazing work on #OSS 00010000103 +622231380104048283513710560750000000000#688445302426# Olivia Davis 1121042880000104 +705Hunterruby bonus pay for amazing work on #OSS 00010000104 +622231380104846383038800763420000000000#205472042848# Aubrey Wilson 1121042880000105 +705Bootrattle bonus pay for amazing work on #OSS 00010000105 +622231380104540485505264844850000000000#258320740274# Lily Jackson 1121042880000106 +705Mousecyan bonus pay for amazing work on #OSS 00010000106 +622231380104521104475822188510000000000#646214223611# Zoey Wilson 1121042880000107 +705Cougarmoor bonus pay for amazing work on #OSS 00010000107 +622231380104728722584287637470000000000#582505134807# Isabella Harris 1121042880000108 +705Owlmaple bonus pay for amazing work on #OSS 00010000108 +622231380104878258003667321330000000000#177700625754# Daniel Jones 1121042880000109 +705Fairygeode bonus pay for amazing work on #OSS 00010000109 +622231380104886751353470830110000000000#206253274681# Liam Jones 1121042880000110 +705Koalameadow bonus pay for amazing work on #OSS 00010000110 +622231380104330485185367471340000000000#062664851225# Zoey Martin 1121042880000111 +705Unicornshag bonus pay for amazing work on #OSS 00010000111 +622231380104058856012471037440000000000#236027625264# Ella Moore 1121042880000112 +705Binderglitter bonus pay for amazing work on #OSS 00010000112 +622231380104583163118368421650000000000#405505018435# Daniel Jackson 1121042880000113 +705Shakergray bonus pay for amazing work on #OSS 00010000113 +622231380104647126175625315850000000000#221141761518# Emma Taylor 1121042880000114 +705Crusherpurple bonus pay for amazing work on #OSS 00010000114 +622231380104674555713843426180000000000#484281861334# Charlotte Martin 1121042880000115 +705Geckopeppermint bonus pay for amazing work on #OSS 00010000115 +622231380104048016652632004140000000000#351814725462# Sofia Martin 1121042880000116 +705Bisonpond bonus pay for amazing work on #OSS 00010000116 +622231380104466247412334413040000000000#205525255318# Ella Jones 1121042880000117 +705Buffalobasalt bonus pay for amazing work on #OSS 00010000117 +622231380104534344100121420750000000000#435613562804# James Williams 1121042880000118 +705Sharkwheat bonus pay for amazing work on #OSS 00010000118 +622231380104486010256618483200000000000#460514586831# Olivia Robinson 1121042880000119 +705Zebraharvest bonus pay for amazing work on #OSS 00010000119 +622231380104322744046062407420000000000#666667203442# Isabella Johnson 1121042880000120 +705Hawksugar bonus pay for amazing work on #OSS 00010000120 +622231380104714181716423613110000000000#118211248217# Chloe Taylor 1121042880000121 +705Soarerclever bonus pay for amazing work on #OSS 00010000121 +622231380104040012216567043760000000000#858821775184# Alexander Moore 1121042880000122 +705Stinglime bonus pay for amazing work on #OSS 00010000122 +622231380104531388734725885130000000000#374263817041# Lily Miller 1121042880000123 +705Dutchessmorning bonus pay for amazing work on #OSS 00010000123 +622231380104485831344151828080000000000#002843157044# Lily Robinson 1121042880000124 +705Tradernotch bonus pay for amazing work on #OSS 00010000124 +622231380104014446771401538630000000000#656610850282# Michael Taylor 1121042880000125 +705Catchermisty bonus pay for amazing work on #OSS 00010000125 +622231380104743413376613281340000000000#622585168181# Noah Jones 1121042880000126 +705Pumaroan bonus pay for amazing work on #OSS 00010000126 +622231380104235023438041865400000000000#645806562045# Jayden Martin 1121042880000127 +705Sparrowflash bonus pay for amazing work on #OSS 00010000127 +622231380104130835305510676060000000000#436128662265# Charlotte Moore 1121042880000128 +705Divejet bonus pay for amazing work on #OSS 00010000128 +622231380104580510113773261750000000000#050457314141# Charlotte Taylor 1121042880000129 +705Volepitch bonus pay for amazing work on #OSS 00010000129 +622231380104071614237837451580000000000#612832714886# Isabella Jackson 1121042880000130 +705Cougarglen bonus pay for amazing work on #OSS 00010000130 +622231380104820640648180000810000000000#046263277064# Mia Moore 1121042880000131 +705Spurgravel bonus pay for amazing work on #OSS 00010000131 +622231380104472115842872484030000000000#128508200734# Emily Brown 1121042880000132 +705Thieflong bonus pay for amazing work on #OSS 00010000132 +622231380104111080564086727830000000000#024581142388# Chloe Robinson 1121042880000133 +705Chatterchatter bonus pay for amazing work on #OSS 00010000133 +622231380104161022724552108700000000000#665654101821# Aubrey Williams 1121042880000134 +705Batmeadow bonus pay for amazing work on #OSS 00010000134 +622231380104804760785645343850000000000#455441457462# Aiden Taylor 1121042880000135 +705Spikecurly bonus pay for amazing work on #OSS 00010000135 +622231380104868438381832824750000000000#353704123164# Aubrey Johnson 1121042880000136 +705Kangarooquasar bonus pay for amazing work on #OSS 00010000136 +622231380104157680354805061350000000000#447306410077# Emma Miller 1121042880000137 +705Hoofash bonus pay for amazing work on #OSS 00010000137 +622231380104473831441672886020000000000#331218034047# Sofia Wilson 1121042880000138 +705Scowlroan bonus pay for amazing work on #OSS 00010000138 +622231380104014870788882407820000000000#718676874155# Natalie Thomas 1121042880000139 +705Legsrapid bonus pay for amazing work on #OSS 00010000139 +622231380104080618401373236760000000000#508471842363# Daniel Thompson 1121042880000140 +705Scalecrocus bonus pay for amazing work on #OSS 00010000140 +622231380104165060774435288480000000000#025822628624# Abigail Anderson 1121042880000141 +705Eagleruby bonus pay for amazing work on #OSS 00010000141 +622231380104457143527330175520000000000#011517884205# Isabella Taylor 1121042880000142 +705Jayflicker bonus pay for amazing work on #OSS 00010000142 +622231380104107331355778334870000000000#280270866542# Anthony Miller 1121042880000143 +705Weedlong bonus pay for amazing work on #OSS 00010000143 +622231380104273077601601601270000000000#323118854867# Sophia Miller 1121042880000144 +705Houndlizard bonus pay for amazing work on #OSS 00010000144 +622231380104600276313334025710000000000#676522104410# Lily Robinson 1121042880000145 +705Raylaser bonus pay for amazing work on #OSS 00010000145 +622231380104810025257186081730000000000#888111137816# Matthew Martinez 1121042880000146 +705Browrainbow bonus pay for amazing work on #OSS 00010000146 +622231380104137412326835826340000000000#857463561553# James Robinson 1121042880000147 +705Graspwheat bonus pay for amazing work on #OSS 00010000147 +622231380104640758651026616520000000000#876475218843# Charlotte Williams 1121042880000148 +705Burnsky bonus pay for amazing work on #OSS 00010000148 +622231380104164721642804883670000000000#512307502323# Mia Thomas 1121042880000149 +705Ravencomet bonus pay for amazing work on #OSS 00010000149 +622231380104386732584842854860000000000#876633254652# Olivia Miller 1121042880000150 +705Saverfog bonus pay for amazing work on #OSS 00010000150 +622231380104834628828885788010000000000#358678283010# Aubrey Wilson 1121042880000151 +705Slayerplanet bonus pay for amazing work on #OSS 00010000151 +622231380104341447756487863410000000000#237610100216# William Thomas 1121042880000152 +705Snarlmarble bonus pay for amazing work on #OSS 00010000152 +622231380104653004738145406700000000000#482640815016# Anthony White 1121042880000153 +705Scalehail bonus pay for amazing work on #OSS 00010000153 +622231380104841672673727185520000000000#723222117642# Sofia Smith 1121042880000154 +705Scribestar bonus pay for amazing work on #OSS 00010000154 +622231380104307707485346234010000000000#178060180282# Isabella Martin 1121042880000155 +705Lancercloud bonus pay for amazing work on #OSS 00010000155 +622231380104504587638218184840000000000#123432840737# James Robinson 1121042880000156 +705Lifterapple bonus pay for amazing work on #OSS 00010000156 +622231380104322687563687345100000000000#853643376375# Matthew Thomas 1121042880000157 +705Biterainbow bonus pay for amazing work on #OSS 00010000157 +622231380104636140426502307720000000000#317068721126# Mason White 1121042880000158 +705Whimseydeep bonus pay for amazing work on #OSS 00010000158 +622231380104253603723327852680000000000#211686845754# Daniel Thomas 1121042880000159 +705Stingerfantasy bonus pay for amazing work on #OSS 00010000159 +622231380104445773837130246580000000000#437712606426# Anthony Smith 1121042880000160 +705Guardianplume bonus pay for amazing work on #OSS 00010000160 +622231380104466534817261277620000000000#373014671052# Mason Martinez 1121042880000161 +705Warriormulberry bonus pay for amazing work on #OSS 00010000161 +622231380104054274775002403740000000000#067426882170# Ethan Miller 1121042880000162 +705Hairswamp bonus pay for amazing work on #OSS 00010000162 +622231380104543001305180548330000000000#238745741330# Emily Martinez 1121042880000163 +705Markheather bonus pay for amazing work on #OSS 00010000163 +622231380104263057312186556200000000000#484810006601# Aubrey Thomas 1121042880000164 +705Boltcosmic bonus pay for amazing work on #OSS 00010000164 +622231380104041141765106605470000000000#663287644175# Elijah Robinson 1121042880000165 +705Catclear bonus pay for amazing work on #OSS 00010000165 +622231380104887665610262137140000000000#484270406476# Aiden Jackson 1121042880000166 +705Necksteel bonus pay for amazing work on #OSS 00010000166 +622231380104756456486454384240000000000#763083680326# Sofia Smith 1121042880000167 +705Pantherlaser bonus pay for amazing work on #OSS 00010000167 +622231380104255327833460257610000000000#662547246052# Charlotte Martin 1121042880000168 +705Roarglacier bonus pay for amazing work on #OSS 00010000168 +622231380104376416324631587600000000000#640757317567# Mason Moore 1121042880000169 +705Daggergrave bonus pay for amazing work on #OSS 00010000169 +622231380104303751137331023410000000000#221405700534# Andrew Jackson 1121042880000170 +705Cowldark bonus pay for amazing work on #OSS 00010000170 +622231380104517378123317077340000000000#722345143042# James Harris 1121042880000171 +705Queenmalachite bonus pay for amazing work on #OSS 00010000171 +622231380104723550762683628050000000000#510526645658# Liam Garcia 1121042880000172 +705Haresulpher bonus pay for amazing work on #OSS 00010000172 +622231380104351832284722501180000000000#061738845034# Jacob Jones 1121042880000173 +705Bowpink bonus pay for amazing work on #OSS 00010000173 +622231380104882604882145654350000000000#670383284177# Jayden Wilson 1121042880000174 +705Spiderapple bonus pay for amazing work on #OSS 00010000174 +622231380104276723885071627540000000000#336480726864# Ava Thomas 1121042880000175 +705Boaember bonus pay for amazing work on #OSS 00010000175 +622231380104107221861422813830000000000#266557562730# William Harris 1121042880000176 +705Hidethorn bonus pay for amazing work on #OSS 00010000176 +622231380104867271088412076860000000000#118133054887# Joshua Miller 1121042880000177 +705Knightbloom bonus pay for amazing work on #OSS 00010000177 +622231380104882446328703056820000000000#320687417842# Elijah Anderson 1121042880000178 +705Razorwind bonus pay for amazing work on #OSS 00010000178 +622231380104627665623608510040000000000#602623520377# Isabella Johnson 1121042880000179 +705Chopperprism bonus pay for amazing work on #OSS 00010000179 +622231380104200620506784451110000000000#263554662480# Natalie Harris 1121042880000180 +705Friendseed bonus pay for amazing work on #OSS 00010000180 +622231380104708811443681386380000000000#616727152316# Alexander Garcia 1121042880000181 +705Staghorse bonus pay for amazing work on #OSS 00010000181 +622231380104332650427367040620000000000#313524311574# Natalie Robinson 1121042880000182 +705Pythonglass bonus pay for amazing work on #OSS 00010000182 +622231380104187603157262635100000000000#721867160163# Joseph Wilson 1121042880000183 +705Orioledenim bonus pay for amazing work on #OSS 00010000183 +622231380104366854543077482340000000000#142732382486# William Jones 1121042880000184 +705Turnerfree bonus pay for amazing work on #OSS 00010000184 +622231380104552227145685814500000000000#646150581387# Matthew Thompson 1121042880000185 +705Hidecoconut bonus pay for amazing work on #OSS 00010000185 +622231380104338870662726127580000000000#727583738378# Sofia Taylor 1121042880000186 +705Rippertime bonus pay for amazing work on #OSS 00010000186 +622231380104830534070512424750000000000#464130453230# Benjamin Johnson 1121042880000187 +705Shoulderballistic bonus pay for amazing work on #OSS 00010000187 +622231380104127568625268571080000000000#512220673624# James Smith 1121042880000188 +705Lancersun bonus pay for amazing work on #OSS 00010000188 +622231380104444462223685586380000000000#878258226038# Alexander Miller 1121042880000189 +705Swallowbasalt bonus pay for amazing work on #OSS 00010000189 +622231380104632575081248501430000000000#842325856417# Mason Johnson 1121042880000190 +705Elkpool bonus pay for amazing work on #OSS 00010000190 +622231380104477444128512321180000000000#077731041851# Olivia Taylor 1121042880000191 +705Parrotgreen bonus pay for amazing work on #OSS 00010000191 +622231380104610301755735605030000000000#403867833856# Mason Johnson 1121042880000192 +705Crowrune bonus pay for amazing work on #OSS 00010000192 +622231380104468432544176460040000000000#243051400148# James Jones 1121042880000193 +705Falconstar bonus pay for amazing work on #OSS 00010000193 +622231380104408544381335471030000000000#778810368117# Noah Wilson 1121042880000194 +705Hawksurf bonus pay for amazing work on #OSS 00010000194 +622231380104362511442276587680000000000#816381535182# Sofia Harris 1121042880000195 +705Burnkiwi bonus pay for amazing work on #OSS 00010000195 +622231380104656456080205547460000000000#877751142834# Elizabeth Davis 1121042880000196 +705Heronbrown bonus pay for amazing work on #OSS 00010000196 +622231380104334242713146433760000000000#761086006284# Olivia Harris 1121042880000197 +705Sharkmountain bonus pay for amazing work on #OSS 00010000197 +622231380104583151403752216830000000000#368550845435# Avery Martinez 1121042880000198 +705Cloudpeppermint bonus pay for amazing work on #OSS 00010000198 +622231380104244165181526434270000000000#643045832254# Daniel White 1121042880000199 +705Unicornblue bonus pay for amazing work on #OSS 00010000199 +622231380104146607581685517360000000000#518210776242# Abigail Williams 1121042880000200 +705Princeshallow bonus pay for amazing work on #OSS 00010000200 +622231380104572048664548207270000000000#003672612806# James Thompson 1121042880000201 +705Bugclever bonus pay for amazing work on #OSS 00010000201 +622231380104544225867228265830000000000#575352270174# Liam Wilson 1121042880000202 +705Swallownorth bonus pay for amazing work on #OSS 00010000202 +622231380104642355501468133810000000000#635507676803# Liam Williams 1121042880000203 +705Lanternebony bonus pay for amazing work on #OSS 00010000203 +622231380104678417841038877410000000000#306552286301# Sophia Miller 1121042880000204 +705Kingginger bonus pay for amazing work on #OSS 00010000204 +622231380104535072125387200350000000000#876787588765# Sofia Harris 1121042880000205 +705Lizardwater bonus pay for amazing work on #OSS 00010000205 +622231380104867138186685571200000000000#341452627006# Lily Williams 1121042880000206 +705Cowlwinter bonus pay for amazing work on #OSS 00010000206 +622231380104678315602546262450000000000#731604602233# Benjamin Garcia 1121042880000207 +705Samuraichatter bonus pay for amazing work on #OSS 00010000207 +622231380104775584184031211540000000000#032581518800# Anthony White 1121042880000208 +705Tigerfossil bonus pay for amazing work on #OSS 00010000208 +622231380104251715441841358420000000000#116553287560# Ava Taylor 1121042880000209 +705Bootwest bonus pay for amazing work on #OSS 00010000209 +622231380104271335613347152180000000000#574351748267# Mason Smith 1121042880000210 +705Salmoncyber bonus pay for amazing work on #OSS 00010000210 +622231380104147235021441743300000000000#521160060662# William Taylor 1121042880000211 +705Warriordog bonus pay for amazing work on #OSS 00010000211 +622231380104543125266433746020000000000#314130562326# Joseph Wilson 1121042880000212 +705Llamafree bonus pay for amazing work on #OSS 00010000212 +622231380104052214433203763530000000000#565562213000# Alexander White 1121042880000213 +705Knifesaber bonus pay for amazing work on #OSS 00010000213 +622231380104264482368503817320000000000#561883216154# Olivia Jackson 1121042880000214 +705Handnorth bonus pay for amazing work on #OSS 00010000214 +622231380104513152482732146260000000000#857483328356# Andrew Brown 1121042880000215 +705Shriekroot bonus pay for amazing work on #OSS 00010000215 +622231380104252174455756071760000000000#001387082447# Addison Thompson 1121042880000216 +705Nosejasper bonus pay for amazing work on #OSS 00010000216 +622231380104162707761300482850000000000#771068388764# Mason Wilson 1121042880000217 +705Eatersun bonus pay for amazing work on #OSS 00010000217 +622231380104145048768263507310000000000#724451688140# Zoey Robinson 1121042880000218 +705Takermulberry bonus pay for amazing work on #OSS 00010000218 +622231380104873117377511588760000000000#430051811878# Olivia Thomas 1121042880000219 +705Weedbutton bonus pay for amazing work on #OSS 00010000219 +622231380104557278833585865370000000000#467624701714# Aiden Robinson 1121042880000220 +705Thiefdestiny bonus pay for amazing work on #OSS 00010000220 +622231380104126413255185132760000000000#644532765545# Lily Brown 1121042880000221 +705Viperrag bonus pay for amazing work on #OSS 00010000221 +622231380104638821455223660780000000000#660310470360# Mia Miller 1121042880000222 +705Cloudmarble bonus pay for amazing work on #OSS 00010000222 +622231380104254614822046542600000000000#864642038350# Avery White 1121042880000223 +705Boltstitch bonus pay for amazing work on #OSS 00010000223 +622231380104215501216183177070000000000#276008854560# Michael Thompson 1121042880000224 +705Stalkerclever bonus pay for amazing work on #OSS 00010000224 +622231380104346482206361010150000000000#500366617876# Joshua Thomas 1121042880000225 +705Mouseshine bonus pay for amazing work on #OSS 00010000225 +622231380104637766085186418630000000000#250640801165# James Anderson 1121042880000226 +705Weedequinox bonus pay for amazing work on #OSS 00010000226 +622231380104411234767324062570000000000#467624102184# Liam Harris 1121042880000227 +705Warriorbutter bonus pay for amazing work on #OSS 00010000227 +622231380104888125002203187820000000000#172087580403# Aubrey Thompson 1121042880000228 +705Shieldsly bonus pay for amazing work on #OSS 00010000228 +622231380104266422036063062750000000000#423244366307# Matthew Taylor 1121042880000229 +705Spritemaple bonus pay for amazing work on #OSS 00010000229 +622231380104116332434205475160000000000#847256113548# Ethan Thomas 1121042880000230 +705Sightmeteor bonus pay for amazing work on #OSS 00010000230 +622231380104743058058177032100000000000#475528545254# Andrew Robinson 1121042880000231 +705Fangsky bonus pay for amazing work on #OSS 00010000231 +622231380104121487212324380180000000000#817746577840# Benjamin Johnson 1121042880000232 +705Lasherquartz bonus pay for amazing work on #OSS 00010000232 +622231380104508106567101214440000000000#267712647736# James Harris 1121042880000233 +705Shiftspark bonus pay for amazing work on #OSS 00010000233 +622231380104381160145150788820000000000#831113180174# Ethan Harris 1121042880000234 +705Lightningshallow bonus pay for amazing work on #OSS 00010000234 +622231380104367212504681788360000000000#748845175715# Ethan Martin 1121042880000235 +705Snakeplump bonus pay for amazing work on #OSS 00010000235 +622231380104753253767255747130000000000#665318231208# Emma Thompson 1121042880000236 +705Hyenasunset bonus pay for amazing work on #OSS 00010000236 +622231380104552635210051233680000000000#433485545061# Aubrey Garcia 1121042880000237 +705Lightningboom bonus pay for amazing work on #OSS 00010000237 +622231380104306181316810671330000000000#707245015318# Ethan Davis 1121042880000238 +705Sargentscarlet bonus pay for amazing work on #OSS 00010000238 +622231380104125111303328265730000000000#230028840316# Natalie Jackson 1121042880000239 +705Otterfantasy bonus pay for amazing work on #OSS 00010000239 +622231380104656515754835387850000000000#305018563225# Noah Johnson 1121042880000240 +705Flybrindle bonus pay for amazing work on #OSS 00010000240 +622231380104567471734478285280000000000#507147270755# Daniel Martin 1121042880000241 +705Graspsour bonus pay for amazing work on #OSS 00010000241 +622231380104251166103226874120000000000#432601536535# Joseph Davis 1121042880000242 +705Samuraisponge bonus pay for amazing work on #OSS 00010000242 +622231380104044030003866828580000000000#312645213850# Joseph Robinson 1121042880000243 +705Scourgegrass bonus pay for amazing work on #OSS 00010000243 +622231380104428354062670174420000000000#025542407572# William Wilson 1121042880000244 +705Ogrecoral bonus pay for amazing work on #OSS 00010000244 +622231380104103466462021126180000000000#731052212163# Addison Martin 1121042880000245 +705Fingerfreckle bonus pay for amazing work on #OSS 00010000245 +622231380104542011576788436220000000000#787723782317# Daniel Robinson 1121042880000246 +705Eaterpaper bonus pay for amazing work on #OSS 00010000246 +622231380104174605742588142210000000000#077321238107# Aubrey Wilson 1121042880000247 +705Raybutton bonus pay for amazing work on #OSS 00010000247 +622231380104767622613621815220000000000#326321527602# Chloe Davis 1121042880000248 +705Musecurse bonus pay for amazing work on #OSS 00010000248 +622231380104713550606813260160000000000#076446128545# Isabella Martinez 1121042880000249 +705Howlerchestnut bonus pay for amazing work on #OSS 00010000249 +622231380104858156863614830870000000000#615172515758# Madison Williams 1121042880000250 +705Mustangjasper bonus pay for amazing work on #OSS 00010000250 +622231380104406506027065645480000000000#502668178156# Lily Anderson 1121042880000251 +705Cockatoofair bonus pay for amazing work on #OSS 00010000251 +622231380104753645588462602170000000000#333224107073# Sofia Johnson 1121042880000252 +705Doomluck bonus pay for amazing work on #OSS 00010000252 +622231380104273430657880603480000000000#153016576455# Matthew Jones 1121042880000253 +705Hoofbitter bonus pay for amazing work on #OSS 00010000253 +622231380104543043500232315840000000000#201452641268# James Taylor 1121042880000254 +705Finvalley bonus pay for amazing work on #OSS 00010000254 +622231380104338811064665844330000000000#255856020112# Noah Jones 1121042880000255 +705Birdequinox bonus pay for amazing work on #OSS 00010000255 +622231380104356521646003277480000000000#838536537568# Elijah White 1121042880000256 +705Ladysticky bonus pay for amazing work on #OSS 00010000256 +622231380104737808144328885250000000000#555802381706# Olivia Miller 1121042880000257 +705Bitertulip bonus pay for amazing work on #OSS 00010000257 +622231380104865587007144613380000000000#802845516727# Benjamin Davis 1121042880000258 +705Geckobutter bonus pay for amazing work on #OSS 00010000258 +622231380104003683136756344500000000000#701018276771# James Davis 1121042880000259 +705Vultureroot bonus pay for amazing work on #OSS 00010000259 +622231380104867880115261408100000000000#244582438215# Elizabeth Martin 1121042880000260 +705Rabbitsprinkle bonus pay for amazing work on #OSS 00010000260 +622231380104878311302654875380000000000#687274048405# Emily Martinez 1121042880000261 +705Tongueprickle bonus pay for amazing work on #OSS 00010000261 +622231380104470034831655846420000000000#618237877182# Isabella Wilson 1121042880000262 +705Apemetal bonus pay for amazing work on #OSS 00010000262 +622231380104304333150385186680000000000#625536257124# Chloe White 1121042880000263 +705Elkgold bonus pay for amazing work on #OSS 00010000263 +622231380104155658706163375150000000000#200778015055# Sophia Smith 1121042880000264 +705Gooseplume bonus pay for amazing work on #OSS 00010000264 +622231380104207706626282655040000000000#525550264268# Addison Garcia 1121042880000265 +705Binderkeen bonus pay for amazing work on #OSS 00010000265 +622231380104460404115300882020000000000#448775843331# Isabella Johnson 1121042880000266 +705Scarhail bonus pay for amazing work on #OSS 00010000266 +622231380104307483761870152870000000000#831574015222# Chloe Robinson 1121042880000267 +705Seedred bonus pay for amazing work on #OSS 00010000267 +622231380104712133287807570060000000000#788110788103# Andrew Jackson 1121042880000268 +705Carpetrapid bonus pay for amazing work on #OSS 00010000268 +622231380104041681027115134280000000000#201027537631# Liam Anderson 1121042880000269 +705Legivory bonus pay for amazing work on #OSS 00010000269 +622231380104208636610261073140000000000#478864881512# Matthew Anderson 1121042880000270 +705Lightningruby bonus pay for amazing work on #OSS 00010000270 +622231380104128038812037206780000000000#831422811862# Natalie Brown 1121042880000271 +705Hoofblack bonus pay for amazing work on #OSS 00010000271 +622231380104143138413401536100000000000#780188566381# Zoey Anderson 1121042880000272 +705Apepine bonus pay for amazing work on #OSS 00010000272 +622231380104018480358414188410000000000#157818861636# Alexander Thompson 1121042880000273 +705Warriorzenith bonus pay for amazing work on #OSS 00010000273 +622231380104813828232381338700000000000#182018247446# Jayden Smith 1121042880000274 +705Slicerplain bonus pay for amazing work on #OSS 00010000274 +622231380104773051242475462780000000000#460015612058# Noah Robinson 1121042880000275 +705Stalkerglacier bonus pay for amazing work on #OSS 00010000275 +622231380104175807183171101760000000000#244665487126# Ella White 1121042880000276 +705Divedeep bonus pay for amazing work on #OSS 00010000276 +622231380104113518670183776530000000000#030105512488# Emma Thompson 1121042880000277 +705Batbuttercup bonus pay for amazing work on #OSS 00010000277 +622231380104381580736536735520000000000#762127400760# James Thomas 1121042880000278 +705Pegasusgem bonus pay for amazing work on #OSS 00010000278 +622231380104186286075573777740000000000#236073348388# Emily White 1121042880000279 +705Owlweak bonus pay for amazing work on #OSS 00010000279 +622231380104280735267685657000000000000#881703011085# Sofia Davis 1121042880000280 +705Bisonspot bonus pay for amazing work on #OSS 00010000280 +622231380104476817741175623360000000000#121577661021# Isabella Moore 1121042880000281 +705Fancieroil bonus pay for amazing work on #OSS 00010000281 +622231380104270104573783105740000000000#647717352025# Avery Robinson 1121042880000282 +705Sharkphase bonus pay for amazing work on #OSS 00010000282 +622231380104210475135101417030000000000#234125070806# Natalie Wilson 1121042880000283 +705Carptime bonus pay for amazing work on #OSS 00010000283 +622231380104153443031465863460000000000#514588836527# Sofia Smith 1121042880000284 +705Bearsnow bonus pay for amazing work on #OSS 00010000284 +622231380104863203740126183680000000000#705825703388# William Martin 1121042880000285 +705Weaveriridescent bonus pay for amazing work on #OSS 00010000285 +622231380104528071261712046030000000000#404642032115# Jayden Moore 1121042880000286 +705Otterpower bonus pay for amazing work on #OSS 00010000286 +622231380104321252301823378280000000000#276582055784# Aubrey Anderson 1121042880000287 +705Toeveil bonus pay for amazing work on #OSS 00010000287 +622231380104773435280848724360000000000#141074756808# Aubrey Anderson 1121042880000288 +705Crowplume bonus pay for amazing work on #OSS 00010000288 +622231380104207706756577266560000000000#657672614857# Joshua White 1121042880000289 +705Piperpink bonus pay for amazing work on #OSS 00010000289 +622231380104664606451433148460000000000#061823812718# Addison Davis 1121042880000290 +705Eaterhollow bonus pay for amazing work on #OSS 00010000290 +622231380104853233857302345540000000000#230083803211# Zoey White 1121042880000291 +705Sparrowlinen bonus pay for amazing work on #OSS 00010000291 +622231380104564045316438781250000000000#081324128425# Isabella Williams 1121042880000292 +705Dartwhite bonus pay for amazing work on #OSS 00010000292 +622231380104514183467676282850000000000#826302420331# Liam Taylor 1121042880000293 +705Runnerfrost bonus pay for amazing work on #OSS 00010000293 +622231380104606116517137623500000000000#861773025314# David Miller 1121042880000294 +705Binderplanet bonus pay for amazing work on #OSS 00010000294 +622231380104345270402000821540000000000#583246087562# Madison Martin 1121042880000295 +705Walkerbush bonus pay for amazing work on #OSS 00010000295 +622231380104874283760781377210000000000#302717412753# Lily Smith 1121042880000296 +705Gemfoam bonus pay for amazing work on #OSS 00010000296 +622231380104205550328476785720000000000#056273275788# Addison White 1121042880000297 +705Slavebead bonus pay for amazing work on #OSS 00010000297 +622231380104347286757337736630000000000#210330573173# Alexander Brown 1121042880000298 +705Doomhickory bonus pay for amazing work on #OSS 00010000298 +622231380104176545514362246820000000000#266556567376# Ethan Martinez 1121042880000299 +705Snagglefoothurricane bonus pay for amazing work on #OSS 00010000299 +622231380104758887757014521500000000000#426032123714# Joseph Harris 1121042880000300 +705Catfree bonus pay for amazing work on #OSS 00010000300 +622231380104243566478884012780000000000#853717731157# Emily Harris 1121042880000301 +705Toucaniron bonus pay for amazing work on #OSS 00010000301 +622231380104883744783336678150000000000#120124015335# William Anderson 1121042880000302 +705Talonchocolate bonus pay for amazing work on #OSS 00010000302 +622231380104752137233037418500000000000#025048157335# Avery Martinez 1121042880000303 +705Venomprairie bonus pay for amazing work on #OSS 00010000303 +622231380104551268357808005770000000000#258252632521# Charlotte Martin 1121042880000304 +705Cubscythe bonus pay for amazing work on #OSS 00010000304 +622231380104422325567132081330000000000#635465478456# Liam Moore 1121042880000305 +705Rabbitchrome bonus pay for amazing work on #OSS 00010000305 +622231380104488563336481726210000000000#036820076467# Anthony Thompson 1121042880000306 +705Maskcarnation bonus pay for amazing work on #OSS 00010000306 +622231380104460657041300854460000000000#888830460887# Mason Smith 1121042880000307 +705Riderripple bonus pay for amazing work on #OSS 00010000307 +622231380104007552577235137160000000000#804074834325# Madison Garcia 1121042880000308 +705Beardestiny bonus pay for amazing work on #OSS 00010000308 +622231380104426072353206322700000000000#357161305771# Aiden Wilson 1121042880000309 +705Napeshy bonus pay for amazing work on #OSS 00010000309 +622231380104273044742277544640000000000#541423505178# Benjamin Moore 1121042880000310 +705Lizardmagenta bonus pay for amazing work on #OSS 00010000310 +622231380104368083254765888050000000000#682004008134# Natalie Miller 1121042880000311 +705Waspsummer bonus pay for amazing work on #OSS 00010000311 +622231380104518104185160468150000000000#602080013384# Liam Robinson 1121042880000312 +705Turnerweak bonus pay for amazing work on #OSS 00010000312 +622231380104503288012368645530000000000#425810404011# Joshua Garcia 1121042880000313 +705Legscypress bonus pay for amazing work on #OSS 00010000313 +622231380104045771471043141500000000000#023247222234# Aiden Anderson 1121042880000314 +705Apetorch bonus pay for amazing work on #OSS 00010000314 +622231380104221067470564447540000000000#733634028336# Natalie White 1121042880000315 +705Fancierjust bonus pay for amazing work on #OSS 00010000315 +622231380104403163004100124880000000000#727448048621# Benjamin Jackson 1121042880000316 +705Fangclear bonus pay for amazing work on #OSS 00010000316 +622231380104460344478057532010000000000#142508657107# David Davis 1121042880000317 +705Swoopmalachite bonus pay for amazing work on #OSS 00010000317 +622231380104208531366321803450000000000#264755470333# Avery Miller 1121042880000318 +705Roverhickory bonus pay for amazing work on #OSS 00010000318 +622231380104347421844286227520000000000#562462448437# Emma Jones 1121042880000319 +705Sargentkiwi bonus pay for amazing work on #OSS 00010000319 +622231380104061174428160117540000000000#756612615125# Liam Johnson 1121042880000320 +705Pawdark bonus pay for amazing work on #OSS 00010000320 +622231380104573111120267537000000000000#532757767053# Aiden White 1121042880000321 +705Devourertree bonus pay for amazing work on #OSS 00010000321 +622231380104855841180537443210000000000#848631211041# Zoey Moore 1121042880000322 +705Leadercoral bonus pay for amazing work on #OSS 00010000322 +622231380104665485354002766010000000000#036167737242# Elijah Garcia 1121042880000323 +705Wingmud bonus pay for amazing work on #OSS 00010000323 +622231380104601801146008528440000000000#728213643351# Elijah Jackson 1121042880000324 +705Binderflicker bonus pay for amazing work on #OSS 00010000324 +622231380104350142808866135480000000000#460534188726# Addison Taylor 1121042880000325 +705Liftercream bonus pay for amazing work on #OSS 00010000325 +622231380104581635867643002240000000000#515804402133# William Thompson 1121042880000326 +705Sightsprinkle bonus pay for amazing work on #OSS 00010000326 +622231380104717628600502318110000000000#771231288740# Chloe Moore 1121042880000327 +705Diverindigo bonus pay for amazing work on #OSS 00010000327 +622231380104643116362354170520000000000#021725612172# Elizabeth Martinez 1121042880000328 +705Warlockheavy bonus pay for amazing work on #OSS 00010000328 +622231380104300161781088452860000000000#630820302058# Joshua Thomas 1121042880000329 +705Sharkapple bonus pay for amazing work on #OSS 00010000329 +622231380104200231646236853150000000000#602364521174# Jacob Miller 1121042880000330 +705Stealerspring bonus pay for amazing work on #OSS 00010000330 +622231380104861033216866317430000000000#510334012004# Andrew Martin 1121042880000331 +705Toucanpaper bonus pay for amazing work on #OSS 00010000331 +622231380104374844107676348660000000000#073153667243# Olivia Martinez 1121042880000332 +705Ribmarsh bonus pay for amazing work on #OSS 00010000332 +622231380104606028847252716060000000000#622322674610# Liam Garcia 1121042880000333 +705Eatertruth bonus pay for amazing work on #OSS 00010000333 +622231380104563718483778664050000000000#820105585203# Elizabeth Anderson 1121042880000334 +705Lighteryellow bonus pay for amazing work on #OSS 00010000334 +622231380104337731855450751610000000000#158816872780# Elijah Miller 1121042880000335 +705Frightwhite bonus pay for amazing work on #OSS 00010000335 +622231380104723818114023821300000000000#234632683744# Liam Harris 1121042880000336 +705Howlerfortune bonus pay for amazing work on #OSS 00010000336 +622231380104123178853826505370000000000#351600832631# Ava Robinson 1121042880000337 +705Headzest bonus pay for amazing work on #OSS 00010000337 +622231380104240246314014618350000000000#276135332516# Aiden Johnson 1121042880000338 +705Frillflint bonus pay for amazing work on #OSS 00010000338 +622231380104452363703263381610000000000#851354060546# Isabella Harris 1121042880000339 +705Sparrowhoneysuckle bonus pay for amazing work on #OSS 00010000339 +622231380104307001664716550680000000000#552104571336# Chloe Jackson 1121042880000340 +705Terriervolcano bonus pay for amazing work on #OSS 00010000340 +622231380104264430722885056430000000000#180238122154# Olivia Davis 1121042880000341 +705Weedsplash bonus pay for amazing work on #OSS 00010000341 +622231380104052622276637331510000000000#587261222177# Joshua Robinson 1121042880000342 +705Legband bonus pay for amazing work on #OSS 00010000342 +622231380104213427614803371640000000000#578115457025# Aubrey Miller 1121042880000343 +705Crusherfoil bonus pay for amazing work on #OSS 00010000343 +622231380104241081065283175250000000000#338751664820# Natalie Harris 1121042880000344 +705Robinweak bonus pay for amazing work on #OSS 00010000344 +622231380104028432621233832310000000000#154771376544# Joseph Wilson 1121042880000345 +705Watcherfreckle bonus pay for amazing work on #OSS 00010000345 +622231380104431648253081022440000000000#364041883644# Sofia Thompson 1121042880000346 +705Leopardchain bonus pay for amazing work on #OSS 00010000346 +622231380104652400261258210820000000000#331215878116# Joseph White 1121042880000347 +705Oxpaper bonus pay for amazing work on #OSS 00010000347 +622231380104725378805144780130000000000#855517733235# Chloe Miller 1121042880000348 +705Loonwave bonus pay for amazing work on #OSS 00010000348 +622231380104637144321708244550000000000#601157287071# Addison Thomas 1121042880000349 +705Flasherrainbow bonus pay for amazing work on #OSS 00010000349 +622231380104376653041431804480000000000#102636134517# Anthony Jackson 1121042880000350 +705Snagglefootcopper bonus pay for amazing work on #OSS 00010000350 +622231380104400231323124728460000000000#167348813058# Mason Taylor 1121042880000351 +705Mistresscalico bonus pay for amazing work on #OSS 00010000351 +622231380104136307387026031800000000000#080674326840# James Wilson 1121042880000352 +705Flyheather bonus pay for amazing work on #OSS 00010000352 +622231380104133744685316242630000000000#453846000811# Avery Johnson 1121042880000353 +705Boltcharm bonus pay for amazing work on #OSS 00010000353 +622231380104413500005710507080000000000#418263785640# Aubrey Taylor 1121042880000354 +705Touchgrave bonus pay for amazing work on #OSS 00010000354 +622231380104451072175746463730000000000#767848752671# Ethan Jackson 1121042880000355 +705Hideprism bonus pay for amazing work on #OSS 00010000355 +622231380104407235533610550070000000000#575477074288# Ava Smith 1121042880000356 +705Goosealder bonus pay for amazing work on #OSS 00010000356 +622231380104531568126155276720000000000#223551203374# Zoey Miller 1121042880000357 +705Mindjade bonus pay for amazing work on #OSS 00010000357 +622231380104367026814084443020000000000#883508406278# Elijah Martin 1121042880000358 +705Dartglimmer bonus pay for amazing work on #OSS 00010000358 +622231380104777314848673354170000000000#565168603551# Aiden Anderson 1121042880000359 +705Edgeemerald bonus pay for amazing work on #OSS 00010000359 +622231380104452852334023500730000000000#851330315314# Natalie Moore 1121042880000360 +705Chattertitanium bonus pay for amazing work on #OSS 00010000360 +622231380104823570732881706020000000000#380471741023# Alexander Johnson 1121042880000361 +705Bellypine bonus pay for amazing work on #OSS 00010000361 +622231380104048024832848583500000000000#436356364684# Michael Smith 1121042880000362 +705Beeatom bonus pay for amazing work on #OSS 00010000362 +622231380104767534166840573100000000000#250538631558# Chloe Garcia 1121042880000363 +705Bisonsolar bonus pay for amazing work on #OSS 00010000363 +622231380104087177056320555400000000000#772603735455# Joshua Brown 1121042880000364 +705Hornshade bonus pay for amazing work on #OSS 00010000364 +622231380104683064682176810250000000000#331570501682# Elijah Smith 1121042880000365 +705Facehazel bonus pay for amazing work on #OSS 00010000365 +622231380104554505503606358560000000000#611037528110# Ella Taylor 1121042880000366 +705Crafterspot bonus pay for amazing work on #OSS 00010000366 +622231380104464851827166232160000000000#321763852415# Daniel Martinez 1121042880000367 +705Mindzinc bonus pay for amazing work on #OSS 00010000367 +622231380104660805558880827850000000000#114214365843# Joseph White 1121042880000368 +705Bindercake bonus pay for amazing work on #OSS 00010000368 +622231380104642210520218211300000000000#803821133306# Aubrey Garcia 1121042880000369 +705Capgray bonus pay for amazing work on #OSS 00010000369 +622231380104051874665617020330000000000#476131517720# Isabella Jackson 1121042880000370 +705Choppercake bonus pay for amazing work on #OSS 00010000370 +622231380104280046755753443210000000000#237142608645# James Smith 1121042880000371 +705Lanternbald bonus pay for amazing work on #OSS 00010000371 +622231380104727507205334516150000000000#713704845266# Jacob Harris 1121042880000372 +705Stoneband bonus pay for amazing work on #OSS 00010000372 +622231380104822261408265144370000000000#244252740582# Ella Davis 1121042880000373 +705Chattermaze bonus pay for amazing work on #OSS 00010000373 +622231380104485414460783187820000000000#667827717020# Ella Thompson 1121042880000374 +705Owlflax bonus pay for amazing work on #OSS 00010000374 +622231380104537405475488437780000000000#637764468477# Andrew Thompson 1121042880000375 +705Kittennimble bonus pay for amazing work on #OSS 00010000375 +622231380104125884526717408330000000000#371442283653# Ethan Smith 1121042880000376 +705Jaguarcrazy bonus pay for amazing work on #OSS 00010000376 +622231380104623446358445072640000000000#541368088002# Sofia Davis 1121042880000377 +705Fangsugar bonus pay for amazing work on #OSS 00010000377 +622231380104425230116303403560000000000#065341888652# Olivia Brown 1121042880000378 +705Mastervanilla bonus pay for amazing work on #OSS 00010000378 +622231380104482265283163406360000000000#610250560884# Liam Taylor 1121042880000379 +705Collarpear bonus pay for amazing work on #OSS 00010000379 +622231380104357765028787231700000000000#175568601528# Noah Johnson 1121042880000380 +705Toesavage bonus pay for amazing work on #OSS 00010000380 +622231380104258731280776373410000000000#146670301434# Olivia Harris 1121042880000381 +705Bitebitter bonus pay for amazing work on #OSS 00010000381 +622231380104208333536707661130000000000#415131071258# Noah Thompson 1121042880000382 +705Flamewild bonus pay for amazing work on #OSS 00010000382 +622231380104240638278412725550000000000#188535177528# Joseph Robinson 1121042880000383 +705Stalkerhail bonus pay for amazing work on #OSS 00010000383 +622231380104521673060161827040000000000#530605016010# Elijah Davis 1121042880000384 +705Thorntwisty bonus pay for amazing work on #OSS 00010000384 +622231380104183268155810088300000000000#486084468675# Michael Davis 1121042880000385 +705Keepercrimson bonus pay for amazing work on #OSS 00010000385 +622231380104606726817565180440000000000#835703587486# Charlotte Miller 1121042880000386 +705Gargoylekiwi bonus pay for amazing work on #OSS 00010000386 +622231380104544375072850251180000000000#647430080063# Addison Miller 1121042880000387 +705Bardcrystal bonus pay for amazing work on #OSS 00010000387 +622231380104353614463676747210000000000#837374241461# Zoey Davis 1121042880000388 +705Jestersour bonus pay for amazing work on #OSS 00010000388 +622231380104315462573153227440000000000#277863187007# Sophia Taylor 1121042880000389 +705Sentrywood bonus pay for amazing work on #OSS 00010000389 +622231380104373244756858277060000000000#458450067424# Ava Thompson 1121042880000390 +705Hunterpond bonus pay for amazing work on #OSS 00010000390 +622231380104110614008556521320000000000#777621524126# Lily White 1121042880000391 +705Songskitter bonus pay for amazing work on #OSS 00010000391 +622231380104665052186538337550000000000#386317108233# Noah Anderson 1121042880000392 +705Chillchocolate bonus pay for amazing work on #OSS 00010000392 +622231380104561170006441225810000000000#184442367155# Mason Jones 1121042880000393 +705Stalkerprong bonus pay for amazing work on #OSS 00010000393 +622231380104078016188642177860000000000#243561786807# Natalie Garcia 1121042880000394 +705Thornsquare bonus pay for amazing work on #OSS 00010000394 +622231380104282567885817260580000000000#613088663330# Avery Garcia 1121042880000395 +705Flameweed bonus pay for amazing work on #OSS 00010000395 +622231380104612237122244203560000000000#111414020788# Aubrey Thomas 1121042880000396 +705Leaderround bonus pay for amazing work on #OSS 00010000396 +622231380104051118852754002620000000000#713571300531# Anthony Martin 1121042880000397 +705Jaguarsly bonus pay for amazing work on #OSS 00010000397 +622231380104165207374385500780000000000#826381268845# Isabella Moore 1121042880000398 +705Gorillavolcano bonus pay for amazing work on #OSS 00010000398 +622231380104571112434665475250000000000#538425842314# Zoey White 1121042880000399 +705Wolverinemesquite bonus pay for amazing work on #OSS 00010000399 +622231380104263177800107173680000000000#028540048711# Joseph Brown 1121042880000400 +705Grabberspeckle bonus pay for amazing work on #OSS 00010000400 +622231380104107242108356077230000000000#171663766425# Anthony Moore 1121042880000401 +705Playerdisco bonus pay for amazing work on #OSS 00010000401 +622231380104588418302365826610000000000#343557562668# Avery Martinez 1121042880000402 +705Bowcrocus bonus pay for amazing work on #OSS 00010000402 +622231380104354643280244576580000000000#462231036074# Elijah Martin 1121042880000403 +705Lanterndesert bonus pay for amazing work on #OSS 00010000403 +622231380104067211625368604520000000000#381064678012# Lily Martinez 1121042880000404 +705Huggerbramble bonus pay for amazing work on #OSS 00010000404 +622231380104154611363886724030000000000#277821775141# David Martinez 1121042880000405 +705Markdust bonus pay for amazing work on #OSS 00010000405 +622231380104727166184374860700000000000#018703843028# Elizabeth Taylor 1121042880000406 +705Edgesoft bonus pay for amazing work on #OSS 00010000406 +622231380104718372581458114000000000000#628630476160# Isabella Thomas 1121042880000407 +705Hunterglaze bonus pay for amazing work on #OSS 00010000407 +622231380104872728362777745040000000000#134742550024# Anthony Jackson 1121042880000408 +705Crystalpineapple bonus pay for amazing work on #OSS 00010000408 +622231380104238461851702827330000000000#603726400201# David Smith 1121042880000409 +705Pythonwax bonus pay for amazing work on #OSS 00010000409 +622231380104515068147625870760000000000#854247305303# Jayden Martinez 1121042880000410 +705Vulturephase bonus pay for amazing work on #OSS 00010000410 +622231380104200276844710403210000000000#768707708877# Sofia Johnson 1121042880000411 +705Scalelong bonus pay for amazing work on #OSS 00010000411 +622231380104404770866256156580000000000#423524402687# Avery White 1121042880000412 +705Markmire bonus pay for amazing work on #OSS 00010000412 +622231380104524522236238170200000000000#571541848570# Joshua Miller 1121042880000413 +705Ladystump bonus pay for amazing work on #OSS 00010000413 +622231380104571578572153328560000000000#302578031644# Ethan Harris 1121042880000414 +705Fighterblossom bonus pay for amazing work on #OSS 00010000414 +622231380104543302203620203780000000000#654268361340# Madison Moore 1121042880000415 +705Slothhill bonus pay for amazing work on #OSS 00010000415 +622231380104271424768717724870000000000#868738777303# Zoey Williams 1121042880000416 +705Runnertiny bonus pay for amazing work on #OSS 00010000416 +622231380104216256467428636100000000000#043673680456# Ethan Jones 1121042880000417 +705Runnermangrove bonus pay for amazing work on #OSS 00010000417 +622231380104176612372236621330000000000#477311731761# Addison Williams 1121042880000418 +705Roarervivid bonus pay for amazing work on #OSS 00010000418 +622231380104151000058648413270000000000#573054404813# Ella Martinez 1121042880000419 +705Falconjasper bonus pay for amazing work on #OSS 00010000419 +622231380104883427487811550320000000000#148637610340# Charlotte Johnson 1121042880000420 +705Cranecarnelian bonus pay for amazing work on #OSS 00010000420 +622231380104278674626415512750000000000#281704102568# Ethan Anderson 1121042880000421 +705Ringersavage bonus pay for amazing work on #OSS 00010000421 +622231380104572213182740113240000000000#482615282700# Daniel Martin 1121042880000422 +705Horsedisco bonus pay for amazing work on #OSS 00010000422 +622231380104504835452286456240000000000#807551306604# Avery Thompson 1121042880000423 +705Stingerfossil bonus pay for amazing work on #OSS 00010000423 +622231380104127760387473356000000000000#188360711260# Liam Garcia 1121042880000424 +705Mistressgrove bonus pay for amazing work on #OSS 00010000424 +622231380104148556677060244850000000000#810105282503# Anthony Garcia 1121042880000425 +705Singerclover bonus pay for amazing work on #OSS 00010000425 +622231380104505061037662517630000000000#030731701178# Charlotte Harris 1121042880000426 +705Keeperdisco bonus pay for amazing work on #OSS 00010000426 +622231380104204227253182387150000000000#764785065205# Benjamin Martinez 1121042880000427 +705Flierlace bonus pay for amazing work on #OSS 00010000427 +622231380104015011732063847470000000000#657522704357# Mia Johnson 1121042880000428 +705Cowlperidot bonus pay for amazing work on #OSS 00010000428 +622231380104557425712736847540000000000#371277754125# Natalie Martinez 1121042880000429 +705Huggervivid bonus pay for amazing work on #OSS 00010000429 +622231380104248733355172233150000000000#811001602425# Emily Robinson 1121042880000430 +705Storksky bonus pay for amazing work on #OSS 00010000430 +622231380104271418613520810100000000000#151878326267# Daniel Wilson 1121042880000431 +705Bardglow bonus pay for amazing work on #OSS 00010000431 +622231380104005553230025400700000000000#102772805064# Emily Martinez 1121042880000432 +705Antlerdaffodil bonus pay for amazing work on #OSS 00010000432 +622231380104340708244576211800000000000#018240581830# Michael Anderson 1121042880000433 +705Oriolegiant bonus pay for amazing work on #OSS 00010000433 +622231380104380710174702308780000000000#188105224887# Joseph Anderson 1121042880000434 +705Crownfish bonus pay for amazing work on #OSS 00010000434 +622231380104160253127522583620000000000#242085553867# Anthony Thomas 1121042880000435 +705Knifepine bonus pay for amazing work on #OSS 00010000435 +622231380104586441086870325800000000000#702650557053# Michael Smith 1121042880000436 +705Scalerhinestone bonus pay for amazing work on #OSS 00010000436 +622231380104208724782850018280000000000#357468551080# Ethan Williams 1121042880000437 +705Noseflower bonus pay for amazing work on #OSS 00010000437 +622231380104326364650485242050000000000#302442825782# Aiden Jackson 1121042880000438 +705Masterzest bonus pay for amazing work on #OSS 00010000438 +622231380104201623737401453680000000000#652603200766# Addison Taylor 1121042880000439 +705Seeramber bonus pay for amazing work on #OSS 00010000439 +622231380104148446164813412010000000000#336650068680# Joshua Johnson 1121042880000440 +705Shakerroot bonus pay for amazing work on #OSS 00010000440 +622231380104035862135323377210000000000#880016645850# Chloe Miller 1121042880000441 +705Foeatom bonus pay for amazing work on #OSS 00010000441 +622231380104266840742173213310000000000#008770622632# Jayden Thomas 1121042880000442 +705Tailrune bonus pay for amazing work on #OSS 00010000442 +622231380104636056083063742110000000000#144408687888# Zoey Williams 1121042880000443 +705Birdperidot bonus pay for amazing work on #OSS 00010000443 +622231380104425816410178148720000000000#573668254404# Elijah White 1121042880000444 +705Howlerpeach bonus pay for amazing work on #OSS 00010000444 +622231380104354265607563013780000000000#352871153480# Mia Robinson 1121042880000445 +705Shoulderblossom bonus pay for amazing work on #OSS 00010000445 +622231380104447502512100283140000000000#760427068262# Chloe Robinson 1121042880000446 +705Painterlime bonus pay for amazing work on #OSS 00010000446 +622231380104135008466675781370000000000#555514162204# Michael Miller 1121042880000447 +705Jaycalico bonus pay for amazing work on #OSS 00010000447 +622231380104885502633764133740000000000#170358067346# Charlotte Anderson 1121042880000448 +705Ladydent bonus pay for amazing work on #OSS 00010000448 +622231380104347706548761818550000000000#660075607260# Ethan Thomas 1121042880000449 +705Walkerstream bonus pay for amazing work on #OSS 00010000449 +622231380104132203722157467840000000000#116642621105# Alexander Jones 1121042880000450 +705Hareweed bonus pay for amazing work on #OSS 00010000450 +622231380104203424022130473360000000000#888343047760# Zoey Smith 1121042880000451 +705Hairpeat bonus pay for amazing work on #OSS 00010000451 +622231380104121630416710247240000000000#107043821216# Addison Jackson 1121042880000452 +705Carvermuck bonus pay for amazing work on #OSS 00010000452 +622231380104081332775668264160000000000#528528572200# Emma Brown 1121042880000453 +705Llamaplum bonus pay for amazing work on #OSS 00010000453 +622231380104330808888672183660000000000#004871736165# Noah Jones 1121042880000454 +705Thumbpine bonus pay for amazing work on #OSS 00010000454 +622231380104738532578757118110000000000#856615448841# James Martinez 1121042880000455 +705Stingerbush bonus pay for amazing work on #OSS 00010000455 +622231380104824248534485046850000000000#507030852648# Ella Martinez 1121042880000456 +705Daggerlove bonus pay for amazing work on #OSS 00010000456 +622231380104262441306300004070000000000#108715180301# Madison Davis 1121042880000457 +705Gamblerplume bonus pay for amazing work on #OSS 00010000457 +622231380104866884530305662610000000000#700645385652# Elijah Davis 1121042880000458 +705Runnertime bonus pay for amazing work on #OSS 00010000458 +622231380104133816687365806560000000000#277456256817# Daniel Moore 1121042880000459 +705Voicecold bonus pay for amazing work on #OSS 00010000459 +622231380104630715165823886500000000000#571116484088# Michael Robinson 1121042880000460 +705Ringerbrook bonus pay for amazing work on #OSS 00010000460 +622231380104585807774715745040000000000#025348106173# Emma Smith 1121042880000461 +705Thunderpie bonus pay for amazing work on #OSS 00010000461 +622231380104138455101867527180000000000#411443650136# Emma Harris 1121042880000462 +705Lancerroad bonus pay for amazing work on #OSS 00010000462 +622231380104363508322173637180000000000#127181660164# Mia Moore 1121042880000463 +705Riderboom bonus pay for amazing work on #OSS 00010000463 +622231380104876826048731474560000000000#011633207776# Ethan Brown 1121042880000464 +705Biterroot bonus pay for amazing work on #OSS 00010000464 +622231380104341625034133658880000000000#544457025374# William Williams 1121042880000465 +705Racertime bonus pay for amazing work on #OSS 00010000465 +622231380104161287358607125680000000000#608072047772# Mason Harris 1121042880000466 +705Pantherspring bonus pay for amazing work on #OSS 00010000466 +622231380104037036673310831680000000000#671371368223# Elizabeth White 1121042880000467 +705Hyenadenim bonus pay for amazing work on #OSS 00010000467 +622231380104073325616226518130000000000#132832571180# Jayden Taylor 1121042880000468 +705Crowgarnet bonus pay for amazing work on #OSS 00010000468 +622231380104873541015256374580000000000#787673565476# Elijah Thomas 1121042880000469 +705Knightglimmer bonus pay for amazing work on #OSS 00010000469 +622231380104348573375630065000000000000#504104073246# Aubrey White 1121042880000470 +705Hornetseason bonus pay for amazing work on #OSS 00010000470 +622231380104877354411885826870000000000#842766772142# Anthony White 1121042880000471 +705Daggercactus bonus pay for amazing work on #OSS 00010000471 +622231380104504063713884570410000000000#851033404368# Lily Martin 1121042880000472 +705Craneshard bonus pay for amazing work on #OSS 00010000472 +622231380104554066140138620250000000000#416045420131# Ava Johnson 1121042880000473 +705Kangaroocurse bonus pay for amazing work on #OSS 00010000473 +622231380104728404167274220510000000000#867215765163# Madison Taylor 1121042880000474 +705Toothwood bonus pay for amazing work on #OSS 00010000474 +622231380104134780533386865030000000000#250721230408# Isabella Robinson 1121042880000475 +705Kickersheer bonus pay for amazing work on #OSS 00010000475 +622231380104685365404021740200000000000#568088055011# Noah Smith 1121042880000476 +705Minnowmud bonus pay for amazing work on #OSS 00010000476 +622231380104214768755415457730000000000#034023411084# Natalie Miller 1121042880000477 +705Pigveil bonus pay for amazing work on #OSS 00010000477 +622231380104852003122005584320000000000#504131337878# Charlotte Wilson 1121042880000478 +705Shriekernavy bonus pay for amazing work on #OSS 00010000478 +622231380104477451534883660850000000000#425720584107# Charlotte Jones 1121042880000479 +705Shirtdull bonus pay for amazing work on #OSS 00010000479 +622231380104247418667600426230000000000#616755835274# Mason Williams 1121042880000480 +705Elfspeckle bonus pay for amazing work on #OSS 00010000480 +622231380104658274486042452630000000000#315538337074# Avery Robinson 1121042880000481 +705Chinhazel bonus pay for amazing work on #OSS 00010000481 +622231380104124317343711551060000000000#251224644111# Olivia Thomas 1121042880000482 +705Doomsugar bonus pay for amazing work on #OSS 00010000482 +622231380104745065118438212430000000000#157212155153# Elijah Harris 1121042880000483 +705Daggernebula bonus pay for amazing work on #OSS 00010000483 +622231380104047173237067001600000000000#352521063827# Mia Jones 1121042880000484 +705Hidegem bonus pay for amazing work on #OSS 00010000484 +622231380104481151253106204170000000000#677157387620# Aiden Taylor 1121042880000485 +705Thundercoal bonus pay for amazing work on #OSS 00010000485 +622231380104227285168653851430000000000#812061061265# Zoey Brown 1121042880000486 +705Scorpionblack bonus pay for amazing work on #OSS 00010000486 +622231380104103783826170623560000000000#681480203881# Mason Wilson 1121042880000487 +705Legendwest bonus pay for amazing work on #OSS 00010000487 +622231380104566223861262420070000000000#168046284730# Elizabeth Taylor 1121042880000488 +705Weedbush bonus pay for amazing work on #OSS 00010000488 +622231380104004443377302535830000000000#374488400560# Ella Wilson 1121042880000489 +705Beakshell bonus pay for amazing work on #OSS 00010000489 +622231380104665554575057684540000000000#172745145011# Aiden Taylor 1121042880000490 +705Voiceprickle bonus pay for amazing work on #OSS 00010000490 +622231380104052504726583323000000000000#037447606504# Lily Brown 1121042880000491 +705Chillmetal bonus pay for amazing work on #OSS 00010000491 +622231380104550807403853126600000000000#145661320746# Addison Taylor 1121042880000492 +705Carpshade bonus pay for amazing work on #OSS 00010000492 +622231380104638148241825702100000000000#157340831731# Lily Garcia 1121042880000493 +705Bisonwalnut bonus pay for amazing work on #OSS 00010000493 +622231380104420866635768340060000000000#370038715560# Jayden Thomas 1121042880000494 +705Otterlegend bonus pay for amazing work on #OSS 00010000494 +622231380104876865637580683310000000000#703727373000# Aubrey Jackson 1121042880000495 +705Eaglesequoia bonus pay for amazing work on #OSS 00010000495 +622231380104038285572778241300000000000#083843688064# Andrew Jackson 1121042880000496 +705Burntide bonus pay for amazing work on #OSS 00010000496 +622231380104882441467252423580000000000#337073248326# Ava White 1121042880000497 +705Marerampant bonus pay for amazing work on #OSS 00010000497 +622231380104766728254448620000000000000#337653646145# Jacob Williams 1121042880000498 +705Carpplum bonus pay for amazing work on #OSS 00010000498 +622231380104740276140750035720000000000#743366283164# Andrew Anderson 1121042880000499 +705Hawkshard bonus pay for amazing work on #OSS 00010000499 +622231380104124127335580580780000000000#404007624106# Ethan Anderson 1121042880000500 +705Toothcactus bonus pay for amazing work on #OSS 00010000500 +622231380104431515703074642760000000000#114767024185# Anthony Robinson 1121042880000501 +705Guardianpurple bonus pay for amazing work on #OSS 00010000501 +622231380104263175835381645160000000000#616326375802# Sofia Garcia 1121042880000502 +705Bearfork bonus pay for amazing work on #OSS 00010000502 +622231380104602148153335001800000000000#370883233666# Liam Thomas 1121042880000503 +705Gemmidnight bonus pay for amazing work on #OSS 00010000503 +622231380104767781151658735510000000000#624572823808# Liam Jones 1121042880000504 +705Thornflame bonus pay for amazing work on #OSS 00010000504 +622231380104765131373886663550000000000#285673370372# Andrew Smith 1121042880000505 +705Shriekplanet bonus pay for amazing work on #OSS 00010000505 +622231380104132104387366732720000000000#221515235163# Daniel Garcia 1121042880000506 +705Finmint bonus pay for amazing work on #OSS 00010000506 +622231380104277435444888111720000000000#553553305038# Zoey White 1121042880000507 +705Hyenacarnation bonus pay for amazing work on #OSS 00010000507 +622231380104266742153808152010000000000#163508508104# Benjamin Garcia 1121042880000508 +705Ripperazure bonus pay for amazing work on #OSS 00010000508 +622231380104485615611255327030000000000#741613073636# Lily Williams 1121042880000509 +705Beaktopaz bonus pay for amazing work on #OSS 00010000509 +622231380104785706142842587410000000000#104444466851# Matthew Robinson 1121042880000510 +705Flasherbrass bonus pay for amazing work on #OSS 00010000510 +622231380104451754562783878800000000000#636546548565# Michael Johnson 1121042880000511 +705Beetlecord bonus pay for amazing work on #OSS 00010000511 +622231380104165748528688718460000000000#488414516084# Aubrey Johnson 1121042880000512 +705Ocelottwilight bonus pay for amazing work on #OSS 00010000512 +622231380104200257051485045540000000000#671748130338# Ava Thomas 1121042880000513 +705Mythbog bonus pay for amazing work on #OSS 00010000513 +622231380104774828765420425120000000000#877284050467# Elijah Harris 1121042880000514 +705Marealder bonus pay for amazing work on #OSS 00010000514 +622231380104538553337136776280000000000#713115172728# Ethan Martinez 1121042880000515 +705Vultureprong bonus pay for amazing work on #OSS 00010000515 +622231380104604766810732845270000000000#475444504675# Sofia Harris 1121042880000516 +705Boarvolcano bonus pay for amazing work on #OSS 00010000516 +622231380104571450744240715630000000000#417270821744# Benjamin Moore 1121042880000517 +705Griffinring bonus pay for amazing work on #OSS 00010000517 +622231380104700153574518018610000000000#410132004212# Andrew Jones 1121042880000518 +705Servantglow bonus pay for amazing work on #OSS 00010000518 +622231380104381846848148733380000000000#365876400408# Anthony Garcia 1121042880000519 +705Toothtar bonus pay for amazing work on #OSS 00010000519 +622231380104252186537740288260000000000#105056233635# Avery Jackson 1121042880000520 +705Burndestiny bonus pay for amazing work on #OSS 00010000520 +622231380104462224231536121000000000000#038478685771# Jayden Jones 1121042880000521 +705Grinplanet bonus pay for amazing work on #OSS 00010000521 +622231380104363157565183603050000000000#731662442577# Avery Miller 1121042880000522 +705Bugsummer bonus pay for amazing work on #OSS 00010000522 +622231380104634840165573407830000000000#672054585675# Mason Taylor 1121042880000523 +705Shielddisco bonus pay for amazing work on #OSS 00010000523 +622231380104711561757637763410000000000#252070258445# Michael Miller 1121042880000524 +705Seekergossamer bonus pay for amazing work on #OSS 00010000524 +622231380104841000556107078240000000000#658554375583# Chloe Moore 1121042880000525 +705Ferretchisel bonus pay for amazing work on #OSS 00010000525 +622231380104088027552887070480000000000#124612852352# Mason Robinson 1121042880000526 +705Coyoteswift bonus pay for amazing work on #OSS 00010000526 +622231380104804778504417615330000000000#177532552653# Joseph Garcia 1121042880000527 +705Grinmirror bonus pay for amazing work on #OSS 00010000527 +622231380104406538318373423850000000000#385270826334# Jayden Anderson 1121042880000528 +705Eaterhill bonus pay for amazing work on #OSS 00010000528 +622231380104643870363875544870000000000#001182832837# Alexander Smith 1121042880000529 +705Parrotribbon bonus pay for amazing work on #OSS 00010000529 +622231380104732655757500820380000000000#033244530428# Isabella Brown 1121042880000530 +705Jawcoral bonus pay for amazing work on #OSS 00010000530 +622231380104528223607628215550000000000#608442268027# Ella Johnson 1121042880000531 +705Huntermirage bonus pay for amazing work on #OSS 00010000531 +622231380104843048671727557220000000000#007167348582# Ava Thomas 1121042880000532 +705Swoopdandelion bonus pay for amazing work on #OSS 00010000532 +622231380104015036530351125840000000000#130721136442# Anthony Williams 1121042880000533 +705Weaverpalm bonus pay for amazing work on #OSS 00010000533 +622231380104553734572132775200000000000#748650262756# Matthew Garcia 1121042880000534 +705Glassseed bonus pay for amazing work on #OSS 00010000534 +622231380104676278486535765470000000000#343713176136# Ava White 1121042880000535 +705Carpcyan bonus pay for amazing work on #OSS 00010000535 +622231380104550582600636021270000000000#160624340085# Emma Martin 1121042880000536 +705Spikeiris bonus pay for amazing work on #OSS 00010000536 +622231380104334652228733427460000000000#748030080351# Mason Johnson 1121042880000537 +705Crusherjet bonus pay for amazing work on #OSS 00010000537 +622231380104726536686515452080000000000#848823172334# Sofia Moore 1121042880000538 +705Scowlbrown bonus pay for amazing work on #OSS 00010000538 +622231380104112777630148077510000000000#653743382617# Joshua Taylor 1121042880000539 +705Watcherdiamond bonus pay for amazing work on #OSS 00010000539 +622231380104330148231470324320000000000#507644070120# Jayden Johnson 1121042880000540 +705Tonguefierce bonus pay for amazing work on #OSS 00010000540 +622231380104717777200313136250000000000#354001328146# Noah Brown 1121042880000541 +705Heronash bonus pay for amazing work on #OSS 00010000541 +622231380104065863551570622530000000000#387332336325# Liam Jones 1121042880000542 +705Goatfortune bonus pay for amazing work on #OSS 00010000542 +622231380104234621533418378650000000000#502053208355# Olivia Garcia 1121042880000543 +705Heroblossom bonus pay for amazing work on #OSS 00010000543 +622231380104815302886506315750000000000#372706441848# Ethan Wilson 1121042880000544 +705Ringerhollow bonus pay for amazing work on #OSS 00010000544 +622231380104284615317206684710000000000#146708357706# Jacob Thomas 1121042880000545 +705Wandererflash bonus pay for amazing work on #OSS 00010000545 +622231380104826448726381217230000000000#220048038132# Lily Williams 1121042880000546 +705Cranemire bonus pay for amazing work on #OSS 00010000546 +622231380104174712338382656450000000000#662512083410# Andrew Miller 1121042880000547 +705Crafterphantom bonus pay for amazing work on #OSS 00010000547 +622231380104574426368420024850000000000#403754628065# Chloe Moore 1121042880000548 +705Dukeviolet bonus pay for amazing work on #OSS 00010000548 +622231380104810105868230282020000000000#583482850720# Michael Smith 1121042880000549 +705Rovermalachite bonus pay for amazing work on #OSS 00010000549 +622231380104307121021208436100000000000#441747436435# Anthony Miller 1121042880000550 +705Llamablaze bonus pay for amazing work on #OSS 00010000550 +622231380104801667725504566210000000000#268305365551# Elijah Robinson 1121042880000551 +705Foxflannel bonus pay for amazing work on #OSS 00010000551 +622231380104588751022827588150000000000#264754578426# Sophia Wilson 1121042880000552 +705Scowlancient bonus pay for amazing work on #OSS 00010000552 +622231380104087841407506511100000000000#618467827024# Jacob Jones 1121042880000553 +705Jaguarlead bonus pay for amazing work on #OSS 00010000553 +622231380104562415263730261710000000000#524868337742# Mia Miller 1121042880000554 +705Loonsoft bonus pay for amazing work on #OSS 00010000554 +622231380104111424280540326470000000000#863475116401# Jayden Johnson 1121042880000555 +705Shriekerluminous bonus pay for amazing work on #OSS 00010000555 +622231380104422148125115165120000000000#427311232713# Ethan Martinez 1121042880000556 +705Musecookie bonus pay for amazing work on #OSS 00010000556 +622231380104726863560672664420000000000#356133163680# Abigail Thompson 1121042880000557 +705Giverfire bonus pay for amazing work on #OSS 00010000557 +622231380104022765767587225180000000000#837583023018# Ethan Martin 1121042880000558 +705Legcurse bonus pay for amazing work on #OSS 00010000558 +622231380104340840367074845350000000000#200151675300# Mia Robinson 1121042880000559 +705Scribemeteor bonus pay for amazing work on #OSS 00010000559 +622231380104080542244258713300000000000#210428463770# Ava Thomas 1121042880000560 +705Hidepollen bonus pay for amazing work on #OSS 00010000560 +622231380104286605680332713550000000000#367127401587# Mia Miller 1121042880000561 +705Centaurspice bonus pay for amazing work on #OSS 00010000561 +622231380104141311020067621230000000000#030110354331# Michael Garcia 1121042880000562 +705Donkeyblack bonus pay for amazing work on #OSS 00010000562 +622231380104152271817852544840000000000#802302484123# Benjamin Wilson 1121042880000563 +705Sentryobsidian bonus pay for amazing work on #OSS 00010000563 +622231380104456604427576121440000000000#576866742170# Aiden Jones 1121042880000564 +705Carverbrave bonus pay for amazing work on #OSS 00010000564 +622231380104041022437108100780000000000#488542115701# Lily White 1121042880000565 +705Stonecherry bonus pay for amazing work on #OSS 00010000565 +622231380104564884620048774670000000000#585263673834# Zoey Moore 1121042880000566 +705Questercactus bonus pay for amazing work on #OSS 00010000566 +622231380104366003641433262170000000000#720275083208# James Wilson 1121042880000567 +705Falconwind bonus pay for amazing work on #OSS 00010000567 +622231380104878215865643312620000000000#503412717624# William Thompson 1121042880000568 +705Skullnavy bonus pay for amazing work on #OSS 00010000568 +622231380104656450283616383080000000000#870151042564# Chloe Williams 1121042880000569 +705Princessvivid bonus pay for amazing work on #OSS 00010000569 +622231380104163337442208168040000000000#213320441867# Liam White 1121042880000570 +705Wolfmalachite bonus pay for amazing work on #OSS 00010000570 +622231380104566654127412200740000000000#511736384771# Charlotte Smith 1121042880000571 +705Roarmica bonus pay for amazing work on #OSS 00010000571 +622231380104775757007314638700000000000#710742531381# Aubrey Thompson 1121042880000572 +705Raverthread bonus pay for amazing work on #OSS 00010000572 +622231380104703540133477516130000000000#770016308223# Sofia Robinson 1121042880000573 +705Snapperplatinum bonus pay for amazing work on #OSS 00010000573 +622231380104832754334076755250000000000#784215510244# Abigail White 1121042880000574 +705Seerstream bonus pay for amazing work on #OSS 00010000574 +622231380104737117185324088400000000000#084261611654# Michael Miller 1121042880000575 +705Marefluff bonus pay for amazing work on #OSS 00010000575 +622231380104654351532765768140000000000#442141767161# James Williams 1121042880000576 +705Pantherhoney bonus pay for amazing work on #OSS 00010000576 +622231380104184704678323567780000000000#578741472285# Aubrey Thompson 1121042880000577 +705Throatlead bonus pay for amazing work on #OSS 00010000577 +622231380104517177870467673830000000000#226487706142# Natalie White 1121042880000578 +705Stalliongarnet bonus pay for amazing work on #OSS 00010000578 +622231380104628660704085185050000000000#602083832388# Zoey Davis 1121042880000579 +705Rayalpine bonus pay for amazing work on #OSS 00010000579 +622231380104660213035550461230000000000#362012767122# Jacob Jackson 1121042880000580 +705Lanternshy bonus pay for amazing work on #OSS 00010000580 +622231380104655235370155310280000000000#870552155618# Abigail Jackson 1121042880000581 +705Turnersilver bonus pay for amazing work on #OSS 00010000581 +622231380104471884378580636870000000000#680468167714# Liam White 1121042880000582 +705Napespeckle bonus pay for amazing work on #OSS 00010000582 +622231380104772178555002417700000000000#722688852015# Emma Johnson 1121042880000583 +705Snagglefootbone bonus pay for amazing work on #OSS 00010000583 +622231380104526403478532402640000000000#447175454005# Sofia White 1121042880000584 +705Pawgrove bonus pay for amazing work on #OSS 00010000584 +622231380104285070580826001330000000000#252450272222# Chloe Davis 1121042880000585 +705Queenspiral bonus pay for amazing work on #OSS 00010000585 +622231380104047635240837734580000000000#200055555103# Andrew Davis 1121042880000586 +705Fingertyphoon bonus pay for amazing work on #OSS 00010000586 +622231380104711572854274107620000000000#724336048140# James Garcia 1121042880000587 +705Carveroil bonus pay for amazing work on #OSS 00010000587 +622231380104520768844153155110000000000#301822651328# Daniel Martinez 1121042880000588 +705Scorpioncurly bonus pay for amazing work on #OSS 00010000588 +622231380104875731130835157430000000000#143171688718# Natalie Garcia 1121042880000589 +705Takerdestiny bonus pay for amazing work on #OSS 00010000589 +622231380104873560828767246540000000000#451685810744# James Johnson 1121042880000590 +705Owlcharm bonus pay for amazing work on #OSS 00010000590 +622231380104223743266685506740000000000#615848113842# Elijah Moore 1121042880000591 +705Bisonvenom bonus pay for amazing work on #OSS 00010000591 +622231380104232758031446553660000000000#708760284015# Elijah Brown 1121042880000592 +705Warlockcream bonus pay for amazing work on #OSS 00010000592 +622231380104286618272038234220000000000#534846335273# Michael Taylor 1121042880000593 +705Riderbloom bonus pay for amazing work on #OSS 00010000593 +622231380104840442484555176230000000000#051726160348# Ella Williams 1121042880000594 +705Fighterspring bonus pay for amazing work on #OSS 00010000594 +622231380104427026538735631350000000000#855202471360# Jacob Martinez 1121042880000595 +705Sageplanet bonus pay for amazing work on #OSS 00010000595 +622231380104886660420588864310000000000#031647144138# Elizabeth Moore 1121042880000596 +705Whalefree bonus pay for amazing work on #OSS 00010000596 +622231380104638401740228762740000000000#003211552436# Avery Anderson 1121042880000597 +705Warriorblue bonus pay for amazing work on #OSS 00010000597 +622231380104063862355878082040000000000#341802323244# Anthony Garcia 1121042880000598 +705Killeralder bonus pay for amazing work on #OSS 00010000598 +622231380104275238512172633500000000000#314340853245# Sofia Thomas 1121042880000599 +705Bunnymisty bonus pay for amazing work on #OSS 00010000599 +622231380104604736722557226220000000000#733383476017# Matthew Martin 1121042880000600 +705Shriekerpuzzle bonus pay for amazing work on #OSS 00010000600 +622231380104473472602750055200000000000#031605243826# Ava Jones 1121042880000601 +705Killerweak bonus pay for amazing work on #OSS 00010000601 +622231380104383806706126050300000000000#286585605302# Joseph Moore 1121042880000602 +705Viperpitch bonus pay for amazing work on #OSS 00010000602 +622231380104687171176080437140000000000#202205613470# Mia Thompson 1121042880000603 +705Ladyballistic bonus pay for amazing work on #OSS 00010000603 +622231380104384012013736601480000000000#877811448606# Chloe Davis 1121042880000604 +705Leopardblossom bonus pay for amazing work on #OSS 00010000604 +622231380104424104285732781340000000000#615845225453# Aiden Jones 1121042880000605 +705Keepersnapdragon bonus pay for amazing work on #OSS 00010000605 +622231380104135270220814726830000000000#621245108578# Abigail Martin 1121042880000606 +705Knightbranch bonus pay for amazing work on #OSS 00010000606 +622231380104582883434870673100000000000#647143707773# Abigail Martin 1121042880000607 +705Lightninglinen bonus pay for amazing work on #OSS 00010000607 +622231380104433320542824165550000000000#456101156642# William Wilson 1121042880000608 +705Cranelily bonus pay for amazing work on #OSS 00010000608 +622231380104083728450246357240000000000#837466326334# Addison Anderson 1121042880000609 +705Catfire bonus pay for amazing work on #OSS 00010000609 +622231380104023611722152243460000000000#525138163825# Andrew Davis 1121042880000610 +705Molezircon bonus pay for amazing work on #OSS 00010000610 +622231380104206456847053840730000000000#566143031665# Charlotte Williams 1121042880000611 +705Unicornchip bonus pay for amazing work on #OSS 00010000611 +622231380104833704351606761620000000000#406287748382# Sofia Williams 1121042880000612 +705Singerflame bonus pay for amazing work on #OSS 00010000612 +622231380104478224836725511710000000000#174383068785# Daniel Taylor 1121042880000613 +705Pythonberry bonus pay for amazing work on #OSS 00010000613 +622231380104800806750278748640000000000#863868008741# Isabella Martinez 1121042880000614 +705Roachheather bonus pay for amazing work on #OSS 00010000614 +622231380104131661163718588340000000000#574610663206# William Robinson 1121042880000615 +705Elkbow bonus pay for amazing work on #OSS 00010000615 +622231380104427831027051326350000000000#002407050878# Emma Martinez 1121042880000616 +705Handlove bonus pay for amazing work on #OSS 00010000616 +622231380104831727685257307550000000000#354707015076# James Johnson 1121042880000617 +705Ridershine bonus pay for amazing work on #OSS 00010000617 +622231380104535726045042763640000000000#307040043813# Liam Garcia 1121042880000618 +705Runnerlavender bonus pay for amazing work on #OSS 00010000618 +622231380104416147133734666860000000000#007364637736# Sophia Taylor 1121042880000619 +705Ninjaoasis bonus pay for amazing work on #OSS 00010000619 +622231380104847787382521543760000000000#354674206540# Mia Martin 1121042880000620 +705Geckocarnelian bonus pay for amazing work on #OSS 00010000620 +622231380104006224156558388060000000000#133738030181# Chloe Brown 1121042880000621 +705Koalashell bonus pay for amazing work on #OSS 00010000621 +622231380104245352656532456680000000000#048136164100# Sophia Davis 1121042880000622 +705Craftersoft bonus pay for amazing work on #OSS 00010000622 +622231380104164750031132455010000000000#760248865186# Ella Moore 1121042880000623 +705Stormlove bonus pay for amazing work on #OSS 00010000623 +622231380104510641345337323530000000000#467745342017# Andrew Anderson 1121042880000624 +705Kangarooflax bonus pay for amazing work on #OSS 00010000624 +622231380104324846223745362770000000000#541833771220# Olivia Jackson 1121042880000625 +705Scorpionsprinkle bonus pay for amazing work on #OSS 00010000625 +622231380104476782074306878570000000000#811014650357# Matthew Robinson 1121042880000626 +705Turnerfancy bonus pay for amazing work on #OSS 00010000626 +622231380104820572822283027170000000000#284163386521# Alexander Harris 1121042880000627 +705Tradermica bonus pay for amazing work on #OSS 00010000627 +622231380104344023617322361400000000000#175306057514# Sophia Davis 1121042880000628 +705Trackertopaz bonus pay for amazing work on #OSS 00010000628 +622231380104312586207536811060000000000#117554872503# Emma Williams 1121042880000629 +705Llamatabby bonus pay for amazing work on #OSS 00010000629 +622231380104075667033884201450000000000#745286767222# William Moore 1121042880000630 +705Stealercurse bonus pay for amazing work on #OSS 00010000630 +622231380104602525642606384660000000000#716108612524# James Smith 1121042880000631 +705Shieldtrail bonus pay for amazing work on #OSS 00010000631 +622231380104747568185788513840000000000#126861637760# Andrew Brown 1121042880000632 +705Weaselgem bonus pay for amazing work on #OSS 00010000632 +622231380104674127565537863380000000000#563016571780# Emma Davis 1121042880000633 +705Gooseequinox bonus pay for amazing work on #OSS 00010000633 +622231380104447130721765531420000000000#774115605273# Jacob Martinez 1121042880000634 +705Hunterbasalt bonus pay for amazing work on #OSS 00010000634 +622231380104682315374650615480000000000#455427224628# Aiden Davis 1121042880000635 +705Singertopaz bonus pay for amazing work on #OSS 00010000635 +622231380104382826773114422440000000000#676725443286# Natalie Martinez 1121042880000636 +705Skinnerebony bonus pay for amazing work on #OSS 00010000636 +622231380104771262166020501180000000000#355313422020# Jacob Brown 1121042880000637 +705Dragonroot bonus pay for amazing work on #OSS 00010000637 +622231380104755275722235328200000000000#838063656028# Emma Martinez 1121042880000638 +705Oxcedar bonus pay for amazing work on #OSS 00010000638 +622231380104824853534286842780000000000#201802855140# Matthew Harris 1121042880000639 +705Ringerleather bonus pay for amazing work on #OSS 00010000639 +622231380104048488738068040450000000000#425804448667# Ella Brown 1121042880000640 +705Twistertulip bonus pay for amazing work on #OSS 00010000640 +622231380104505581177544074000000000000#005488845161# William Smith 1121042880000641 +705Swordnoble bonus pay for amazing work on #OSS 00010000641 +622231380104830757205768702700000000000#270153748081# William White 1121042880000642 +705Stallionchip bonus pay for amazing work on #OSS 00010000642 +622231380104772466331207570870000000000#284183556772# Liam Martinez 1121042880000643 +705Sparrowpine bonus pay for amazing work on #OSS 00010000643 +622231380104412507461571215410000000000#121173640657# Joseph Williams 1121042880000644 +705Toefrill bonus pay for amazing work on #OSS 00010000644 +622231380104433543053660207530000000000#156610617664# Benjamin Jones 1121042880000645 +705Bisonsoft bonus pay for amazing work on #OSS 00010000645 +622231380104632303582801566780000000000#415674516732# Andrew Davis 1121042880000646 +705Mastersharp bonus pay for amazing work on #OSS 00010000646 +622231380104674140257324250420000000000#050720644380# Alexander Taylor 1121042880000647 +705Robinchocolate bonus pay for amazing work on #OSS 00010000647 +622231380104815705872134165000000000000#006041034007# Daniel Thomas 1121042880000648 +705Salmonbird bonus pay for amazing work on #OSS 00010000648 +622231380104023116638743428140000000000#578167111470# Zoey Robinson 1121042880000649 +705Jaguarbrick bonus pay for amazing work on #OSS 00010000649 +622231380104332561605155477630000000000#480742363222# Michael Harris 1121042880000650 +705Dropcyber bonus pay for amazing work on #OSS 00010000650 +622231380104630842871438111240000000000#703311803215# Chloe Williams 1121042880000651 +705Chinring bonus pay for amazing work on #OSS 00010000651 +622231380104057864453545760510000000000#266613471083# Michael Anderson 1121042880000652 +705Flashernebula bonus pay for amazing work on #OSS 00010000652 +622231380104012855436803085740000000000#767443687664# Noah Brown 1121042880000653 +705Seedfancy bonus pay for amazing work on #OSS 00010000653 +622231380104878687201664632740000000000#104575365088# Elijah Jackson 1121042880000654 +705Burnivory bonus pay for amazing work on #OSS 00010000654 +622231380104783432147701288230000000000#304758523345# Ethan Robinson 1121042880000655 +705Giversage bonus pay for amazing work on #OSS 00010000655 +622231380104247214183681378480000000000#306670455262# Anthony Garcia 1121042880000656 +705Guardianshy bonus pay for amazing work on #OSS 00010000656 +622231380104028170533040764650000000000#748671777857# William Jackson 1121042880000657 +705Roverlavender bonus pay for amazing work on #OSS 00010000657 +622231380104728465326463480820000000000#181016748218# Madison Brown 1121042880000658 +705Condortar bonus pay for amazing work on #OSS 00010000658 +622231380104054546437688361310000000000#644551716218# Sophia Smith 1121042880000659 +705Healerfog bonus pay for amazing work on #OSS 00010000659 +622231380104241466362656224740000000000#265030616252# Mason Johnson 1121042880000660 +705Snaketopaz bonus pay for amazing work on #OSS 00010000660 +622231380104506160806386211610000000000#031627818288# Isabella Moore 1121042880000661 +705Twistercosmic bonus pay for amazing work on #OSS 00010000661 +622231380104051178627211007270000000000#875206844725# Emma Garcia 1121042880000662 +705Whimseyshard bonus pay for amazing work on #OSS 00010000662 +622231380104686440587133350040000000000#831832214306# Daniel Brown 1121042880000663 +705Lionvoid bonus pay for amazing work on #OSS 00010000663 +622231380104224360154507366570000000000#143023321384# Ethan Miller 1121042880000664 +705Skinnerroot bonus pay for amazing work on #OSS 00010000664 +622231380104041355680456637320000000000#408131184446# Isabella Wilson 1121042880000665 +705Slavehill bonus pay for amazing work on #OSS 00010000665 +622231380104564678042116200370000000000#354422478313# Abigail Miller 1121042880000666 +705Boltflicker bonus pay for amazing work on #OSS 00010000666 +622231380104452741022337541510000000000#551803628113# Matthew Smith 1121042880000667 +705Falconchisel bonus pay for amazing work on #OSS 00010000667 +622231380104045273615636460470000000000#861100307707# William Smith 1121042880000668 +705Houndspice bonus pay for amazing work on #OSS 00010000668 +622231380104155035080023515450000000000#840064670200# Elijah Jackson 1121042880000669 +705Touchvenom bonus pay for amazing work on #OSS 00010000669 +622231380104603702167203885650000000000#114072233020# Charlotte Davis 1121042880000670 +705Whiptruth bonus pay for amazing work on #OSS 00010000670 +622231380104568433345248714300000000000#580310283765# James Moore 1121042880000671 +705Kittenshard bonus pay for amazing work on #OSS 00010000671 +622231380104881345144335137000000000000#266668223513# Olivia Thomas 1121042880000672 +705Storkhate bonus pay for amazing work on #OSS 00010000672 +622231380104811033285543361210000000000#660204481262# Andrew Thompson 1121042880000673 +705Chantersplit bonus pay for amazing work on #OSS 00010000673 +622231380104130742321017351850000000000#786004838804# Sofia Robinson 1121042880000674 +705Droppetal bonus pay for amazing work on #OSS 00010000674 +622231380104226336082180442570000000000#844567132228# Jacob Martinez 1121042880000675 +705Crestpolar bonus pay for amazing work on #OSS 00010000675 +622231380104124715324122051520000000000#718782875367# Elizabeth Brown 1121042880000676 +705Curtainquark bonus pay for amazing work on #OSS 00010000676 +622231380104165823706372458070000000000#088534874435# Jayden Taylor 1121042880000677 +705Speakerquiver bonus pay for amazing work on #OSS 00010000677 +622231380104448631830358102470000000000#265233347520# Anthony Moore 1121042880000678 +705Cloudbrindle bonus pay for amazing work on #OSS 00010000678 +622231380104254230050837060550000000000#686622072837# Matthew Brown 1121042880000679 +705Roverpale bonus pay for amazing work on #OSS 00010000679 +622231380104152173752658751840000000000#410862557746# Sophia Moore 1121042880000680 +705Witchlove bonus pay for amazing work on #OSS 00010000680 +622231380104334026281777235530000000000#720363455012# Emma Harris 1121042880000681 +705Mareplume bonus pay for amazing work on #OSS 00010000681 +622231380104306627723541650300000000000#451824533278# Chloe Johnson 1121042880000682 +705Duckpear bonus pay for amazing work on #OSS 00010000682 +622231380104357441612484377150000000000#374804647550# Liam Jones 1121042880000683 +705Hoofphase bonus pay for amazing work on #OSS 00010000683 +622231380104700336114147558570000000000#118156473641# Sophia Harris 1121042880000684 +705Iguanavalley bonus pay for amazing work on #OSS 00010000684 +622231380104455174545001502370000000000#224160270635# Abigail Jackson 1121042880000685 +705Crowncotton bonus pay for amazing work on #OSS 00010000685 +622231380104068225186847370730000000000#008401471872# Ethan Wilson 1121042880000686 +705Boltshort bonus pay for amazing work on #OSS 00010000686 +622231380104563052157315208410000000000#825847433166# Mason Martin 1121042880000687 +705Knavemud bonus pay for amazing work on #OSS 00010000687 +622231380104381110630435206660000000000#348336406672# William Smith 1121042880000688 +705Shriekcotton bonus pay for amazing work on #OSS 00010000688 +622231380104020137866625401540000000000#776637573138# Ava Thompson 1121042880000689 +705Walkerrag bonus pay for amazing work on #OSS 00010000689 +622231380104622811852381118760000000000#113314111862# Daniel Thompson 1121042880000690 +705Legsebony bonus pay for amazing work on #OSS 00010000690 +622231380104284557761364232060000000000#366140437403# Lily Thomas 1121042880000691 +705Elflight bonus pay for amazing work on #OSS 00010000691 +622231380104370466055051750170000000000#162177251828# Liam Johnson 1121042880000692 +705Dancersaber bonus pay for amazing work on #OSS 00010000692 +622231380104604583308450758310000000000#860787637440# Andrew Davis 1121042880000693 +705Talonagate bonus pay for amazing work on #OSS 00010000693 +622231380104210727123858401470000000000#117043864408# Emma Harris 1121042880000694 +705Lifteralmond bonus pay for amazing work on #OSS 00010000694 +622231380104486826683811757880000000000#256730116407# Mason Smith 1121042880000695 +705Sentryswift bonus pay for amazing work on #OSS 00010000695 +622231380104464472120886365640000000000#576463535484# Abigail Martin 1121042880000696 +705Chilllinen bonus pay for amazing work on #OSS 00010000696 +622231380104828818063546614780000000000#352761230353# William Martinez 1121042880000697 +705Roarjuniper bonus pay for amazing work on #OSS 00010000697 +622231380104372760224082084540000000000#181080710036# Andrew Harris 1121042880000698 +705Gorillaorchid bonus pay for amazing work on #OSS 00010000698 +622231380104632835611434368600000000000#137805358053# Sophia Smith 1121042880000699 +705Throatgranite bonus pay for amazing work on #OSS 00010000699 +622231380104253677353803052010000000000#217770126386# Charlotte Robinson 1121042880000700 +705Scribeiron bonus pay for amazing work on #OSS 00010000700 +622231380104801064230747888840000000000#645453131027# William Moore 1121042880000701 +705Paladinplanet bonus pay for amazing work on #OSS 00010000701 +622231380104732701858113416370000000000#675321202177# Sophia Taylor 1121042880000702 +705Turnerpewter bonus pay for amazing work on #OSS 00010000702 +622231380104408827332436048820000000000#500617527335# Charlotte Martin 1121042880000703 +705Footspiral bonus pay for amazing work on #OSS 00010000703 +622231380104755757556306523420000000000#812574238656# Daniel Miller 1121042880000704 +705Lashercoconut bonus pay for amazing work on #OSS 00010000704 +622231380104567074666843242750000000000#456257572051# Aiden Thompson 1121042880000705 +705Skullmango bonus pay for amazing work on #OSS 00010000705 +622231380104430006550253255170000000000#186561736056# Noah Davis 1121042880000706 +705Fighterpineapple bonus pay for amazing work on #OSS 00010000706 +622231380104567017513565874130000000000#745668718234# Joseph Johnson 1121042880000707 +705Nosecosmic bonus pay for amazing work on #OSS 00010000707 +622231380104601655045162881070000000000#043501478536# Sophia Moore 1121042880000708 +705Elfivy bonus pay for amazing work on #OSS 00010000708 +622231380104430336741651453200000000000#041211274818# Natalie Thomas 1121042880000709 +705Bellyapple bonus pay for amazing work on #OSS 00010000709 +622231380104722037042571166450000000000#540106354460# Lily Thomas 1121042880000710 +705Turnersequoia bonus pay for amazing work on #OSS 00010000710 +622231380104731070087006455030000000000#752836523453# Jayden Anderson 1121042880000711 +705Fishriver bonus pay for amazing work on #OSS 00010000711 +622231380104085857567727247120000000000#408266378787# Chloe Brown 1121042880000712 +705Toothflash bonus pay for amazing work on #OSS 00010000712 +622231380104057871362100051700000000000#433585000548# Sofia Thomas 1121042880000713 +705Riderdenim bonus pay for amazing work on #OSS 00010000713 +622231380104788025568014465710000000000#813888455110# Aubrey Smith 1121042880000714 +705Flashergarnet bonus pay for amazing work on #OSS 00010000714 +622231380104237735482038114200000000000#785551044352# Emily Smith 1121042880000715 +705Ravenvenom bonus pay for amazing work on #OSS 00010000715 +622231380104641376628008525600000000000#016363800645# Elizabeth Wilson 1121042880000716 +705Sharkbold bonus pay for amazing work on #OSS 00010000716 +622231380104127488801430548320000000000#241188007420# James Martinez 1121042880000717 +705Mythglory bonus pay for amazing work on #OSS 00010000717 +622231380104760348034675118510000000000#511882768716# Chloe Miller 1121042880000718 +705Chatterhorn bonus pay for amazing work on #OSS 00010000718 +622231380104006357424002861340000000000#778603545450# Addison Garcia 1121042880000719 +705Curtaintime bonus pay for amazing work on #OSS 00010000719 +622231380104840203522820366310000000000#320445081744# Daniel Jackson 1121042880000720 +705Antlershade bonus pay for amazing work on #OSS 00010000720 +622231380104647601616511277780000000000#281121104713# Isabella Anderson 1121042880000721 +705Weaselrazor bonus pay for amazing work on #OSS 00010000721 +622231380104175830506575624750000000000#136541016510# Elizabeth Thomas 1121042880000722 +705Wandererlava bonus pay for amazing work on #OSS 00010000722 +622231380104767233774144360520000000000#506453038533# Aiden Thompson 1121042880000723 +705Hunterdog bonus pay for amazing work on #OSS 00010000723 +622231380104264578170237845680000000000#835820511086# Sofia Thompson 1121042880000724 +705Weedpinto bonus pay for amazing work on #OSS 00010000724 +622231380104616447602226123880000000000#822251161388# Elizabeth Garcia 1121042880000725 +705Wandererpond bonus pay for amazing work on #OSS 00010000725 +622231380104208228274527486830000000000#268746285640# Natalie Harris 1121042880000726 +705Puppyrampant bonus pay for amazing work on #OSS 00010000726 +622231380104246424303414782160000000000#228363136725# Mia Moore 1121042880000727 +705Jaguarlaser bonus pay for amazing work on #OSS 00010000727 +622231380104284277667645643680000000000#227441260086# Ethan Davis 1121042880000728 +705Deermirage bonus pay for amazing work on #OSS 00010000728 +622231380104541020044033526260000000000#284374736683# Michael Wilson 1121042880000729 +705Lionpeach bonus pay for amazing work on #OSS 00010000729 +622231380104670622507523374130000000000#080272765110# Aiden Thomas 1121042880000730 +705Stingersteel bonus pay for amazing work on #OSS 00010000730 +622231380104870622685136227680000000000#678172732326# Mia Thompson 1121042880000731 +705Thumbhill bonus pay for amazing work on #OSS 00010000731 +622231380104330841515822372840000000000#278012287623# Aiden Martin 1121042880000732 +705Kickerdisco bonus pay for amazing work on #OSS 00010000732 +622231380104353407525864518360000000000#163237425172# Sophia Harris 1121042880000733 +705Llamabrass bonus pay for amazing work on #OSS 00010000733 +622231380104608851082687813600000000000#444783028715# Benjamin White 1121042880000734 +705Hidestellar bonus pay for amazing work on #OSS 00010000734 +622231380104173806413688448060000000000#116765473362# David Johnson 1121042880000735 +705Edgeeast bonus pay for amazing work on #OSS 00010000735 +622231380104424323080070244620000000000#642645568186# Sophia Thomas 1121042880000736 +705Seedcyan bonus pay for amazing work on #OSS 00010000736 +622231380104626346766046720260000000000#106187582670# Avery Harris 1121042880000737 +705Riderglacier bonus pay for amazing work on #OSS 00010000737 +622231380104384060806806300350000000000#726486204427# Abigail Johnson 1121042880000738 +705Toothgreen bonus pay for amazing work on #OSS 00010000738 +622231380104441613520173525480000000000#370245854778# Joshua Harris 1121042880000739 +705Roarerglaze bonus pay for amazing work on #OSS 00010000739 +622231380104025861544043601570000000000#718647384817# Olivia Brown 1121042880000740 +705Antcosmic bonus pay for amazing work on #OSS 00010000740 +622231380104075146301661826370000000000#820787328586# Chloe Taylor 1121042880000741 +705Knaveweed bonus pay for amazing work on #OSS 00010000741 +622231380104465416446876258440000000000#736082187064# Elijah Garcia 1121042880000742 +705Cranesolar bonus pay for amazing work on #OSS 00010000742 +622231380104706014740450421130000000000#753703753848# Anthony Jackson 1121042880000743 +705Browchip bonus pay for amazing work on #OSS 00010000743 +622231380104157607642582407700000000000#355821025663# Michael Taylor 1121042880000744 +705Ottermangrove bonus pay for amazing work on #OSS 00010000744 +622231380104127268383778024250000000000#075516537482# Jayden Taylor 1121042880000745 +705Runnerhelix bonus pay for amazing work on #OSS 00010000745 +622231380104156263334465145010000000000#778602510285# Sophia Miller 1121042880000746 +705Flasherleather bonus pay for amazing work on #OSS 00010000746 +622231380104751543846518127450000000000#224327381206# Olivia Thompson 1121042880000747 +705Mousecitrine bonus pay for amazing work on #OSS 00010000747 +622231380104826487301713841000000000000#813414424030# Alexander Thomas 1121042880000748 +705Mustanglinen bonus pay for amazing work on #OSS 00010000748 +622231380104220176018043201560000000000#752745577833# Emily Anderson 1121042880000749 +705Hornetring bonus pay for amazing work on #OSS 00010000749 +622231380104201403012315038850000000000#681766730282# Abigail Smith 1121042880000750 +705Snoutmint bonus pay for amazing work on #OSS 00010000750 +622231380104023031667651824050000000000#767658838788# Elijah Jackson 1121042880000751 +705Kingbattle bonus pay for amazing work on #OSS 00010000751 +622231380104341178181400650760000000000#800800804183# Zoey Taylor 1121042880000752 +705Beegray bonus pay for amazing work on #OSS 00010000752 +622231380104547023520703588480000000000#037341082466# Isabella Jones 1121042880000753 +705Chillerdandy bonus pay for amazing work on #OSS 00010000753 +622231380104503121061685308520000000000#445687517274# Matthew Jackson 1121042880000754 +705Grabberdenim bonus pay for amazing work on #OSS 00010000754 +622231380104334377242447013100000000000#873216578661# Madison Harris 1121042880000755 +705Toeflax bonus pay for amazing work on #OSS 00010000755 +622231380104337524744000631540000000000#787045064246# David Smith 1121042880000756 +705Ocelotgrave bonus pay for amazing work on #OSS 00010000756 +622231380104715748845332156260000000000#552508060013# Joshua Garcia 1121042880000757 +705Turnergeode bonus pay for amazing work on #OSS 00010000757 +622231380104686148151882531400000000000#073354441152# Chloe Johnson 1121042880000758 +705Sharkcliff bonus pay for amazing work on #OSS 00010000758 +622231380104512708501611784530000000000#518388264771# Anthony Anderson 1121042880000759 +705Mouselinen bonus pay for amazing work on #OSS 00010000759 +622231380104351367443813586500000000000#756528083004# Abigail Wilson 1121042880000760 +705Boartopaz bonus pay for amazing work on #OSS 00010000760 +622231380104250647744304870150000000000#831665843872# Mason Brown 1121042880000761 +705Bisongiant bonus pay for amazing work on #OSS 00010000761 +622231380104257705437084663580000000000#006422715647# Benjamin Miller 1121042880000762 +705Cubaquamarine bonus pay for amazing work on #OSS 00010000762 +622231380104541166282732101570000000000#524276125645# Jacob Wilson 1121042880000763 +705Ottercerulean bonus pay for amazing work on #OSS 00010000763 +622231380104742334670337106280000000000#885388037155# Noah Wilson 1121042880000764 +705Pigpeat bonus pay for amazing work on #OSS 00010000764 +622231380104888644158457528650000000000#322074318223# Daniel Jackson 1121042880000765 +705Carpettyphoon bonus pay for amazing work on #OSS 00010000765 +622231380104270215226281500650000000000#554566304407# Ethan Robinson 1121042880000766 +705Skullpuzzle bonus pay for amazing work on #OSS 00010000766 +622231380104815376565734514570000000000#266484636038# Charlotte Williams 1121042880000767 +705Ninjalunar bonus pay for amazing work on #OSS 00010000767 +622231380104464536276846620480000000000#287454218378# Sofia Wilson 1121042880000768 +705Sargenttopaz bonus pay for amazing work on #OSS 00010000768 +622231380104075255657054215830000000000#478274445302# Emily Thompson 1121042880000769 +705Servantblossom bonus pay for amazing work on #OSS 00010000769 +622231380104040147868488620420000000000#400886364604# James Jackson 1121042880000770 +705Monkeybitter bonus pay for amazing work on #OSS 00010000770 +622231380104567865701460856320000000000#253275025726# Zoey Martinez 1121042880000771 +705Dogglass bonus pay for amazing work on #OSS 00010000771 +622231380104720253661050555560000000000#553425485822# Alexander Miller 1121042880000772 +705Robinsatin bonus pay for amazing work on #OSS 00010000772 +622231380104243057416132202000000000000#410475740528# Avery Miller 1121042880000773 +705Scaleriver bonus pay for amazing work on #OSS 00010000773 +622231380104537304162183668720000000000#838746770741# Andrew Martin 1121042880000774 +705Mousenotch bonus pay for amazing work on #OSS 00010000774 +622231380104581136870606112810000000000#187578014066# Emma Williams 1121042880000775 +705Carveriridescent bonus pay for amazing work on #OSS 00010000775 +622231380104235166815317025440000000000#477034501673# Mason Jackson 1121042880000776 +705Shriekersilent bonus pay for amazing work on #OSS 00010000776 +622231380104576415405677223050000000000#477786811054# Sofia Jones 1121042880000777 +705Whalerelic bonus pay for amazing work on #OSS 00010000777 +622231380104388333136015448030000000000#271153028645# Aiden Williams 1121042880000778 +705Spiritlemon bonus pay for amazing work on #OSS 00010000778 +622231380104877058061464217300000000000#261080331837# Andrew Martin 1121042880000779 +705Serpentzinc bonus pay for amazing work on #OSS 00010000779 +622231380104271410143477828410000000000#030158655172# Emily Thomas 1121042880000780 +705Gemsatin bonus pay for amazing work on #OSS 00010000780 +622231380104574020368647163140000000000#366735245726# Matthew Davis 1121042880000781 +705Whippalm bonus pay for amazing work on #OSS 00010000781 +622231380104320367626731545740000000000#433382034040# Natalie Brown 1121042880000782 +705Singerlaser bonus pay for amazing work on #OSS 00010000782 +622231380104413452167822276380000000000#102226870704# Joshua Harris 1121042880000783 +705Donkeyruby bonus pay for amazing work on #OSS 00010000783 +622231380104808436276160075630000000000#148646645325# Madison Davis 1121042880000784 +705Daggerdisco bonus pay for amazing work on #OSS 00010000784 +622231380104784222607516283560000000000#215170258010# David Johnson 1121042880000785 +705Scarribbon bonus pay for amazing work on #OSS 00010000785 +622231380104601110328578713470000000000#033007788502# Aiden Johnson 1121042880000786 +705Chinmeteor bonus pay for amazing work on #OSS 00010000786 +622231380104285426878040332370000000000#241661044155# Mason Moore 1121042880000787 +705Salmonphase bonus pay for amazing work on #OSS 00010000787 +622231380104176426568758663480000000000#353873110300# Aubrey Brown 1121042880000788 +705Twisterfern bonus pay for amazing work on #OSS 00010000788 +622231380104227551163028747450000000000#543256527330# Matthew Williams 1121042880000789 +705Snagglefootfair bonus pay for amazing work on #OSS 00010000789 +622231380104687841644338821130000000000#616573352657# Matthew Davis 1121042880000790 +705Cockatoosly bonus pay for amazing work on #OSS 00010000790 +622231380104778520064082310840000000000#772446771060# David Williams 1121042880000791 +705Skulllime bonus pay for amazing work on #OSS 00010000791 +622231380104435862823835856050000000000#533403661118# Avery Garcia 1121042880000792 +705Stormiridescent bonus pay for amazing work on #OSS 00010000792 +622231380104367810502150211800000000000#572152524255# Anthony Harris 1121042880000793 +705Griffinmorning bonus pay for amazing work on #OSS 00010000793 +622231380104804706113575441640000000000#443362386683# Aiden Taylor 1121042880000794 +705Stealerchisel bonus pay for amazing work on #OSS 00010000794 +622231380104608368670871844120000000000#846020223245# Natalie Johnson 1121042880000795 +705Koalasnow bonus pay for amazing work on #OSS 00010000795 +622231380104362004566128458700000000000#010414411568# Andrew Thompson 1121042880000796 +705Healerlight bonus pay for amazing work on #OSS 00010000796 +622231380104361403336055360710000000000#623032777754# Emily Martin 1121042880000797 +705Antlerrust bonus pay for amazing work on #OSS 00010000797 +622231380104153701888142408700000000000#408861372354# Sophia Thompson 1121042880000798 +705Coyotebead bonus pay for amazing work on #OSS 00010000798 +622231380104582381340836302840000000000#350232261742# Joseph Robinson 1121042880000799 +705Kittenspice bonus pay for amazing work on #OSS 00010000799 +622231380104248214471187688310000000000#346863463200# Matthew Brown 1121042880000800 +705Fishblue bonus pay for amazing work on #OSS 00010000800 +622231380104330345766788145840000000000#722514671340# Isabella Taylor 1121042880000801 +705Catchertime bonus pay for amazing work on #OSS 00010000801 +622231380104610850672704846510000000000#806386651411# Mason Anderson 1121042880000802 +705Chargergrave bonus pay for amazing work on #OSS 00010000802 +622231380104151150153820544160000000000#506874772250# Michael Taylor 1121042880000803 +705Gargoylejelly bonus pay for amazing work on #OSS 00010000803 +622231380104360600242270186850000000000#688428862617# David Taylor 1121042880000804 +705Legendfire bonus pay for amazing work on #OSS 00010000804 +622231380104371105030440842560000000000#030827667740# Sofia Harris 1121042880000805 +705Noseviridian bonus pay for amazing work on #OSS 00010000805 +622231380104023675025420714750000000000#655545318842# Addison Davis 1121042880000806 +705Tracksprout bonus pay for amazing work on #OSS 00010000806 +622231380104804840138215128700000000000#836227436564# Michael Miller 1121042880000807 +705Unicorngiant bonus pay for amazing work on #OSS 00010000807 +622231380104206218854561027240000000000#872465443217# Abigail Thomas 1121042880000808 +705Paladincoffee bonus pay for amazing work on #OSS 00010000808 +622231380104110002675744801470000000000#574705416185# Avery Jones 1121042880000809 +705Stonespark bonus pay for amazing work on #OSS 00010000809 +622231380104370511321537137130000000000#553027272210# Andrew Jackson 1121042880000810 +705Panthersoft bonus pay for amazing work on #OSS 00010000810 +622231380104874812505642666120000000000#024440776387# Anthony Williams 1121042880000811 +705Rippersponge bonus pay for amazing work on #OSS 00010000811 +622231380104814202043143505380000000000#532383621652# Chloe Johnson 1121042880000812 +705Boabush bonus pay for amazing work on #OSS 00010000812 +622231380104732444427672846370000000000#565616822376# Noah Miller 1121042880000813 +705Birdpie bonus pay for amazing work on #OSS 00010000813 +622231380104802444471444855110000000000#125384515013# Anthony Martin 1121042880000814 +705Whimseywheat bonus pay for amazing work on #OSS 00010000814 +622231380104660342114683370150000000000#177432762203# Chloe White 1121042880000815 +705Beakslime bonus pay for amazing work on #OSS 00010000815 +622231380104461560437310585240000000000#334282148406# Daniel Thompson 1121042880000816 +705Spikewhite bonus pay for amazing work on #OSS 00010000816 +622231380104805585134111402670000000000#850422288780# Abigail White 1121042880000817 +705Owlrazor bonus pay for amazing work on #OSS 00010000817 +622231380104788456478664378130000000000#286167717845# Joseph Jackson 1121042880000818 +705Ribhorn bonus pay for amazing work on #OSS 00010000818 +622231380104866572076886080340000000000#715060577464# James Williams 1121042880000819 +705Glasslemon bonus pay for amazing work on #OSS 00010000819 +622231380104316562615543648650000000000#512657155806# Aubrey Williams 1121042880000820 +705Foecoal bonus pay for amazing work on #OSS 00010000820 +622231380104318362065055432730000000000#621485635508# Emily Martinez 1121042880000821 +705Lizardspring bonus pay for amazing work on #OSS 00010000821 +622231380104785168005764253670000000000#460314286847# Addison Robinson 1121042880000822 +705Runnernotch bonus pay for amazing work on #OSS 00010000822 +622231380104750866760307150120000000000#257670365107# Elijah Robinson 1121042880000823 +705Forgeramber bonus pay for amazing work on #OSS 00010000823 +622231380104355152833406350630000000000#808687508488# Elijah Martin 1121042880000824 +705Cloakberry bonus pay for amazing work on #OSS 00010000824 +622231380104644318007857066450000000000#340865387633# William Wilson 1121042880000825 +705Flyfossil bonus pay for amazing work on #OSS 00010000825 +622231380104487731508853728520000000000#184347173220# Joshua Williams 1121042880000826 +705Bootcoffee bonus pay for amazing work on #OSS 00010000826 +622231380104487727263487447280000000000#764025078212# Matthew Martin 1121042880000827 +705Piperpond bonus pay for amazing work on #OSS 00010000827 +622231380104018682017668726740000000000#842256552367# James Thomas 1121042880000828 +705Salmonhorse bonus pay for amazing work on #OSS 00010000828 +622231380104027843322588477520000000000#314835211455# Daniel Jackson 1121042880000829 +705Mistressobsidian bonus pay for amazing work on #OSS 00010000829 +622231380104202353348548813380000000000#180706438774# Natalie Taylor 1121042880000830 +705Snapperrain bonus pay for amazing work on #OSS 00010000830 +622231380104343855121158165000000000000#810120541741# David Jackson 1121042880000831 +705Ridgefield bonus pay for amazing work on #OSS 00010000831 +622231380104030252338775581520000000000#418408135742# Matthew Harris 1121042880000832 +705Killercyber bonus pay for amazing work on #OSS 00010000832 +622231380104371485372321872870000000000#862244708333# Jayden Thomas 1121042880000833 +705Raccoonfringe bonus pay for amazing work on #OSS 00010000833 +622231380104042887856780804710000000000#884831683652# Charlotte Taylor 1121042880000834 +705Lancercoral bonus pay for amazing work on #OSS 00010000834 +622231380104648415813285323530000000000#055614715432# Ethan Smith 1121042880000835 +705Otterhot bonus pay for amazing work on #OSS 00010000835 +622231380104056437264051523680000000000#247171747301# Joseph Robinson 1121042880000836 +705Kangaroovenom bonus pay for amazing work on #OSS 00010000836 +622231380104765630703338433220000000000#818003280685# Ava Harris 1121042880000837 +705Hidetree bonus pay for amazing work on #OSS 00010000837 +622231380104575580431252706780000000000#870021865275# Avery Thomas 1121042880000838 +705Venomwarp bonus pay for amazing work on #OSS 00010000838 +622231380104600414524481013050000000000#721007824683# Ethan Harris 1121042880000839 +705Grabberweed bonus pay for amazing work on #OSS 00010000839 +622231380104105307667032483830000000000#738542581452# Zoey Garcia 1121042880000840 +705Toucanlaser bonus pay for amazing work on #OSS 00010000840 +622231380104160851732305448130000000000#077615350542# Charlotte Smith 1121042880000841 +705Sharksavage bonus pay for amazing work on #OSS 00010000841 +622231380104583307385247054600000000000#733803300351# Daniel Anderson 1121042880000842 +705Kickerdour bonus pay for amazing work on #OSS 00010000842 +622231380104565076876514408220000000000#482775560883# Sophia Harris 1121042880000843 +705Bellypaint bonus pay for amazing work on #OSS 00010000843 +622231380104227150246337237560000000000#145027152182# Emma Johnson 1121042880000844 +705Playercarnation bonus pay for amazing work on #OSS 00010000844 +622231380104404150463475104840000000000#402038262345# Ava Wilson 1121042880000845 +705Walkerdot bonus pay for amazing work on #OSS 00010000845 +622231380104105766044853502140000000000#416301621800# Avery Miller 1121042880000846 +705Whimseypond bonus pay for amazing work on #OSS 00010000846 +622231380104384821652537601840000000000#477032063555# Avery Miller 1121042880000847 +705Loonpewter bonus pay for amazing work on #OSS 00010000847 +622231380104346412070535253150000000000#552663206704# David Martinez 1121042880000848 +705Spiritbrindle bonus pay for amazing work on #OSS 00010000848 +622231380104272221356711484620000000000#744535183750# Lily Miller 1121042880000849 +705Roverpine bonus pay for amazing work on #OSS 00010000849 +622231380104225621552688861150000000000#211571022164# Isabella White 1121042880000850 +705Banecyber bonus pay for amazing work on #OSS 00010000850 +622231380104118765228547500240000000000#350647675102# Olivia Smith 1121042880000851 +705Goatpollen bonus pay for amazing work on #OSS 00010000851 +622231380104047063447733315560000000000#212226077527# Ava Taylor 1121042880000852 +705Speakerbrave bonus pay for amazing work on #OSS 00010000852 +622231380104462558202363007170000000000#011378372182# Natalie Thomas 1121042880000853 +705Leopardpool bonus pay for amazing work on #OSS 00010000853 +622231380104813143783730421450000000000#145283165052# Jacob Robinson 1121042880000854 +705Hunterglory bonus pay for amazing work on #OSS 00010000854 +622231380104864377217353536180000000000#450448350063# Chloe Thomas 1121042880000855 +705Bearpuddle bonus pay for amazing work on #OSS 00010000855 +622231380104618633444343208160000000000#322553723200# David Williams 1121042880000856 +705Killermangrove bonus pay for amazing work on #OSS 00010000856 +622231380104374103602837055200000000000#426342702846# Isabella Thompson 1121042880000857 +705Dukecold bonus pay for amazing work on #OSS 00010000857 +622231380104044853456072224480000000000#703705500046# David Brown 1121042880000858 +705Condorbow bonus pay for amazing work on #OSS 00010000858 +622231380104875836428543323450000000000#588577461016# Matthew Williams 1121042880000859 +705Piratecoffee bonus pay for amazing work on #OSS 00010000859 +622231380104046510651224000260000000000#071578287738# Mia Davis 1121042880000860 +705Dancerproud bonus pay for amazing work on #OSS 00010000860 +622231380104800525424665501800000000000#044637028677# Joseph Garcia 1121042880000861 +705Sentrybush bonus pay for amazing work on #OSS 00010000861 +622231380104285263822181838800000000000#640704673431# Emily Miller 1121042880000862 +705Knightroad bonus pay for amazing work on #OSS 00010000862 +622231380104686012715337876080000000000#137315202605# Addison Thompson 1121042880000863 +705Goosetulip bonus pay for amazing work on #OSS 00010000863 +622231380104112837170148285700000000000#727182467706# Ava Martinez 1121042880000864 +705Crestflame bonus pay for amazing work on #OSS 00010000864 +622231380104571443167031785640000000000#770114743075# Elizabeth Miller 1121042880000865 +705Raybrass bonus pay for amazing work on #OSS 00010000865 +622231380104624625827751371840000000000#275738378550# Zoey Thomas 1121042880000866 +705Shriektorch bonus pay for amazing work on #OSS 00010000866 +622231380104404372755061118670000000000#576383008148# Noah Garcia 1121042880000867 +705Toeivy bonus pay for amazing work on #OSS 00010000867 +622231380104705386666063432840000000000#542545343771# Joshua Martinez 1121042880000868 +705Llamaflint bonus pay for amazing work on #OSS 00010000868 +622231380104053386413118687510000000000#330202856270# Addison Martinez 1121042880000869 +705Boajust bonus pay for amazing work on #OSS 00010000869 +622231380104700373455787164420000000000#515445214380# Sofia Taylor 1121042880000870 +705Bunnyshadow bonus pay for amazing work on #OSS 00010000870 +622231380104468631465074332720000000000#423055160265# Olivia Jones 1121042880000871 +705Pirategossamer bonus pay for amazing work on #OSS 00010000871 +622231380104424718833535481420000000000#447547853804# Mia Jones 1121042880000872 +705Kickerjelly bonus pay for amazing work on #OSS 00010000872 +622231380104881606787736842710000000000#326670182885# Anthony Smith 1121042880000873 +705Kittenorchid bonus pay for amazing work on #OSS 00010000873 +622231380104211036821001176710000000000#211022126635# Elizabeth Taylor 1121042880000874 +705Moleebony bonus pay for amazing work on #OSS 00010000874 +622231380104117185858168204320000000000#383852170401# Benjamin White 1121042880000875 +705Watcherkiwi bonus pay for amazing work on #OSS 00010000875 +622231380104468252505318275870000000000#188044632833# Sophia Anderson 1121042880000876 +705Gemlizard bonus pay for amazing work on #OSS 00010000876 +622231380104357871141856441030000000000#612316540578# James Harris 1121042880000877 +705Jackallake bonus pay for amazing work on #OSS 00010000877 +622231380104744553012714372740000000000#388153275116# Benjamin White 1121042880000878 +705Weaverspeckle bonus pay for amazing work on #OSS 00010000878 +622231380104083342823375521630000000000#724511235732# Michael Taylor 1121042880000879 +705Terriersleet bonus pay for amazing work on #OSS 00010000879 +622231380104685422341551601120000000000#536046440480# Emma Martin 1121042880000880 +705Chestwheat bonus pay for amazing work on #OSS 00010000880 +622231380104527643061337307340000000000#446827725383# Noah White 1121042880000881 +705Fairyshag bonus pay for amazing work on #OSS 00010000881 +622231380104225530780756125620000000000#564357215184# Aiden Robinson 1121042880000882 +705Watchersavage bonus pay for amazing work on #OSS 00010000882 +622231380104814568546371323250000000000#300470830306# Alexander Johnson 1121042880000883 +705Foemisty bonus pay for amazing work on #OSS 00010000883 +622231380104167778727751633170000000000#068625476251# Noah Harris 1121042880000884 +705Hoofsheer bonus pay for amazing work on #OSS 00010000884 +622231380104483374025234085070000000000#767887314275# Matthew Thomas 1121042880000885 +705Hawkpaper bonus pay for amazing work on #OSS 00010000885 +622231380104478645506464582440000000000#828837386544# Ethan Martinez 1121042880000886 +705Otterroot bonus pay for amazing work on #OSS 00010000886 +622231380104867610276883331140000000000#475751315763# Avery Jones 1121042880000887 +705Songdour bonus pay for amazing work on #OSS 00010000887 +622231380104076236165784727130000000000#672554225785# Jacob Jackson 1121042880000888 +705Shieldweed bonus pay for amazing work on #OSS 00010000888 +622231380104047882484524462400000000000#732788245360# Andrew Williams 1121042880000889 +705Shriekripple bonus pay for amazing work on #OSS 00010000889 +622231380104417004550715765420000000000#834852163823# Ella Garcia 1121042880000890 +705Whaletide bonus pay for amazing work on #OSS 00010000890 +622231380104632063857516085530000000000#687368601422# Mason Thomas 1121042880000891 +705Weaselfork bonus pay for amazing work on #OSS 00010000891 +622231380104443785804212627080000000000#022581270586# James Moore 1121042880000892 +705Footquill bonus pay for amazing work on #OSS 00010000892 +622231380104241841112255328680000000000#251650172711# Ava Martin 1121042880000893 +705Hornetivy bonus pay for amazing work on #OSS 00010000893 +622231380104351137645705476530000000000#130868455466# Anthony Moore 1121042880000894 +705Carpetquiver bonus pay for amazing work on #OSS 00010000894 +622231380104228107464657864140000000000#108053486031# Liam Smith 1121042880000895 +705Runnercaramel bonus pay for amazing work on #OSS 00010000895 +622231380104134563521106634600000000000#380515007827# Mason Garcia 1121042880000896 +705Pegasuscanyon bonus pay for amazing work on #OSS 00010000896 +622231380104647748653380187600000000000#722772502474# Joshua Smith 1121042880000897 +705Parrotfoul bonus pay for amazing work on #OSS 00010000897 +622231380104538435112121643570000000000#172256786404# Michael Moore 1121042880000898 +705Fingertar bonus pay for amazing work on #OSS 00010000898 +622231380104520805868418880340000000000#240376587820# Alexander Thomas 1121042880000899 +705Weedweak bonus pay for amazing work on #OSS 00010000899 +622231380104042443023365101330000000000#300248625721# Sofia Taylor 1121042880000900 +705Loonproud bonus pay for amazing work on #OSS 00010000900 +622231380104441155070361517110000000000#838700345345# Noah Jackson 1121042880000901 +705Hyenashade bonus pay for amazing work on #OSS 00010000901 +622231380104122014110505874540000000000#103411058157# Charlotte Martin 1121042880000902 +705Bitediamond bonus pay for amazing work on #OSS 00010000902 +622231380104777708240220731880000000000#853377640832# Anthony Williams 1121042880000903 +705Elfbranch bonus pay for amazing work on #OSS 00010000903 +622231380104410370461264524380000000000#586874706778# Matthew Smith 1121042880000904 +705Flyalder bonus pay for amazing work on #OSS 00010000904 +622231380104315714365000453660000000000#716017781333# Mason Harris 1121042880000905 +705Raptorbuttercup bonus pay for amazing work on #OSS 00010000905 +622231380104823025235254835260000000000#831484668575# Liam Jones 1121042880000906 +705Bisonspot bonus pay for amazing work on #OSS 00010000906 +622231380104682665671651870020000000000#612531348264# William Wilson 1121042880000907 +705Heronwild bonus pay for amazing work on #OSS 00010000907 +622231380104437353530666034700000000000#461703036158# Mason Martin 1121042880000908 +705Slavezenith bonus pay for amazing work on #OSS 00010000908 +622231380104781075464786533120000000000#145008724171# Anthony Martinez 1121042880000909 +705Weaverpond bonus pay for amazing work on #OSS 00010000909 +622231380104644462723000025770000000000#575261380356# Ella Brown 1121042880000910 +705Huggerquartz bonus pay for amazing work on #OSS 00010000910 +622231380104445787013078011160000000000#057352168763# Chloe Robinson 1121042880000911 +705Scarnorth bonus pay for amazing work on #OSS 00010000911 +622231380104021834835800384850000000000#514827508837# Jayden Taylor 1121042880000912 +705Huggersly bonus pay for amazing work on #OSS 00010000912 +622231380104705164623862347230000000000#831045165286# William Smith 1121042880000913 +705Cougarbrook bonus pay for amazing work on #OSS 00010000913 +622231380104242805312816400530000000000#166783137227# Chloe Davis 1121042880000914 +705Salmonash bonus pay for amazing work on #OSS 00010000914 +622231380104631604526328600220000000000#731253566368# Jacob Robinson 1121042880000915 +705Liondesert bonus pay for amazing work on #OSS 00010000915 +622231380104303716825083073210000000000#547647601608# Natalie Martin 1121042880000916 +705Snarlluck bonus pay for amazing work on #OSS 00010000916 +622231380104517031324612142700000000000#316087352551# Benjamin Garcia 1121042880000917 +705Traderdot bonus pay for amazing work on #OSS 00010000917 +622231380104282835427246204670000000000#201385631680# Sophia Jackson 1121042880000918 +705Scowlmalachite bonus pay for amazing work on #OSS 00010000918 +622231380104574732573278748570000000000#425135144781# Madison Harris 1121042880000919 +705Moosestripe bonus pay for amazing work on #OSS 00010000919 +622231380104652688014652872040000000000#816628401043# Ella Jones 1121042880000920 +705Bellphase bonus pay for amazing work on #OSS 00010000920 +622231380104382253335422053630000000000#716520738288# Chloe Anderson 1121042880000921 +705Otterquill bonus pay for amazing work on #OSS 00010000921 +622231380104778174541823631220000000000#273260684820# Elijah Robinson 1121042880000922 +705Bardfringe bonus pay for amazing work on #OSS 00010000922 +622231380104730645375633313340000000000#484017700501# Aubrey Wilson 1121042880000923 +705Koalapineapple bonus pay for amazing work on #OSS 00010000923 +622231380104555315452776810310000000000#214240435457# Aubrey Moore 1121042880000924 +705Dognotch bonus pay for amazing work on #OSS 00010000924 +622231380104142046425067222840000000000#133851157051# Lily Smith 1121042880000925 +705Edgewhite bonus pay for amazing work on #OSS 00010000925 +622231380104211308082552843650000000000#617812880432# Daniel Garcia 1121042880000926 +705Waspdirt bonus pay for amazing work on #OSS 00010000926 +622231380104530837743182105500000000000#664714802045# Ella Jones 1121042880000927 +705Apespice bonus pay for amazing work on #OSS 00010000927 +622231380104532522263870207320000000000#140288487140# Michael Jackson 1121042880000928 +705Samurailead bonus pay for amazing work on #OSS 00010000928 +622231380104036058618037750500000000000#358746221141# Mia Smith 1121042880000929 +705Hissmarble bonus pay for amazing work on #OSS 00010000929 +622231380104587268232237701170000000000#881771565363# Addison Davis 1121042880000930 +705Killerfog bonus pay for amazing work on #OSS 00010000930 +622231380104665125578544037580000000000#767875232488# Anthony Harris 1121042880000931 +705Heronnight bonus pay for amazing work on #OSS 00010000931 +622231380104677714572326652540000000000#874881062074# Lily Miller 1121042880000932 +705Stingchestnut bonus pay for amazing work on #OSS 00010000932 +622231380104641234480584732440000000000#882150502816# Lily Williams 1121042880000933 +705Samuraibig bonus pay for amazing work on #OSS 00010000933 +622231380104860732673060286120000000000#812611351204# Benjamin Anderson 1121042880000934 +705Reaperorchid bonus pay for amazing work on #OSS 00010000934 +622231380104145528864756782130000000000#000282621255# Ava Garcia 1121042880000935 +705Swallowforest bonus pay for amazing work on #OSS 00010000935 +622231380104451174588077504500000000000#653056043018# Sofia Miller 1121042880000936 +705Scarbutter bonus pay for amazing work on #OSS 00010000936 +622231380104384315313858844250000000000#831604407678# James Robinson 1121042880000937 +705Catcherorchid bonus pay for amazing work on #OSS 00010000937 +622231380104281734373538472420000000000#424884778200# Joshua Smith 1121042880000938 +705Reaperchatter bonus pay for amazing work on #OSS 00010000938 +622231380104824780727680111420000000000#544447526014# Aubrey Jackson 1121042880000939 +705Legendslash bonus pay for amazing work on #OSS 00010000939 +622231380104250542200731506880000000000#072275124311# Ethan Johnson 1121042880000940 +705Friendpyrite bonus pay for amazing work on #OSS 00010000940 +622231380104763735257035377550000000000#538041308852# Sophia Jackson 1121042880000941 +705Musesky bonus pay for amazing work on #OSS 00010000941 +622231380104677732022266100630000000000#471762838688# Olivia Jones 1121042880000942 +705Cloudswift bonus pay for amazing work on #OSS 00010000942 +622231380104115634562484087140000000000#511708072283# William Robinson 1121042880000943 +705Scaleember bonus pay for amazing work on #OSS 00010000943 +622231380104180486466035084030000000000#123146776030# Olivia Anderson 1121042880000944 +705Carpwild bonus pay for amazing work on #OSS 00010000944 +622231380104468310603600724420000000000#747238871320# Ella Miller 1121042880000945 +705Stonesnapdragon bonus pay for amazing work on #OSS 00010000945 +622231380104103282437537550720000000000#058723123270# Michael Johnson 1121042880000946 +705Butterflysouth bonus pay for amazing work on #OSS 00010000946 +622231380104243014023430583610000000000#632166411070# David White 1121042880000947 +705Chinsmall bonus pay for amazing work on #OSS 00010000947 +622231380104446404311167461260000000000#261388240018# Joshua Moore 1121042880000948 +705Foxdent bonus pay for amazing work on #OSS 00010000948 +622231380104810883826236357740000000000#652608722412# David Williams 1121042880000949 +705Ridgegossamer bonus pay for amazing work on #OSS 00010000949 +622231380104278474010221037700000000000#127347252701# Andrew Martin 1121042880000950 +705Diversurf bonus pay for amazing work on #OSS 00010000950 +622231380104318135460661431750000000000#137064157314# Chloe Johnson 1121042880000951 +705Seerdawn bonus pay for amazing work on #OSS 00010000951 +622231380104604812101435426850000000000#836222677240# Michael Harris 1121042880000952 +705Lasherlightning bonus pay for amazing work on #OSS 00010000952 +622231380104076104624054667650000000000#526235003541# Olivia Jackson 1121042880000953 +705Jaybead bonus pay for amazing work on #OSS 00010000953 +622231380104433718167105524620000000000#057807240485# Daniel Smith 1121042880000954 +705Minnowmidnight bonus pay for amazing work on #OSS 00010000954 +622231380104856767701337876880000000000#720752256580# James Miller 1121042880000955 +705Divetin bonus pay for amazing work on #OSS 00010000955 +622231380104257227445281704810000000000#740327777333# Lily Jones 1121042880000956 +705Birdforest bonus pay for amazing work on #OSS 00010000956 +622231380104843128616037725310000000000#206378283164# Lily Robinson 1121042880000957 +705Antlercitrine bonus pay for amazing work on #OSS 00010000957 +622231380104202406030477801020000000000#130031074360# Joseph Taylor 1121042880000958 +705Pegasusgarnet bonus pay for amazing work on #OSS 00010000958 +622231380104762861823750378380000000000#010160705277# Elizabeth Wilson 1121042880000959 +705Howlerlinen bonus pay for amazing work on #OSS 00010000959 +622231380104306136503344521660000000000#556365688636# Jacob Smith 1121042880000960 +705Scribeapricot bonus pay for amazing work on #OSS 00010000960 +622231380104304177338114043280000000000#024883676322# Chloe Jackson 1121042880000961 +705Knightring bonus pay for amazing work on #OSS 00010000961 +622231380104485673156322454650000000000#236820076583# William Thomas 1121042880000962 +705Dropstar bonus pay for amazing work on #OSS 00010000962 +622231380104224870638157567880000000000#605510368048# Anthony Harris 1121042880000963 +705Sagesand bonus pay for amazing work on #OSS 00010000963 +622231380104118514121311600220000000000#260163068724# William Thomas 1121042880000964 +705Cockatoolong bonus pay for amazing work on #OSS 00010000964 +622231380104831028675655663450000000000#838040312607# Addison Robinson 1121042880000965 +705Toefield bonus pay for amazing work on #OSS 00010000965 +622231380104280253446867412300000000000#378058337763# Charlotte Harris 1121042880000966 +705Toucanzircon bonus pay for amazing work on #OSS 00010000966 +622231380104885143515886442760000000000#705335865652# Liam Thompson 1121042880000967 +705Bootcarnation bonus pay for amazing work on #OSS 00010000967 +622231380104125056718780514370000000000#347613825722# Avery Jackson 1121042880000968 +705Skinnerpine bonus pay for amazing work on #OSS 00010000968 +622231380104574352333173256250000000000#004563586082# Natalie Davis 1121042880000969 +705Wyrmvenom bonus pay for amazing work on #OSS 00010000969 +622231380104128311771235647130000000000#846252351720# Ethan Moore 1121042880000970 +705Darttin bonus pay for amazing work on #OSS 00010000970 +622231380104684217633001575310000000000#718181747051# Addison Jackson 1121042880000971 +705Crystaliridescent bonus pay for amazing work on #OSS 00010000971 +622231380104808846225514718670000000000#516636584504# Isabella Robinson 1121042880000972 +705Markfield bonus pay for amazing work on #OSS 00010000972 +622231380104183423172276557370000000000#750478600638# Mia Thomas 1121042880000973 +705Wyrmluck bonus pay for amazing work on #OSS 00010000973 +622231380104054114617334826860000000000#850833550065# Abigail Johnson 1121042880000974 +705Ringerspangle bonus pay for amazing work on #OSS 00010000974 +622231380104351817457623222540000000000#146184262765# Avery Davis 1121042880000975 +705Kingdune bonus pay for amazing work on #OSS 00010000975 +622231380104475368840687504610000000000#624141237040# Zoey Wilson 1121042880000976 +705Seekernova bonus pay for amazing work on #OSS 00010000976 +622231380104727750832312250680000000000#643862436768# Jacob Martin 1121042880000977 +705Snagglefootplump bonus pay for amazing work on #OSS 00010000977 +622231380104856166608833546040000000000#208517304280# Abigail Martinez 1121042880000978 +705Spikeivy bonus pay for amazing work on #OSS 00010000978 +622231380104428375024452065330000000000#804111340270# Sofia Wilson 1121042880000979 +705Pantherchecker bonus pay for amazing work on #OSS 00010000979 +622231380104542247117363378260000000000#860134708064# Ella Thomas 1121042880000980 +705Carverfir bonus pay for amazing work on #OSS 00010000980 +622231380104708185445851721210000000000#846737425271# William Williams 1121042880000981 +705Elkmisty bonus pay for amazing work on #OSS 00010000981 +622231380104843742133128155420000000000#640352335230# Madison Miller 1121042880000982 +705Snapperpower bonus pay for amazing work on #OSS 00010000982 +622231380104787318153820667250000000000#707374513571# Emily Martin 1121042880000983 +705Markspot bonus pay for amazing work on #OSS 00010000983 +622231380104045722716772631740000000000#545073646248# Charlotte White 1121042880000984 +705Princeevening bonus pay for amazing work on #OSS 00010000984 +622231380104246384625704541160000000000#733130213620# Ethan Wilson 1121042880000985 +705Turnerlight bonus pay for amazing work on #OSS 00010000985 +622231380104256566533585701860000000000#620448307746# James Thomas 1121042880000986 +705Seekerwhip bonus pay for amazing work on #OSS 00010000986 +622231380104426567552214270560000000000#768433284206# Avery Thompson 1121042880000987 +705Scribegranite bonus pay for amazing work on #OSS 00010000987 +622231380104748554433525624100000000000#165440536287# Michael Jones 1121042880000988 +705Frightrelic bonus pay for amazing work on #OSS 00010000988 +622231380104745857375628416750000000000#866061543362# Noah Brown 1121042880000989 +705Stormmetal bonus pay for amazing work on #OSS 00010000989 +622231380104723200338753038840000000000#642832253331# Isabella Moore 1121042880000990 +705Boamidnight bonus pay for amazing work on #OSS 00010000990 +622231380104711264470333270030000000000#502125658887# Ethan Robinson 1121042880000991 +705Fancierlava bonus pay for amazing work on #OSS 00010000991 +622231380104606560686630736870000000000#060112627511# David Wilson 1121042880000992 +705Shiftpsychadelic bonus pay for amazing work on #OSS 00010000992 +622231380104043276734574354770000000000#567477303653# William Thompson 1121042880000993 +705Witchivory bonus pay for amazing work on #OSS 00010000993 +622231380104062120205702482320000000000#287241386788# Joshua Miller 1121042880000994 +705Shoulderrain bonus pay for amazing work on #OSS 00010000994 +622231380104234558531456427430000000000#224771367763# Ava Smith 1121042880000995 +705Howlerjelly bonus pay for amazing work on #OSS 00010000995 +622231380104505824782868687120000000000#401571540747# Zoey White 1121042880000996 +705Tigerfortune bonus pay for amazing work on #OSS 00010000996 +622231380104344801415303888040000000000#611077715318# Addison White 1121042880000997 +705Craneroot bonus pay for amazing work on #OSS 00010000997 +622231380104273115345322663880000000000#643271621063# Olivia Garcia 1121042880000998 +705Seermica bonus pay for amazing work on #OSS 00010000998 +622231380104035086175753073250000000000#186330704176# Anthony Martinez 1121042880000999 +705Daggerfire bonus pay for amazing work on #OSS 00010000999 +622231380104660776074744207830000000000#004833333677# Joshua White 1121042880001000 +705Rippercliff bonus pay for amazing work on #OSS 00010001000 +622231380104858707720612880170000000000#085734773078# Madison Thomas 1121042880001001 +705Weaselnavy bonus pay for amazing work on #OSS 00010001001 +622231380104502772452631870770000000000#156855485638# Sophia Martinez 1121042880001002 +705Salmonvivid bonus pay for amazing work on #OSS 00010001002 +622231380104106834373575125680000000000#688720403113# James Thompson 1121042880001003 +705Fingerspiral bonus pay for amazing work on #OSS 00010001003 +622231380104782874265004520500000000000#668534532687# Avery Moore 1121042880001004 +705Knavebasalt bonus pay for amazing work on #OSS 00010001004 +622231380104570122543315328350000000000#502782046741# David Davis 1121042880001005 +705Flasherclear bonus pay for amazing work on #OSS 00010001005 +622231380104428010723072600730000000000#323218815254# Isabella Harris 1121042880001006 +705Grintundra bonus pay for amazing work on #OSS 00010001006 +622231380104166070413606032430000000000#711273014063# Abigail Harris 1121042880001007 +705Facepine bonus pay for amazing work on #OSS 00010001007 +622231380104454742106306714810000000000#852774816803# Addison Williams 1121042880001008 +705Boarfoam bonus pay for amazing work on #OSS 00010001008 +622231380104220308661178186630000000000#087365774441# Mason Garcia 1121042880001009 +705Bardtyphoon bonus pay for amazing work on #OSS 00010001009 +622231380104538541538052351720000000000#577407181503# Andrew Jackson 1121042880001010 +705Faceplain bonus pay for amazing work on #OSS 00010001010 +622231380104782324488812060380000000000#036431343261# Abigail Thomas 1121042880001011 +705Armclever bonus pay for amazing work on #OSS 00010001011 +622231380104544250210675610640000000000#028182674748# Liam Martinez 1121042880001012 +705Hornsand bonus pay for amazing work on #OSS 00010001012 +622231380104848677627768860050000000000#354163850061# James Miller 1121042880001013 +705Frightroad bonus pay for amazing work on #OSS 00010001013 +622231380104865623128014460330000000000#530314052223# Joshua Thompson 1121042880001014 +705Foeveil bonus pay for amazing work on #OSS 00010001014 +622231380104777366137306548060000000000#681627486772# Zoey Moore 1121042880001015 +705Heronmaple bonus pay for amazing work on #OSS 00010001015 +622231380104630131302301510200000000000#254315145248# Addison Johnson 1121042880001016 +705Snakewave bonus pay for amazing work on #OSS 00010001016 +622231380104316583237430358380000000000#627884123252# Mason Martinez 1121042880001017 +705Vulturepool bonus pay for amazing work on #OSS 00010001017 +622231380104477500726755772600000000000#372552888520# Daniel Williams 1121042880001018 +705Snagglefootmalachite bonus pay for amazing work on #OSS 00010001018 +622231380104647387161608643840000000000#101244840751# Elizabeth Wilson 1121042880001019 +705Hoofchip bonus pay for amazing work on #OSS 00010001019 +622231380104257314614385441160000000000#236075531450# Jayden Davis 1121042880001020 +705Scargravel bonus pay for amazing work on #OSS 00010001020 +622231380104274741607377316560000000000#021407823613# Ava Johnson 1121042880001021 +705Rovereast bonus pay for amazing work on #OSS 00010001021 +622231380104303512363877654280000000000#788124401364# David White 1121042880001022 +705Pumabald bonus pay for amazing work on #OSS 00010001022 +622231380104834054216024167150000000000#082726275853# Natalie Thomas 1121042880001023 +705Sparrowthunder bonus pay for amazing work on #OSS 00010001023 +622231380104033467138672880080000000000#347436586766# Charlotte Jones 1121042880001024 +705Whalepyrite bonus pay for amazing work on #OSS 00010001024 +622231380104501676432268867730000000000#174708673877# Joshua White 1121042880001025 +705Whimseystripe bonus pay for amazing work on #OSS 00010001025 +622231380104565363837036101700000000000#731316814623# William Taylor 1121042880001026 +705Snoutkiwi bonus pay for amazing work on #OSS 00010001026 +622231380104810416044187130750000000000#272017353445# Matthew Moore 1121042880001027 +705Kangarooginger bonus pay for amazing work on #OSS 00010001027 +622231380104311348221736764650000000000#781721820228# Sophia White 1121042880001028 +705Spidercookie bonus pay for amazing work on #OSS 00010001028 +622231380104682532561222787380000000000#111577438403# Jacob Harris 1121042880001029 +705Riderphantom bonus pay for amazing work on #OSS 00010001029 +622231380104831224353628464240000000000#415427141826# Isabella Johnson 1121042880001030 +705Buffaloperidot bonus pay for amazing work on #OSS 00010001030 +622231380104543226154185747780000000000#848783607506# Matthew Anderson 1121042880001031 +705Stonepond bonus pay for amazing work on #OSS 00010001031 +622231380104605686686638822170000000000#535521524318# Anthony Anderson 1121042880001032 +705Hissaquamarine bonus pay for amazing work on #OSS 00010001032 +622231380104763875167578887000000000000#077243134565# Elizabeth Robinson 1121042880001033 +705Devourerpepper bonus pay for amazing work on #OSS 00010001033 +622231380104134463208847673130000000000#387205660330# Madison Wilson 1121042880001034 +705Roveralmond bonus pay for amazing work on #OSS 00010001034 +622231380104832423515262653840000000000#473354344105# William Taylor 1121042880001035 +705Braidsplit bonus pay for amazing work on #OSS 00010001035 +622231380104261358321446723100000000000#311310514285# Sofia Jones 1121042880001036 +705Pigflame bonus pay for amazing work on #OSS 00010001036 +622231380104861788535374701830000000000#054370540466# Jacob Harris 1121042880001037 +705Slayerrhinestone bonus pay for amazing work on #OSS 00010001037 +622231380104487406183118081570000000000#325612721330# Anthony Williams 1121042880001038 +705Razortwisty bonus pay for amazing work on #OSS 00010001038 +622231380104717875830407005700000000000#416074204222# William Taylor 1121042880001039 +705Guardianpeat bonus pay for amazing work on #OSS 00010001039 +622231380104313128270258384070000000000#236457772823# Elijah Williams 1121042880001040 +705Napetitanium bonus pay for amazing work on #OSS 00010001040 +622231380104810210767623518150000000000#802226402087# Joshua Wilson 1121042880001041 +705Gemtree bonus pay for amazing work on #OSS 00010001041 +622231380104651563705313811540000000000#102501064362# Mason Thomas 1121042880001042 +705Wizardshade bonus pay for amazing work on #OSS 00010001042 +622231380104110840446406604230000000000#112736512455# Olivia Anderson 1121042880001043 +705Hairlinen bonus pay for amazing work on #OSS 00010001043 +622231380104883130733806837000000000000#444048806160# Elijah Thompson 1121042880001044 +705Spiderbog bonus pay for amazing work on #OSS 00010001044 +622231380104856737251073473880000000000#176864630562# Anthony Jackson 1121042880001045 +705Monkeyriver bonus pay for amazing work on #OSS 00010001045 +622231380104826834682271217330000000000#000600505022# Aubrey Miller 1121042880001046 +705Foeslime bonus pay for amazing work on #OSS 00010001046 +622231380104045560666101405370000000000#656164301136# Matthew Williams 1121042880001047 +705Guardianalpine bonus pay for amazing work on #OSS 00010001047 +622231380104467204677313835280000000000#372658284720# James Miller 1121042880001048 +705Flyriver bonus pay for amazing work on #OSS 00010001048 +622231380104537787462430882870000000000#746148823873# Benjamin Brown 1121042880001049 +705Knightclever bonus pay for amazing work on #OSS 00010001049 +622231380104260454516848121650000000000#281851033178# Jacob Jones 1121042880001050 +705Thumbswamp bonus pay for amazing work on #OSS 00010001050 +622231380104644401087605405820000000000#586432207880# Emily Martinez 1121042880001051 +705Gorilladawn bonus pay for amazing work on #OSS 00010001051 +622231380104241735827576517380000000000#870560802816# Noah Jackson 1121042880001052 +705Antlerlinen bonus pay for amazing work on #OSS 00010001052 +622231380104525772260104437160000000000#228543221553# Addison Anderson 1121042880001053 +705Terrierrazor bonus pay for amazing work on #OSS 00010001053 +622231380104378541871234417640000000000#300523411312# Chloe Anderson 1121042880001054 +705Pumapitch bonus pay for amazing work on #OSS 00010001054 +622231380104005572652674481220000000000#044151780044# Sophia Johnson 1121042880001055 +705Ladyflax bonus pay for amazing work on #OSS 00010001055 +622231380104581320245554658550000000000#506275851601# Elizabeth Anderson 1121042880001056 +705Runnerflax bonus pay for amazing work on #OSS 00010001056 +622231380104801113884882168870000000000#382516262111# Joseph Harris 1121042880001057 +705Warlockglitter bonus pay for amazing work on #OSS 00010001057 +622231380104741402861010005550000000000#204007718624# Matthew Moore 1121042880001058 +705Frighttar bonus pay for amazing work on #OSS 00010001058 +622231380104188877040516581460000000000#684665234054# Elizabeth Jackson 1121042880001059 +705Whalefluff bonus pay for amazing work on #OSS 00010001059 +622231380104323117638587468810000000000#686167264648# Joseph Martin 1121042880001060 +705Cowltiny bonus pay for amazing work on #OSS 00010001060 +622231380104133535306665333040000000000#765116655278# Mason Moore 1121042880001061 +705Arrowdisco bonus pay for amazing work on #OSS 00010001061 +622231380104255715377422673680000000000#320763650568# Ava White 1121042880001062 +705Waspbuttercup bonus pay for amazing work on #OSS 00010001062 +622231380104483787716030400360000000000#763572442300# Joshua Brown 1121042880001063 +705Knightrogue bonus pay for amazing work on #OSS 00010001063 +622231380104301836681034422220000000000#251220644407# Liam Robinson 1121042880001064 +705Oxsilver bonus pay for amazing work on #OSS 00010001064 +622231380104328473722851674160000000000#143371678438# Lily Moore 1121042880001065 +705Chantersolar bonus pay for amazing work on #OSS 00010001065 +622231380104314304786707170440000000000#568148613135# Elizabeth Moore 1121042880001066 +705Ogreagate bonus pay for amazing work on #OSS 00010001066 +622231380104610670072633026640000000000#443204326500# Lily Miller 1121042880001067 +705Leopardcarnation bonus pay for amazing work on #OSS 00010001067 +622231380104701386108377541140000000000#554232036814# Emily White 1121042880001068 +705Glazerpie bonus pay for amazing work on #OSS 00010001068 +622231380104538303028128311320000000000#437352572630# Natalie Johnson 1121042880001069 +705Riderplanet bonus pay for amazing work on #OSS 00010001069 +622231380104817641054664360220000000000#340731365564# Joshua Taylor 1121042880001070 +705Slavehate bonus pay for amazing work on #OSS 00010001070 +622231380104351422728232318830000000000#257205174151# Zoey Miller 1121042880001071 +705Birdlizard bonus pay for amazing work on #OSS 00010001071 +622231380104381713864355328510000000000#171531621185# Ava Jones 1121042880001072 +705Questerautumn bonus pay for amazing work on #OSS 00010001072 +622231380104174274061362488270000000000#616205140653# Chloe Taylor 1121042880001073 +705Wolfpond bonus pay for amazing work on #OSS 00010001073 +622231380104516012552710725560000000000#823781038052# Elizabeth Thompson 1121042880001074 +705Weavercloud bonus pay for amazing work on #OSS 00010001074 +622231380104635411724085028720000000000#765521216742# Matthew Smith 1121042880001075 +705Ocelotjet bonus pay for amazing work on #OSS 00010001075 +622231380104623833083560102000000000000#726416672417# Mason Martinez 1121042880001076 +705Napequill bonus pay for amazing work on #OSS 00010001076 +622231380104102446301406400840000000000#673664456575# Lily Brown 1121042880001077 +705Flyseed bonus pay for amazing work on #OSS 00010001077 +622231380104755626477277070810000000000#057836154387# Elijah White 1121042880001078 +705Samuraisouth bonus pay for amazing work on #OSS 00010001078 +622231380104560752601288667760000000000#151864416135# Andrew White 1121042880001079 +705Turnerlaser bonus pay for amazing work on #OSS 00010001079 +622231380104467713865207301770000000000#177087516026# Abigail Martinez 1121042880001080 +705Chillerpetal bonus pay for amazing work on #OSS 00010001080 +622231380104026048404732030510000000000#014565474337# Daniel Wilson 1121042880001081 +705Bitenavy bonus pay for amazing work on #OSS 00010001081 +622231380104736546857606257740000000000#320768211042# Noah Johnson 1121042880001082 +705Horsenettle bonus pay for amazing work on #OSS 00010001082 +622231380104081337405317803020000000000#078751570420# Avery Jones 1121042880001083 +705Handleather bonus pay for amazing work on #OSS 00010001083 +622231380104804404282206505200000000000#345814014853# Joshua Anderson 1121042880001084 +705Leadernettle bonus pay for amazing work on #OSS 00010001084 +622231380104458820101351214180000000000#648753075220# Emma Thomas 1121042880001085 +705Ridgestream bonus pay for amazing work on #OSS 00010001085 +622231380104306787147004464110000000000#733570152674# Addison White 1121042880001086 +705Raverthorn bonus pay for amazing work on #OSS 00010001086 +622231380104761640343826046680000000000#614485805123# Elijah Jackson 1121042880001087 +705Ringercomet bonus pay for amazing work on #OSS 00010001087 +622231380104856052038470041470000000000#857817014400# Lily Brown 1121042880001088 +705Antelopegrave bonus pay for amazing work on #OSS 00010001088 +622231380104683226144406357330000000000#426355272854# Matthew Smith 1121042880001089 +705Collargeode bonus pay for amazing work on #OSS 00010001089 +622231380104132516780013422600000000000#408251246586# Ava Jones 1121042880001090 +705Chillerbrindle bonus pay for amazing work on #OSS 00010001090 +622231380104177068177320682120000000000#028848867688# Emily Williams 1121042880001091 +705Antcosmic bonus pay for amazing work on #OSS 00010001091 +622231380104456548316244556070000000000#718676330343# Chloe Martin 1121042880001092 +705Glasssand bonus pay for amazing work on #OSS 00010001092 +622231380104261311084873841730000000000#724231008025# Ava Williams 1121042880001093 +705Kangarooorange bonus pay for amazing work on #OSS 00010001093 +622231380104751100614232884100000000000#030868041826# Zoey Taylor 1121042880001094 +705Tigerroan bonus pay for amazing work on #OSS 00010001094 +622231380104140680470328880220000000000#056746852450# Elizabeth Anderson 1121042880001095 +705Eagleflame bonus pay for amazing work on #OSS 00010001095 +622231380104544240751457315730000000000#272754747231# Aiden Moore 1121042880001096 +705Condorcitrine bonus pay for amazing work on #OSS 00010001096 +622231380104400065763823141150000000000#854778558858# Elijah Garcia 1121042880001097 +705Butterflyjasper bonus pay for amazing work on #OSS 00010001097 +622231380104321468254675704730000000000#207122424034# Daniel Brown 1121042880001098 +705Questerequinox bonus pay for amazing work on #OSS 00010001098 +622231380104515835306457456410000000000#385485211314# Chloe Garcia 1121042880001099 +705Dukestitch bonus pay for amazing work on #OSS 00010001099 +622231380104812625251258822010000000000#648241878641# Mason Martin 1121042880001100 +705Ducknavy bonus pay for amazing work on #OSS 00010001100 +622231380104256271012151517170000000000#702202581765# Lily Davis 1121042880001101 +705Footred bonus pay for amazing work on #OSS 00010001101 +622231380104116264705574880510000000000#513524500584# Madison Davis 1121042880001102 +705Zebrashell bonus pay for amazing work on #OSS 00010001102 +622231380104026610651885812430000000000#336444734434# Joseph Jones 1121042880001103 +705Roarlead bonus pay for amazing work on #OSS 00010001103 +622231380104754606833025082280000000000#488378761277# Charlotte Miller 1121042880001104 +705Collarpineapple bonus pay for amazing work on #OSS 00010001104 +622231380104247272061623350270000000000#186042543338# Benjamin Garcia 1121042880001105 +705Gargoylezircon bonus pay for amazing work on #OSS 00010001105 +622231380104342176354433556270000000000#756328540785# Benjamin Thompson 1121042880001106 +705Musefate bonus pay for amazing work on #OSS 00010001106 +622231380104068143370587641640000000000#242620005664# Noah Johnson 1121042880001107 +705Sentrysky bonus pay for amazing work on #OSS 00010001107 +622231380104801786614246305430000000000#360366156762# Liam Jackson 1121042880001108 +705Glasspsychadelic bonus pay for amazing work on #OSS 00010001108 +622231380104024376025635366520000000000#683750232127# Aiden Thomas 1121042880001109 +705Mothshy bonus pay for amazing work on #OSS 00010001109 +622231380104506484854433357160000000000#364116024581# Chloe Taylor 1121042880001110 +705Parrotplaid bonus pay for amazing work on #OSS 00010001110 +622231380104503863623164160300000000000#431252748477# Elizabeth Martin 1121042880001111 +705Tigerfair bonus pay for amazing work on #OSS 00010001111 +622231380104327072147657260120000000000#278165580867# James White 1121042880001112 +705Cougarsnow bonus pay for amazing work on #OSS 00010001112 +622231380104131618141276303050000000000#614675472845# Abigail Davis 1121042880001113 +705Weednarrow bonus pay for amazing work on #OSS 00010001113 +622231380104851708488658288460000000000#626327711315# Elijah Johnson 1121042880001114 +705Scalestar bonus pay for amazing work on #OSS 00010001114 +622231380104641715673453874130000000000#157357585766# William Thomas 1121042880001115 +705Yakhoney bonus pay for amazing work on #OSS 00010001115 +622231380104163104561687811860000000000#875801745066# Abigail Anderson 1121042880001116 +705Gemcomet bonus pay for amazing work on #OSS 00010001116 +622231380104038781431367328660000000000#143601726471# Madison Anderson 1121042880001117 +705Bearsoft bonus pay for amazing work on #OSS 00010001117 +622231380104166785386300377540000000000#286512707645# Andrew Jackson 1121042880001118 +705Legsfair bonus pay for amazing work on #OSS 00010001118 +622231380104550426012065083300000000000#731781634416# Jayden Moore 1121042880001119 +705Sparrowharvest bonus pay for amazing work on #OSS 00010001119 +622231380104273361186841811030000000000#878632871527# Ethan Wilson 1121042880001120 +705Toucheast bonus pay for amazing work on #OSS 00010001120 +622231380104426824368677552310000000000#204882431741# Charlotte Martin 1121042880001121 +705Kickersprinkle bonus pay for amazing work on #OSS 00010001121 +622231380104443104808540318450000000000#750500748878# Emma Thompson 1121042880001122 +705Grabberperidot bonus pay for amazing work on #OSS 00010001122 +622231380104773027362543510410000000000#684835012856# Daniel Harris 1121042880001123 +705Griffinlace bonus pay for amazing work on #OSS 00010001123 +622231380104278754433443711310000000000#305352500665# Joshua Thompson 1121042880001124 +705Raccoonbush bonus pay for amazing work on #OSS 00010001124 +622231380104448525522613407300000000000#454318032805# Emma Garcia 1121042880001125 +705Zebraregal bonus pay for amazing work on #OSS 00010001125 +622231380104327573044124546140000000000#405370840355# Elijah Smith 1121042880001126 +705Musebubble bonus pay for amazing work on #OSS 00010001126 +622231380104707440617663611440000000000#514850847883# Mia Smith 1121042880001127 +705Mothnight bonus pay for amazing work on #OSS 00010001127 +622231380104470245105771253000000000000#351800327873# Elijah Anderson 1121042880001128 +705Razorbutter bonus pay for amazing work on #OSS 00010001128 +622231380104620811155312051350000000000#100113558543# Joshua White 1121042880001129 +705Shiftmad bonus pay for amazing work on #OSS 00010001129 +622231380104475375585433336170000000000#385368178277# Madison Harris 1121042880001130 +705Carppolar bonus pay for amazing work on #OSS 00010001130 +622231380104251017185317216340000000000#657773012177# Sophia Johnson 1121042880001131 +705Jackalfree bonus pay for amazing work on #OSS 00010001131 +622231380104076270317140025460000000000#222730084017# Aubrey Taylor 1121042880001132 +705Dancerpine bonus pay for amazing work on #OSS 00010001132 +622231380104846726425327070670000000000#450135158374# Isabella Martinez 1121042880001133 +705Crowncharm bonus pay for amazing work on #OSS 00010001133 +622231380104262342081505727320000000000#145835151612# Charlotte Davis 1121042880001134 +705Shieldsprout bonus pay for amazing work on #OSS 00010001134 +622231380104062077067150474030000000000#061323334472# Jacob Jackson 1121042880001135 +705Shieldivy bonus pay for amazing work on #OSS 00010001135 +622231380104406470434330231540000000000#260533142171# Lily Taylor 1121042880001136 +705Givermesquite bonus pay for amazing work on #OSS 00010001136 +622231380104462333178003458720000000000#730705516841# Liam Martin 1121042880001137 +705Crestkiwi bonus pay for amazing work on #OSS 00010001137 +622231380104510873003665640350000000000#641163750535# Elijah Moore 1121042880001138 +705Storktin bonus pay for amazing work on #OSS 00010001138 +622231380104828721306422436710000000000#524602061172# Mason Wilson 1121042880001139 +705Horselava bonus pay for amazing work on #OSS 00010001139 +622231380104315025281315314720000000000#776238847267# Emily Thompson 1121042880001140 +705Bisonshadow bonus pay for amazing work on #OSS 00010001140 +622231380104101586407844386340000000000#882366282304# Elijah Thompson 1121042880001141 +705Cubtyphoon bonus pay for amazing work on #OSS 00010001141 +622231380104858000134314553630000000000#501171275802# Jacob Jones 1121042880001142 +705Chestfog bonus pay for amazing work on #OSS 00010001142 +622231380104115426848545282860000000000#867517765071# Liam Thomas 1121042880001143 +705Griffinisland bonus pay for amazing work on #OSS 00010001143 +622231380104485112688006732130000000000#518128147422# Olivia Jackson 1121042880001144 +705Fairypeppermint bonus pay for amazing work on #OSS 00010001144 +622231380104112541710350178080000000000#238418114387# Avery Robinson 1121042880001145 +705Paintershy bonus pay for amazing work on #OSS 00010001145 +622231380104274047251341650740000000000#641868243478# Benjamin White 1121042880001146 +705Grinkeen bonus pay for amazing work on #OSS 00010001146 +622231380104153621804275577760000000000#765887482325# Elijah Martinez 1121042880001147 +705Flashertabby bonus pay for amazing work on #OSS 00010001147 +622231380104342864816265750470000000000#416873852508# James Harris 1121042880001148 +705Swoopwool bonus pay for amazing work on #OSS 00010001148 +622231380104468871678784525310000000000#660580230182# Michael Garcia 1121042880001149 +705Herobrass bonus pay for amazing work on #OSS 00010001149 +622231380104373060748163744710000000000#648175601525# Isabella Martin 1121042880001150 +705Crystaltitanium bonus pay for amazing work on #OSS 00010001150 +622231380104048388547303451260000000000#456185077711# Aiden Davis 1121042880001151 +705Bardrogue bonus pay for amazing work on #OSS 00010001151 +622231380104011831706283027180000000000#466064287076# Noah Taylor 1121042880001152 +705Legfancy bonus pay for amazing work on #OSS 00010001152 +622231380104738040255073545240000000000#155601487758# Ava Moore 1121042880001153 +705Dragonleather bonus pay for amazing work on #OSS 00010001153 +622231380104831306288076048610000000000#206613131273# Abigail Taylor 1121042880001154 +705Chatterfast bonus pay for amazing work on #OSS 00010001154 +622231380104867586430532888040000000000#685470460386# Lily Thompson 1121042880001155 +705Scalebronze bonus pay for amazing work on #OSS 00010001155 +622231380104502854272267703710000000000#674733073038# Mia Johnson 1121042880001156 +705Stingerplume bonus pay for amazing work on #OSS 00010001156 +622231380104727718677212345210000000000#524333215740# Chloe Jackson 1121042880001157 +705Scribepink bonus pay for amazing work on #OSS 00010001157 +622231380104456484784650406720000000000#022205022663# Elijah Jackson 1121042880001158 +705Slavesilk bonus pay for amazing work on #OSS 00010001158 +622231380104052833011772060570000000000#327788181153# Charlotte Martin 1121042880001159 +705Legivy bonus pay for amazing work on #OSS 00010001159 +622231380104763727073061544530000000000#275701502330# Lily Thomas 1121042880001160 +705Tongueband bonus pay for amazing work on #OSS 00010001160 +622231380104218025177551551380000000000#317722132871# Ethan Jones 1121042880001161 +705Moledent bonus pay for amazing work on #OSS 00010001161 +622231380104877087117357284860000000000#675471827514# Sofia Williams 1121042880001162 +705Seedplatinum bonus pay for amazing work on #OSS 00010001162 +622231380104762257032433186720000000000#851800675832# Ethan Robinson 1121042880001163 +705Flycypress bonus pay for amazing work on #OSS 00010001163 +622231380104816465026037400370000000000#024206376554# Avery Thompson 1121042880001164 +705Trackermidnight bonus pay for amazing work on #OSS 00010001164 +622231380104686312238580533650000000000#758088548764# Jayden Jones 1121042880001165 +705Witchvivid bonus pay for amazing work on #OSS 00010001165 +622231380104867016832740550070000000000#621440200084# Lily Thomas 1121042880001166 +705Shouldermaple bonus pay for amazing work on #OSS 00010001166 +622231380104640714817157278130000000000#017321147743# Benjamin Wilson 1121042880001167 +705Bowrain bonus pay for amazing work on #OSS 00010001167 +622231380104263828261605003180000000000#787823674634# Chloe White 1121042880001168 +705Armfancy bonus pay for amazing work on #OSS 00010001168 +622231380104340353532408408620000000000#006550867328# Alexander Robinson 1121042880001169 +705Iguanabrave bonus pay for amazing work on #OSS 00010001169 +622231380104604227200843125210000000000#678156018262# Noah Thomas 1121042880001170 +705Wyrmpuzzle bonus pay for amazing work on #OSS 00010001170 +622231380104232837777158563770000000000#586655624051# Michael Garcia 1121042880001171 +705Bootbush bonus pay for amazing work on #OSS 00010001171 +622231380104033537778422420050000000000#808041108206# Charlotte White 1121042880001172 +705Shrieknarrow bonus pay for amazing work on #OSS 00010001172 +622231380104282286863270547620000000000#167561235123# Olivia Taylor 1121042880001173 +705Antlerorange bonus pay for amazing work on #OSS 00010001173 +622231380104650678377644686650000000000#828631283747# Chloe Martin 1121042880001174 +705Deathcherry bonus pay for amazing work on #OSS 00010001174 +622231380104607106528212147530000000000#330257876123# Andrew Thompson 1121042880001175 +705Mistressspring bonus pay for amazing work on #OSS 00010001175 +622231380104406462363540860200000000000#210628663635# Chloe Taylor 1121042880001176 +705Raccoonarrow bonus pay for amazing work on #OSS 00010001176 +622231380104300850335818504620000000000#072585034737# Michael Garcia 1121042880001177 +705Howlerdark bonus pay for amazing work on #OSS 00010001177 +622231380104828412712152683330000000000#632656845373# Mia White 1121042880001178 +705Stingnotch bonus pay for amazing work on #OSS 00010001178 +622231380104375857675132526180000000000#227148084175# Avery Williams 1121042880001179 +705Mothshade bonus pay for amazing work on #OSS 00010001179 +622231380104755720407557484030000000000#788540817623# James Martinez 1121042880001180 +705Thumbshadow bonus pay for amazing work on #OSS 00010001180 +622231380104304721857675072050000000000#143248318012# Aiden Wilson 1121042880001181 +705Puppyscratch bonus pay for amazing work on #OSS 00010001181 +622231380104686275856140623300000000000#023752464807# Michael Williams 1121042880001182 +705Flyfair bonus pay for amazing work on #OSS 00010001182 +622231380104218565167022163180000000000#764713810150# Noah Robinson 1121042880001183 +705Razorvolcano bonus pay for amazing work on #OSS 00010001183 +622231380104506222730874030410000000000#222157431268# Ava Smith 1121042880001184 +705Unicornzircon bonus pay for amazing work on #OSS 00010001184 +622231380104338873278477254680000000000#653370472676# Benjamin Martin 1121042880001185 +705Twistershell bonus pay for amazing work on #OSS 00010001185 +622231380104568440381017740750000000000#524418528117# Lily Brown 1121042880001186 +705Songzinc bonus pay for amazing work on #OSS 00010001186 +622231380104620878734123081170000000000#146012181814# Addison Jones 1121042880001187 +705Hawknickel bonus pay for amazing work on #OSS 00010001187 +622231380104625861014312403030000000000#600525426863# Emma Jones 1121042880001188 +705Ocelotdent bonus pay for amazing work on #OSS 00010001188 +622231380104738318744128170540000000000#753468278460# Olivia Davis 1121042880001189 +705Cockatootorch bonus pay for amazing work on #OSS 00010001189 +622231380104167431466155456210000000000#008261002661# Ella Harris 1121042880001190 +705Crestkiwi bonus pay for amazing work on #OSS 00010001190 +622231380104026363034127541700000000000#078412113358# Aubrey Martin 1121042880001191 +705Mouselace bonus pay for amazing work on #OSS 00010001191 +622231380104845455714876171430000000000#275222860087# Joseph Brown 1121042880001192 +705Spriteginger bonus pay for amazing work on #OSS 00010001192 +622231380104013042063147587270000000000#641865582487# Andrew Garcia 1121042880001193 +705Shoulderemerald bonus pay for amazing work on #OSS 00010001193 +622231380104687606721882233250000000000#683068240856# Zoey Williams 1121042880001194 +705Knifesand bonus pay for amazing work on #OSS 00010001194 +622231380104466410671144116100000000000#623025373500# Emma Moore 1121042880001195 +705Roachnickel bonus pay for amazing work on #OSS 00010001195 +622231380104604364023551341030000000000#072576175155# Anthony Johnson 1121042880001196 +705Princefringe bonus pay for amazing work on #OSS 00010001196 +622231380104508748605831433440000000000#744264744860# Anthony Martinez 1121042880001197 +705Foxgossamer bonus pay for amazing work on #OSS 00010001197 +622231380104405078106003027620000000000#884531556042# Ella Brown 1121042880001198 +705Facegem bonus pay for amazing work on #OSS 00010001198 +622231380104188500660378322660000000000#701037066751# Elizabeth Jones 1121042880001199 +705Bellychip bonus pay for amazing work on #OSS 00010001199 +622231380104023767143642865750000000000#431224441062# Olivia Martin 1121042880001200 +705Leadercrystal bonus pay for amazing work on #OSS 00010001200 +622231380104352585277026733600000000000#730182102160# Liam Wilson 1121042880001201 +705Scribenotch bonus pay for amazing work on #OSS 00010001201 +622231380104366308365563872470000000000#420263421721# Elijah Thomas 1121042880001202 +705Oxcopper bonus pay for amazing work on #OSS 00010001202 +622231380104235802135168068030000000000#714348164124# Natalie Jones 1121042880001203 +705Riderrust bonus pay for amazing work on #OSS 00010001203 +622231380104828887386264117030000000000#176368545028# Daniel Robinson 1121042880001204 +705Forgerebony bonus pay for amazing work on #OSS 00010001204 +622231380104333047624600358560000000000#311256254846# Natalie Jones 1121042880001205 +705Haresapphire bonus pay for amazing work on #OSS 00010001205 +622231380104313655285228854180000000000#684043625661# Ethan Martin 1121042880001206 +705Chargerpink bonus pay for amazing work on #OSS 00010001206 +622231380104820503814634727650000000000#228757366324# Ava Robinson 1121042880001207 +705Swallowcyber bonus pay for amazing work on #OSS 00010001207 +622231380104151826685000755870000000000#645267530213# Ella White 1121042880001208 +705Cockatoosatin bonus pay for amazing work on #OSS 00010001208 +622231380104051322701031036080000000000#860170514451# Olivia White 1121042880001209 +705Swordpaint bonus pay for amazing work on #OSS 00010001209 +622231380104585440811822204540000000000#626178065477# Sophia Harris 1121042880001210 +705Venomcrocus bonus pay for amazing work on #OSS 00010001210 +622231380104726380183748553550000000000#804667565088# Jayden Miller 1121042880001211 +705Whimseycarnelian bonus pay for amazing work on #OSS 00010001211 +622231380104072003128456331620000000000#878715106786# Anthony Moore 1121042880001212 +705Herohail bonus pay for amazing work on #OSS 00010001212 +622231380104754166673130483700000000000#624046283665# William Brown 1121042880001213 +705Liftercopper bonus pay for amazing work on #OSS 00010001213 +622231380104863263123620444280000000000#461313254523# Daniel Johnson 1121042880001214 +705Neckripple bonus pay for amazing work on #OSS 00010001214 +622231380104574601825448450050000000000#652742217206# David Williams 1121042880001215 +705Cloakdog bonus pay for amazing work on #OSS 00010001215 +622231380104843831206884240750000000000#208004848223# Elizabeth Martin 1121042880001216 +705Lizardfern bonus pay for amazing work on #OSS 00010001216 +622231380104682017481464503140000000000#318675127585# James Robinson 1121042880001217 +705Deerroot bonus pay for amazing work on #OSS 00010001217 +622231380104865260343755882050000000000#581126826553# Matthew Jones 1121042880001218 +705Gemkeen bonus pay for amazing work on #OSS 00010001218 +622231380104364682785503860800000000000#242811244788# Natalie Johnson 1121042880001219 +705Grincute bonus pay for amazing work on #OSS 00010001219 +622231380104020534502785032730000000000#448876048104# Olivia Thompson 1121042880001220 +705Headrazor bonus pay for amazing work on #OSS 00010001220 +622231380104310008481308106220000000000#374031115648# Isabella Martinez 1121042880001221 +705Stalliondaffodil bonus pay for amazing work on #OSS 00010001221 +622231380104755077583885636800000000000#570502001587# Ava Jackson 1121042880001222 +705Masterpuddle bonus pay for amazing work on #OSS 00010001222 +622231380104371047248664301820000000000#618767034743# Zoey Jackson 1121042880001223 +705Foecherry bonus pay for amazing work on #OSS 00010001223 +622231380104058628602764177080000000000#446617484815# Emily Martin 1121042880001224 +705Hornshadow bonus pay for amazing work on #OSS 00010001224 +622231380104717156885453203180000000000#303525885627# Natalie Jones 1121042880001225 +705Shriekerdew bonus pay for amazing work on #OSS 00010001225 +622231380104032312630700446210000000000#488454721523# Ava Anderson 1121042880001226 +705Doompink bonus pay for amazing work on #OSS 00010001226 +622231380104685103643526858150000000000#183784854476# Benjamin Smith 1121042880001227 +705Kangaroobone bonus pay for amazing work on #OSS 00010001227 +622231380104633071011042447250000000000#046364532871# Elijah White 1121042880001228 +705Ribboom bonus pay for amazing work on #OSS 00010001228 +622231380104638207361674623340000000000#870354411511# Aiden Brown 1121042880001229 +705Collarobsidian bonus pay for amazing work on #OSS 00010001229 +622231380104363741075437654350000000000#247620125614# Chloe Miller 1121042880001230 +705Tracksapphire bonus pay for amazing work on #OSS 00010001230 +622231380104216526313476354240000000000#783747342115# Jayden Johnson 1121042880001231 +705Bowtree bonus pay for amazing work on #OSS 00010001231 +622231380104177221088446880020000000000#111487480283# Andrew Martin 1121042880001232 +705Shieldquartz bonus pay for amazing work on #OSS 00010001232 +622231380104304573854338284280000000000#808850687687# Anthony Jones 1121042880001233 +705Mothberyl bonus pay for amazing work on #OSS 00010001233 +622231380104053727616230337730000000000#272505786223# Liam Jackson 1121042880001234 +705Playerviridian bonus pay for amazing work on #OSS 00010001234 +622231380104271366275385575300000000000#021376388667# Michael Jones 1121042880001235 +705Warlockglass bonus pay for amazing work on #OSS 00010001235 +622231380104546855561607856320000000000#314360627847# Ava Thompson 1121042880001236 +705Beakshell bonus pay for amazing work on #OSS 00010001236 +622231380104500403370117011770000000000#231237122675# Noah Johnson 1121042880001237 +705Hawkribbon bonus pay for amazing work on #OSS 00010001237 +622231380104003083322484738020000000000#167825783607# James Anderson 1121042880001238 +705Kittenbramble bonus pay for amazing work on #OSS 00010001238 +622231380104176100540743268010000000000#880246530637# Mia Martinez 1121042880001239 +705Stealerflame bonus pay for amazing work on #OSS 00010001239 +622231380104541848538038104350000000000#736004763221# Ella Davis 1121042880001240 +705Hornfast bonus pay for amazing work on #OSS 00010001240 +622231380104033858477484225680000000000#277665636802# Mia Garcia 1121042880001241 +705Graspweak bonus pay for amazing work on #OSS 00010001241 +622231380104413115738360085180000000000#270223667068# James Miller 1121042880001242 +705Queendandy bonus pay for amazing work on #OSS 00010001242 +622231380104645462655656007100000000000#741686812745# Anthony Harris 1121042880001243 +705Weaselwhip bonus pay for amazing work on #OSS 00010001243 +622231380104782154135251531650000000000#261270251823# Lily Williams 1121042880001244 +705Chestgem bonus pay for amazing work on #OSS 00010001244 +622231380104674503246647742660000000000#264108448367# Avery Garcia 1121042880001245 +705Capprairie bonus pay for amazing work on #OSS 00010001245 +622231380104842314776106518740000000000#101165330885# Mia Anderson 1121042880001246 +705Venomspring bonus pay for amazing work on #OSS 00010001246 +622231380104823743634450140530000000000#328066776418# Benjamin Brown 1121042880001247 +705Runnerrogue bonus pay for amazing work on #OSS 00010001247 +622231380104065621586022257410000000000#152785273811# Jayden Martinez 1121042880001248 +705Savermotley bonus pay for amazing work on #OSS 00010001248 +622231380104571036615647241810000000000#687076768803# Addison Martin 1121042880001249 +705Facegrave bonus pay for amazing work on #OSS 00010001249 +622231380104462832144331634280000000000#212283282432# Liam Jones 1121042880001250 +705Snapginger bonus pay for amazing work on #OSS 00010001250 +82000025008922512500000000000000000000000000121042882 121042880000004 +9000004001001000100005690050000000000000000000000000000 diff --git a/cmd/writeACH/main.go b/cmd/writeACH/main.go new file mode 100644 index 000000000..6efc70630 --- /dev/null +++ b/cmd/writeACH/main.go @@ -0,0 +1,160 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "encoding/json" + "flag" + "fmt" + "log" + "os" + "path/filepath" + "runtime/pprof" + "time" + + "github.com/Pallinder/go-randomdata" + "github.com/moov-io/ach" +) + +var ( + fPath = flag.String("fPath", "", "File Path") + cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") + + // output formats + flagJson = flag.Bool("json", false, "Output file in json") +) + +// main creates an ACH File with 4 batches of SEC Code PPD. +// Each batch contains an EntryAddendaCount of 2500. +func main() { + flag.Parse() + + filename := time.Now().UTC().Format("200601021504") + if *flagJson { + filename += ".json" + } else { + filename += ".ach" + } + + path := filepath.Join(*fPath, filename) + write(path) +} + +func write(path string) { + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + log.Fatal(err) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + + f, err := os.Create(path) + if err != nil { + fmt.Printf("%T: %s", err, err) + } + + // To create a file + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" + fh.ImmediateOrigin = "121042882" + fh.FileCreationDate = time.Now().Format("060102") + fh.ImmediateDestinationName = "Citadel" + fh.ImmediateOriginName = "Wells Fargo" + file := ach.NewFile() + file.SetHeader(fh) + + // Create 4 Batches of SEC Code PPD + for i := 0; i < 4; i++ { + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.MixedDebitsAndCredits + bh.CompanyName = "Wells Fargo" + bh.CompanyIdentification = "121042882" + bh.StandardEntryClassCode = ach.PPD + bh.CompanyEntryDescription = "Trans. Description" + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") + bh.ODFIIdentification = "121042882" + + batch, _ := ach.NewBatch(bh) + + // Create Entry + entrySeq := 0 + for i := 0; i < 1250; i++ { + entrySeq = entrySeq + 1 + + entryEntrySeq := ach.NewEntryDetail() + entryEntrySeq.TransactionCode = ach.CheckingCredit + entryEntrySeq.SetRDFI("231380104") + entryEntrySeq.DFIAccountNumber = randomdata.StringNumber(10, "") + entryEntrySeq.IndividualName = randomdata.FullName(randomdata.RandomGender) + entryEntrySeq.SetTraceNumber(bh.ODFIIdentification, entrySeq) + entryEntrySeq.IdentificationNumber = "#" + randomdata.StringNumber(6, "") + "#" + entryEntrySeq.Category = ach.CategoryForward + entryEntrySeq.AddendaRecordIndicator = 1 + + // Add addenda record for an entry + addendaEntrySeq := ach.NewAddenda05() + addendaEntrySeq.PaymentRelatedInformation = randomdata.SillyName() + " bonus pay for amazing work on #OSS" + entryEntrySeq.AddAddenda05(addendaEntrySeq) + + // Add entries + batch.AddEntry(entryEntrySeq) + + } + + // Create the batch. + if err := batch.Create(); err != nil { + fmt.Printf("%T: %s", err, err) + } + + // Add batch to the file + file.AddBatch(batch) + } + + // ensure we have a validated file structure + if file.Validate(); err != nil { + fmt.Printf("Could not validate entire file: %v", err) + } + + // Create the file + if err := file.Create(); err != nil { + fmt.Printf("%T: %s", err, err) + } + + // Write to a file + if *flagJson { + // Write in JSON format + if err := json.NewEncoder(f).Encode(file); err != nil { + fmt.Printf("%T: %s", err, err) + } + } else { + // Write in ACH plain text format + w := ach.NewWriter(f) + if err := w.Write(file); err != nil { + fmt.Printf("%T: %s", err, err) + } + w.Flush() + } + + if err := f.Close(); err != nil { + fmt.Println(err.Error()) + } + + fmt.Printf("Wrote %s\n", path) +} diff --git a/cmd/writeACH/main_test.go b/cmd/writeACH/main_test.go new file mode 100644 index 000000000..36de438da --- /dev/null +++ b/cmd/writeACH/main_test.go @@ -0,0 +1,55 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "os" + "testing" +) + +// TestFileCreate tests creating an ACH File +func TestFileWrite(t *testing.T) { + testFileWrite(t) +} + +/*//BenchmarkTestFileCreate benchmarks creating an ACH File +func BenchmarkTestFileWrite(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileWrite(b) + } +}*/ + +// FileCreate creates an ACH File +func testFileWrite(t testing.TB) { + filename, err := os.MkdirTemp("", "ach-writeACH-test") + if err != nil { + t.Fatal(err.Error()) + } + defer os.Remove(filename) + + write(filename) + + s, err := os.Stat(filename) + if err != nil { + t.Fatal(err.Error()) + } + if s.Size() <= 0 { + t.Fatal("expected non-empty file") + } +} diff --git a/converters.go b/converters.go index 0f74b2255..ea07dc6cf 100644 --- a/converters.go +++ b/converters.go @@ -1,13 +1,26 @@ -// Copyright 2017 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. package ach import ( + "math" "strconv" "strings" - "time" ) // converters handles golang to ACH type Converters @@ -18,30 +31,27 @@ func (c *converters) parseNumField(r string) (s int) { return s } -// formatSimpleDate takes a time.Time and returns a string of YYMMDD -func (c *converters) formatSimpleDate(t time.Time) string { - return t.Format("060102") -} - -// parseSimpleDate returns a time.Time when passed time as YYMMDD -func (c *converters) parseSimpleDate(s string) time.Time { - t, _ := time.Parse("060102", s) - return t +func (c *converters) parseStringField(r string) (s string) { + s = strings.TrimSpace(r) + return s } -// formatSimpleTime returns a string of HHMM when passed a time.Time -func (c *converters) formatSimpleTime(t time.Time) string { - return t.Format("1504") +// formatSimpleDate takes a YYMMDD date and formats it for the fixed-width ACH file format +func (c *converters) formatSimpleDate(s string) string { + if s == "" { + return c.stringField(s, 6) + } + return s } -// parseSimpleTime returns a time.Time when passed a string of HHMM -func (c *converters) parseSimpleTime(s string) time.Time { - t, _ := time.Parse("1504", s) - return t +// formatSimpleTime takes a HHmm (H=hour, m=minute) time and formats it for the fixed-width ACH file format +func (c *converters) formatSimpleTime(s string) string { + if s == "" { + return c.stringField(s, 4) + } + return s } -//func (v *Converters) numericField() - // alphaField Alphanumeric and Alphabetic fields are left-justified and space filled. func (c *converters) alphaField(s string, max uint) string { ln := uint(len(s)) @@ -52,7 +62,7 @@ func (c *converters) alphaField(s string, max uint) string { return s } -// numericField right-justified, unisigned, and zero filled +// numericField right-justified, unsigned, and zero filled func (c *converters) numericField(n int, max uint) string { s := strconv.Itoa(n) ln := uint(len(s)) @@ -62,3 +72,18 @@ func (c *converters) numericField(n int, max uint) string { s = strings.Repeat("0", int(max-ln)) + s return s } + +// stringField slices to max length and zero filled +func (c *converters) stringField(s string, max uint) string { + ln := uint(len(s)) + if ln > max { + return s[:max] + } + s = strings.Repeat("0", int(max-ln)) + s + return s +} + +// leastSignificantDigits returns the least significant digits of v limited by maxDigits. +func (c *converters) leastSignificantDigits(v int, maxDigits uint) int { + return v % int(math.Pow10(int(maxDigits))) +} diff --git a/converters_test.go b/converters_test.go index cf57646b8..37a23e24e 100644 --- a/converters_test.go +++ b/converters_test.go @@ -1,13 +1,26 @@ -// Copyright 2017 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. package ach import "testing" -//testAlphaField ensire that padding and two long of strings get properly made -func TestAlphaFieldShort(t *testing.T) { +// testAlphaField ensures that padding and two long of strings get properly made +func testAlphaFieldShort(t testing.TB) { c := converters{} result := c.alphaField("ABC123", 10) if result != "ABC123 " { @@ -15,8 +28,21 @@ func TestAlphaFieldShort(t *testing.T) { } } -// TestAlphaFieldLong ensure that string is left justified and sliced to max -func TestAlphaFieldLong(t *testing.T) { +// TestAlphaFieldShort test ensures that padding and two long of strings get properly made +func TestAlphaFieldShort(t *testing.T) { + testAlphaFieldShort(t) +} + +// BenchmarkAlphaFieldShort benchmark ensures that padding and two long of strings get properly made +func BenchmarkAlphaFieldShort(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAlphaFieldShort(b) + } +} + +// testAlphaFieldLong ensures that string is left justified and sliced to max +func testAlphaFieldLong(t testing.TB) { c := converters{} result := c.alphaField("abcdEFGH123", 10) if result != "abcdEFGH12" { @@ -24,8 +50,21 @@ func TestAlphaFieldLong(t *testing.T) { } } -// TestNumericFieldShort ensures zero padding and right justified -func TestNumericFieldShort(t *testing.T) { +// TestAlphaFieldLong test ensures that string is left justified and sliced to max +func TestAlphaFieldLong(t *testing.T) { + testAlphaFieldLong(t) +} + +// BenchmarkAlphaFieldLong benchmark ensures that string is left justified and sliced to max +func BenchmarkAlphaFieldLong(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAlphaFieldLong(b) + } +} + +// testNumericFieldShort ensures zero padding and right justified +func testNumericFieldShort(t testing.TB) { c := converters{} result := c.numericField(12345, 10) if result != "0000012345" { @@ -33,8 +72,21 @@ func TestNumericFieldShort(t *testing.T) { } } -// TestNumericFieldLong right justified and sliced to max length -func TestNumericFieldLong(t *testing.T) { +// TestNumericFieldShort test ensures zero padding and right justified +func TestNumericFieldShort(t *testing.T) { + testNumericFieldShort(t) +} + +// BenchmarkNumericFieldShort benchmark ensures zero padding and right justified +func BenchmarkNumericFieldShort(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testNumericFieldShort(b) + } +} + +// testNumericFieldLong ensures right justified and sliced to max length +func testNumericFieldLong(t testing.TB) { c := converters{} result := c.numericField(123456, 5) if result != "23456" { @@ -42,11 +94,166 @@ func TestNumericFieldLong(t *testing.T) { } } -//TestParseNumField handle zero and spaces in number conversion -func TestParseNumField(t *testing.T) { +// TestNumericFieldLong test ensures right justified and sliced to max length +func TestNumericFieldLong(t *testing.T) { + testNumericFieldLong(t) +} + +// BenchmarkNumericFieldLong benchmark ensures right justified and sliced to max length +func BenchmarkNumericFieldLong(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testNumericFieldLong(b) + } +} + +// testParseNumField handles zero and spaces in number conversion +func testParseNumField(t testing.TB) { c := converters{} result := c.parseNumField(" 012345") if result != 12345 { t.Errorf("Right justified zero got: '%v'", result) } } + +// TestParseNumField test handles zero and spaces in number conversion +func TestParseNumField(t *testing.T) { + testParseNumField(t) +} + +// BenchmarkParseNumField benchmark handles zero and spaces in number conversion +func BenchmarkParseNumField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testParseNumField(b) + } +} + +// testParseStringField handles spaces in string conversion +func testParseStringField(t testing.TB) { + c := converters{} + result := c.parseStringField(" 012345") + if result != "012345" { + t.Errorf("Trim spaces: '%v'", result) + } +} + +// TestParseStringField test handles spaces in string conversion +func TestParseStringField(t *testing.T) { + testParseStringField(t) +} + +// BenchmarkParseStringField benchmark handles spaces in string conversion +func BenchmarkParseStringField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testParseStringField(b) + } +} + +// testRTNFieldShort ensures zero padding and right justified +func testRTNFieldShort(t testing.TB) { + c := converters{} + result := c.stringField("123456", 8) + if result != "00123456" { + t.Errorf("Zero padding 8 character string : '%v'", result) + } +} + +// TestRTNFieldShort test ensures zero padding and right justified +func TestRTNFieldShort(t *testing.T) { + testRTNFieldShort(t) +} + +// BenchmarkRTNFieldShort benchmark ensures zero padding and right justified +func BenchmarkRTNFieldShort(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testRTNFieldShort(b) + } +} + +// testRTNFieldLong ensures sliced to max length +func testRTNFieldLong(t testing.TB) { + c := converters{} + result := c.stringField("1234567899", 8) + if result != "12345678" { + t.Errorf("first 8 character string: '%v'", result) + } +} + +// TestRTNFieldLong test ensures sliced to max length +func TestRTNFieldLong(t *testing.T) { + testRTNFieldLong(t) +} + +// BenchmarkRTNFieldLong benchmark ensures sliced to max length +func BenchmarkRTNFieldLong(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testRTNFieldLong(b) + } +} + +// testRTNFieldExact ensures exact match +func testRTNFieldExact(t testing.TB) { + c := converters{} + result := c.stringField("123456789", 9) + if result != "123456789" { + t.Errorf("first 9 character string: '%v'", result) + } +} + +// TestRTNFieldExact test ensures exact match +func TestRTNFieldExact(t *testing.T) { + testRTNFieldExact(t) +} + +// BenchmarkRTNFieldExact benchmark ensures exact match +func BenchmarkRTNFieldExact(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testRTNFieldExact(b) + } +} + +func TestLeastSignificantDigits(t *testing.T) { + tests := []struct { + input int + max uint + want int + }{ + { + input: 123, + max: 2, + want: 23, + }, + { + input: 123, + max: 3, + want: 123, + }, + { + input: 123, + max: 5, + want: 123, + }, + { + input: 12345678912, + max: 10, + want: 2345678912, + }, + { + input: 99, + max: 0, + want: 0, + }, + } + + c := converters{} + for _, tt := range tests { + if got := c.leastSignificantDigits(tt.input, tt.max); got != tt.want { + t.Errorf("rightmost digits: want %d, got %d", tt.want, got) + } + } +} diff --git a/dir.go b/dir.go new file mode 100644 index 000000000..747987a9a --- /dev/null +++ b/dir.go @@ -0,0 +1,76 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "fmt" + "os" + "path/filepath" +) + +// ReadDir will attempt to parse all ACH files in the given directory. Only files which +// parse successfully will be returned. +func ReadDir(dir string) ([]*File, error) { + readACH := func(path string) (*File, error) { + fd, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("opening %s failed: %v", path, err) + } + defer fd.Close() + + f, err := NewReader(fd).Read() + if err != nil { + return nil, fmt.Errorf("reading %s failed: %v", path, err) + } + return &f, nil + } + + readJSON := func(path string) (*File, error) { + bs, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("opening %s failed: %v", path, err) + } + return FileFromJSON(bs) + } + + infos, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + + var out []*File + for i := range infos { + path := filepath.Join(dir, infos[i].Name()) + + f, err1 := readACH(path) + if f != nil { + out = append(out, f) + continue + } + f, err2 := readJSON(path) + if f != nil { + out = append(out, f) + continue + } + + if err1 != nil && err2 != nil { + return out, fmt.Errorf("%s failed to parse: %v | %v", path, err1, err2) + } + } + return out, nil +} diff --git a/dir_test.go b/dir_test.go new file mode 100644 index 000000000..5f925cc0f --- /dev/null +++ b/dir_test.go @@ -0,0 +1,123 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "io" + "os" + "path/filepath" + "testing" +) + +func TestReadDir(t *testing.T) { + filenames := []string{ + "ppd-debit.ach", + "ppd-valid-debit.json", + "ppd-valid.json", + "return-WEB.ach", + "web-debit.ach", + } + + dir := copyFilesToTempDir(t, filenames) + defer os.RemoveAll(dir) + + files, err := ReadDir(dir) + if err != nil { + t.Fatal(err) + } + if len(files) != 5 { + t.Errorf("found %d files", len(files)) + } +} + +func TestReadDirErr(t *testing.T) { + filenames := []string{ + "ppd-debit.ach", + "ppd-valid-debit.json", + } + + dir := copyFilesToTempDir(t, filenames) + defer os.RemoveAll(dir) + + // zzz- is a prefix as os.ReadDir seems to return file descriptors ordered alphabetically by filename + if err := os.WriteFile(filepath.Join(dir, "zzz-bad.ach"), []byte("bad data"), 0600); err != nil { + t.Fatal(err) + } + + files, err := ReadDir(dir) + if len(files) != 2 { + t.Errorf("found %d files", len(files)) + } + if err == nil { + t.Error("expected error") + } + + files, err = ReadDir("/not/exist/") + if n := len(files); n != 0 || err == nil { + t.Errorf("got %d files error=%v", n, err) + } +} + +func TestReadDirSymlinkErr(t *testing.T) { + dir, err := os.MkdirTemp("", "readdir-symlink") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + // write an invalid symlink + if err := os.Symlink(filepath.Join("missing", "directory"), filepath.Join(dir, "foo.ach")); err != nil { + t.Fatal(err) + } + + files, err := ReadDir(dir) + if len(files) != 0 { + t.Errorf("got %d files", len(files)) + } + if err == nil { + t.Error("expected error") + } +} + +func copyFilesToTempDir(t *testing.T, filenames []string) string { + dir, err := os.MkdirTemp("", "ach-readdir") + if err != nil { + t.Fatal(err) + } + + for i := range filenames { + in, err := os.Open(filepath.Join("test", "testdata", filenames[i])) + if err != nil { + t.Fatalf("in: filename=%s error=%v", filenames[i], err) + } + out, err := os.Create(filepath.Join(dir, filenames[i])) + if err != nil { + t.Fatalf("out: filename=%s error=%v", filenames[i], err) + } + _, err = io.Copy(out, in) + + in.Close() + out.Close() + + if err != nil { + t.Fatalf("copy: %v", err) + } + } + + return dir +} diff --git a/doc.go b/doc.go new file mode 100644 index 000000000..f16c91998 --- /dev/null +++ b/doc.go @@ -0,0 +1,38 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Package ach reads and writes Automated Clearing House (ACH) files. ACH is the +// primary method of electronic money movement through the United States. +// +// https://en.wikipedia.org/wiki/Automated_Clearing_House +// +// https://moov-io.github.io/ach/ +// +// Read an ACH File +// +// fd, err := os.Open("name-of-your-ach-file.ach") +// if err != nil { +// log.Fatalf("problem opening file: %v", err) +// } +// file, err := ach.NewReader(fd).Read() +// if err != nil { +// log.Fatalf("problem parsing ACH file: %v", err) +// } +// if err := file.Validate(); err != nil { +// log.Fatalf("ACH file isn't valid: %v", err) +// } +package ach diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..646bf79ea --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,7 @@ +version: '3' +services: + ach: + image: moov/ach:latest + ports: + - "8080:8080" + - "8090:8090" diff --git a/documentation/2013-Corporate-Rules-and-Guidelines.pdf b/docs/2013-Corporate-Rules-and-Guidelines.pdf similarity index 100% rename from documentation/2013-Corporate-Rules-and-Guidelines.pdf rename to docs/2013-Corporate-Rules-and-Guidelines.pdf diff --git a/docs/404.html b/docs/404.html new file mode 100644 index 000000000..086a5c9ea --- /dev/null +++ b/docs/404.html @@ -0,0 +1,25 @@ +--- +permalink: /404.html +layout: default +--- + + + +
+

404

+ +

Page not found :(

+

The requested page could not be found.

+
diff --git a/documentation/AAP201 - ACH File Formatting.pdf b/docs/AAP201 - ACH File Formatting.pdf similarity index 100% rename from documentation/AAP201 - ACH File Formatting.pdf rename to docs/AAP201 - ACH File Formatting.pdf diff --git a/docs/Gemfile b/docs/Gemfile new file mode 100644 index 000000000..7963b9613 --- /dev/null +++ b/docs/Gemfile @@ -0,0 +1,28 @@ +source "https://rubygems.org" +# Hello! This is where you manage which Jekyll version is used to run. +# When you want to use a different version, change it below, save the +# file and run `bundle install`. Run Jekyll with `bundle exec`, like so: +# +# bundle exec jekyll serve +# +# This will help ensure the proper Jekyll version is running. +# Happy Jekylling! +# This is the default theme for new Jekyll sites. You may change this to anything you like. +gem "bulma-clean-theme" +# If you want to use GitHub Pages, remove the "gem "jekyll"" above and +# uncomment the line below. To upgrade, run `bundle update github-pages`. +gem "github-pages", ">= 214", group: :jekyll_plugins +# If you have any plugins, put them here! +group :jekyll_plugins do + gem "jekyll-feed", "~> 0.12" +end + +# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem +# and associated library. +platforms :mingw, :x64_mingw, :mswin, :jruby do + gem "tzinfo", "~> 1.2" + gem "tzinfo-data" +end + +# Performance-booster for watching directories on Windows +gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin] diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock new file mode 100644 index 000000000..d089d50f2 --- /dev/null +++ b/docs/Gemfile.lock @@ -0,0 +1,284 @@ +GEM + remote: https://rubygems.org/ + specs: + activesupport (6.0.3.7) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + zeitwerk (~> 2.2, >= 2.2.2) + addressable (2.8.0) + public_suffix (>= 2.0.2, < 5.0) + bulma-clean-theme (0.10.5) + jekyll (~> 3.9) + jekyll-feed (~> 0.15) + jekyll-paginate (~> 1.1) + jekyll-seo-tag (~> 2.6) + jekyll-sitemap (~> 1.4) + kramdown-parser-gfm (~> 1.1) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.11.1) + colorator (1.1.0) + commonmarker (0.17.13) + ruby-enum (~> 0.5) + concurrent-ruby (1.1.8) + dnsruby (1.61.5) + simpleidn (~> 0.1) + em-websocket (0.5.2) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0.6.0) + ethon (0.12.0) + ffi (>= 1.3.0) + eventmachine (1.2.7) + execjs (2.8.0) + faraday (1.4.1) + faraday-excon (~> 1.1) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.1) + multipart-post (>= 1.2, < 3) + ruby2_keywords (>= 0.0.4) + faraday-excon (1.1.0) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.1.0) + ffi (1.14.2) + forwardable-extended (2.6.0) + gemoji (3.0.1) + github-pages (214) + github-pages-health-check (= 1.17.0) + jekyll (= 3.9.0) + jekyll-avatar (= 0.7.0) + jekyll-coffeescript (= 1.1.1) + jekyll-commonmark-ghpages (= 0.1.6) + jekyll-default-layout (= 0.1.4) + jekyll-feed (= 0.15.1) + jekyll-gist (= 1.5.0) + jekyll-github-metadata (= 2.13.0) + jekyll-mentions (= 1.6.0) + jekyll-optional-front-matter (= 0.3.2) + jekyll-paginate (= 1.1.0) + jekyll-readme-index (= 0.3.0) + jekyll-redirect-from (= 0.16.0) + jekyll-relative-links (= 0.6.1) + jekyll-remote-theme (= 0.4.3) + jekyll-sass-converter (= 1.5.2) + jekyll-seo-tag (= 2.7.1) + jekyll-sitemap (= 1.4.0) + jekyll-swiss (= 1.0.0) + jekyll-theme-architect (= 0.1.1) + jekyll-theme-cayman (= 0.1.1) + jekyll-theme-dinky (= 0.1.1) + jekyll-theme-hacker (= 0.1.2) + jekyll-theme-leap-day (= 0.1.1) + jekyll-theme-merlot (= 0.1.1) + jekyll-theme-midnight (= 0.1.1) + jekyll-theme-minimal (= 0.1.1) + jekyll-theme-modernist (= 0.1.1) + jekyll-theme-primer (= 0.5.4) + jekyll-theme-slate (= 0.1.1) + jekyll-theme-tactile (= 0.1.1) + jekyll-theme-time-machine (= 0.1.1) + jekyll-titles-from-headings (= 0.5.3) + jemoji (= 0.12.0) + kramdown (= 2.3.1) + kramdown-parser-gfm (= 1.1.0) + liquid (= 4.0.3) + mercenary (~> 0.3) + minima (= 2.5.1) + nokogiri (>= 1.10.4, < 2.0) + rouge (= 3.26.0) + terminal-table (~> 1.4) + github-pages-health-check (1.17.0) + addressable (~> 2.3) + dnsruby (~> 1.60) + octokit (~> 4.0) + public_suffix (>= 2.0.2, < 5.0) + typhoeus (~> 1.3) + html-pipeline (2.14.0) + activesupport (>= 2) + nokogiri (>= 1.4) + http_parser.rb (0.6.0) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + jekyll (3.9.0) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (~> 0.7) + jekyll-sass-converter (~> 1.0) + jekyll-watch (~> 2.0) + kramdown (>= 1.17, < 3) + liquid (~> 4.0) + mercenary (~> 0.3.3) + pathutil (~> 0.9) + rouge (>= 1.7, < 4) + safe_yaml (~> 1.0) + jekyll-avatar (0.7.0) + jekyll (>= 3.0, < 5.0) + jekyll-coffeescript (1.1.1) + coffee-script (~> 2.2) + coffee-script-source (~> 1.11.1) + jekyll-commonmark (1.3.1) + commonmarker (~> 0.14) + jekyll (>= 3.7, < 5.0) + jekyll-commonmark-ghpages (0.1.6) + commonmarker (~> 0.17.6) + jekyll-commonmark (~> 1.2) + rouge (>= 2.0, < 4.0) + jekyll-default-layout (0.1.4) + jekyll (~> 3.0) + jekyll-feed (0.15.1) + jekyll (>= 3.7, < 5.0) + jekyll-gist (1.5.0) + octokit (~> 4.2) + jekyll-github-metadata (2.13.0) + jekyll (>= 3.4, < 5.0) + octokit (~> 4.0, != 4.4.0) + jekyll-mentions (1.6.0) + html-pipeline (~> 2.3) + jekyll (>= 3.7, < 5.0) + jekyll-optional-front-matter (0.3.2) + jekyll (>= 3.0, < 5.0) + jekyll-paginate (1.1.0) + jekyll-readme-index (0.3.0) + jekyll (>= 3.0, < 5.0) + jekyll-redirect-from (0.16.0) + jekyll (>= 3.3, < 5.0) + jekyll-relative-links (0.6.1) + jekyll (>= 3.3, < 5.0) + jekyll-remote-theme (0.4.3) + addressable (~> 2.0) + jekyll (>= 3.5, < 5.0) + jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) + rubyzip (>= 1.3.0, < 3.0) + jekyll-sass-converter (1.5.2) + sass (~> 3.4) + jekyll-seo-tag (2.7.1) + jekyll (>= 3.8, < 5.0) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-swiss (1.0.0) + jekyll-theme-architect (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-cayman (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-dinky (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-hacker (0.1.2) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-leap-day (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-merlot (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-midnight (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-minimal (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-modernist (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-primer (0.5.4) + jekyll (> 3.5, < 5.0) + jekyll-github-metadata (~> 2.9) + jekyll-seo-tag (~> 2.0) + jekyll-theme-slate (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-tactile (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-time-machine (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-titles-from-headings (0.5.3) + jekyll (>= 3.3, < 5.0) + jekyll-watch (2.2.1) + listen (~> 3.0) + jemoji (0.12.0) + gemoji (~> 3.0) + html-pipeline (~> 2.2) + jekyll (>= 3.0, < 5.0) + kramdown (2.3.1) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.3) + listen (3.4.1) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + mercenary (0.3.6) + minima (2.5.1) + jekyll (>= 3.5, < 5.0) + jekyll-feed (~> 0.9) + jekyll-seo-tag (~> 2.1) + minitest (5.14.4) + multipart-post (2.1.1) + nokogiri (1.13.6-arm64-darwin) + racc (~> 1.4) + nokogiri (1.13.6-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.13.6-x86_64-linux) + racc (~> 1.4) + octokit (4.21.0) + faraday (>= 0.9) + sawyer (~> 0.8.0, >= 0.5.3) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + public_suffix (4.0.6) + racc (1.6.0) + rb-fsevent (0.10.4) + rb-inotify (0.10.1) + ffi (~> 1.0) + rexml (3.2.5) + rouge (3.26.0) + ruby-enum (0.9.0) + i18n + ruby2_keywords (0.0.4) + rubyzip (2.3.0) + safe_yaml (1.0.5) + sass (3.7.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sawyer (0.8.2) + addressable (>= 2.3.5) + faraday (> 0.8, < 2.0) + simpleidn (0.2.1) + unf (~> 0.1.4) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + thread_safe (0.3.6) + typhoeus (1.4.0) + ethon (>= 0.9.0) + tzinfo (1.2.10) + thread_safe (~> 0.1) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.7) + unicode-display_width (1.7.0) + zeitwerk (2.4.2) + +PLATFORMS + universal-darwin-20 + x86_64-linux + +DEPENDENCIES + bulma-clean-theme + github-pages (>= 214) + jekyll-feed (~> 0.12) + tzinfo (~> 1.2) + tzinfo-data + wdm (~> 0.1.1) + +BUNDLED WITH + 2.2.7 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..add0c7bcf --- /dev/null +++ b/docs/README.md @@ -0,0 +1,18 @@ +## Moov ACH + +**[Documentation](https://moov-io.github.io/ach)** | **[Source](https://github.com/moov-io/ach)** | **[Running](https://github.com/moov-io/ach#usage)** | **[Configuration](https://github.com/moov-io/ach#configuration-settings)** + +### Purpose + +Moov ACH implements a file reader and writer written in Go along with an HTTP API for creating, parsing, and validating Automated Clearing House ([ACH](https://en.wikipedia.org/wiki/Automated_Clearing_House)) files. ACH is the primary method of electronic money movement throughout the United States. + +## Getting Help + +If you have ACH-specific questions, NACHA (National Automated Clearing House Association) has their [complete specification](docs/2013-Corporate-Rules-and-Guidelines.pdf) for all file formats and message types. + + channel | info + ------- | ------- + [Project Documentation](https://moov-io.github.io/ach/) | Our project documentation available online. +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. +[GitHub Issue](https://github.com/moov-io/ach/issues/new) | If you are able to reproduce a problem please open a GitHub Issue under the specific project that caused the error. +[moov-io slack](https://slack.moov.io/) | Join our slack channel to have an interactive discussion about the development of the project. diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 000000000..5dc66ef4d --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,57 @@ +# Welcome to Jekyll! +# +# This config file is meant for settings that affect your whole blog, values +# which you are expected to set up once and rarely edit after that. If you find +# yourself editing this file very often, consider using Jekyll's data files +# feature for the data you need to update frequently. +# +# For technical reasons, this file is *NOT* reloaded automatically when you use +# 'bundle exec jekyll serve'. If you change this file, please restart the server process. +# +# If you need help with YAML syntax, here are some quick references for you: +# https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml +# https://learnxinyminutes.com/docs/yaml/ +# +# Site settings +# These are used to personalize your new site. If you look in the HTML files, +# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. +# You can create any custom variable you would like, and they will be accessible +# in the templates via {{ site.myvariable }}. + +title: Moov ACH +email: oss@moov.io +description: >- # this means to ignore newlines until "baseurl:" + ACH implements a reader, writer, and validator for Automated Clearing House (ACH) files. + The HTTP server is available in a Docker image and the Go package is available. +url: "https://moov-io.github.io" # the base hostname & protocol for your site, e.g. http://example.com +baseurl: "/ach" # the subpath of your site, e.g. /blog +twitter_username: moov +github_username: moov-io +source_code: https://github.com/moov-io/ach +permalink: pretty + +# Build settings +remote_theme: moov-io/bulma-clean-theme +plugins: + - jekyll-feed + - github-pages + +# Exclude from processing. +# The following items will not be processed, by default. +# Any item listed under the `exclude:` key here will be automatically added to +# the internal "default list". +# +# Excluded items can be processed by explicitly listing the directories or +# their entries' file path in the `include:` list. +# +# exclude: +# - .sass-cache/ +# - .jekyll-cache/ +# - gemfiles/ +# - Gemfile +# - Gemfile.lock +# - node_modules/ +# - vendor/bundle/ +# - vendor/cache/ +# - vendor/gems/ +# - vendor/ruby/ diff --git a/docs/_data/docs-menu.yml b/docs/_data/docs-menu.yml new file mode 100644 index 000000000..965085029 --- /dev/null +++ b/docs/_data/docs-menu.yml @@ -0,0 +1,59 @@ +- label: Getting started + items: + - name: Overview + link: / + - name: What is ACH? + link: /intro/ + +- label: Usage + items: + - name: Docker + link: /usage-docker/ + - name: Google Cloud Run + link: /usage-google-cloud/ + - name: Server config + link: /server-config/ + - name: Go library + link: /usage-go/ + - name: Command line + link: /usage-command-line/ + +- label: ACH file setup + items: + - name: Create file + link: /create-file/ + - name: File structure + link: /file-structure/ + - name: SEC codes table + link: /sec-codes-table/ + +- label: File operations + items: + - name: Custom validation + link: /custom-validation/ + - name: Balanced offset + link: /balanced-offset/ + - name: Flatten batches + link: /flatten-batches/ + - name: Merging files + link: /merging-files/ + - name: Segmenting files + link: /segment-file/ + - name: Return files + link: /returns/ + - name: Change files + link: /changes/ + +- label: Production and monitoring + items: + - name: Kubernetes + link: /deployment/ + - name: Prometheus metrics + link: /metrics/ + - name: Building for AWS Lambda + link: /aws/lambda/building-go-for-lambda/ + items: + - name: Convert JSON to NACHA + link: /aws/lambda/json_to_nacha/lambda_json_to_nacha/ + - name: Convert NACHA to JSON + link: /aws/lambda/nacha_to_json/lambda_nacha_to_json/ diff --git a/docs/_data/navigation.yml b/docs/_data/navigation.yml new file mode 100644 index 000000000..d7810c360 --- /dev/null +++ b/docs/_data/navigation.yml @@ -0,0 +1,26 @@ +- name: API + link: https://moov-io.github.io/ach/api +- name: Go + link: https://pkg.go.dev/github.com/moov-io/ach#section-documentation +- name: Demo + link: https://oss.moov.io/ach/ +- name: Community + dropdown: + - name: Awesome Fintech + link: https://github.com/moov-io/awesome-fintech + - name: Slack + link: https://slack.moov.io/ + - name: Terms Dictionary + link: https://github.com/moov-io/terms-dictionary +- name: Other Projects + dropdown: + - name: Moov Image Cash Letter + link: https://moov-io.github.io/imagecashletter/ + - name: Moov Wire + link: https://moov-io.github.io/wire/ + - name: Moov Metro 2 + link: https://moov-io.github.io/metro2/ + - name: Moov Fed + link: https://moov-io.github.io/fed/ + - name: Moov Watchman + link: https://moov-io.github.io/watchman/ diff --git a/docs/_posts/2021-01-27-welcome-to-jekyll.markdown b/docs/_posts/2021-01-27-welcome-to-jekyll.markdown new file mode 100644 index 000000000..93142119c --- /dev/null +++ b/docs/_posts/2021-01-27-welcome-to-jekyll.markdown @@ -0,0 +1,29 @@ +--- +layout: post +title: "Welcome to Jekyll!" +date: 2021-01-27 19:59:26 -0800 +categories: jekyll update +--- +You’ll find this post in your `_posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `jekyll serve`, which launches a web server and auto-regenerates your site when a file is updated. + +Jekyll requires blog post files to be named according to the following format: + +`YEAR-MONTH-DAY-title.MARKUP` + +Where `YEAR` is a four-digit number, `MONTH` and `DAY` are both two-digit numbers, and `MARKUP` is the file extension representing the format used in the file. After that, include the necessary front matter. Take a look at the source for this post to get an idea about how it works. + +Jekyll also offers powerful support for code snippets: + +{% highlight ruby %} +def print_hi(name) + puts "Hi, #{name}" +end +print_hi('Tom') +#=> prints 'Hi, Tom' to STDOUT. +{% endhighlight %} + +Check out the [Jekyll docs][jekyll-docs] for more info on how to get the most out of Jekyll. File all bugs/feature requests at [Jekyll’s GitHub repo][jekyll-gh]. If you have questions, you can ask them on [Jekyll Talk][jekyll-talk]. + +[jekyll-docs]: https://jekyllrb.com/docs/home +[jekyll-gh]: https://github.com/jekyll/jekyll +[jekyll-talk]: https://talk.jekyllrb.com/ diff --git a/documentation/ach_file_structure_shg.gif b/docs/ach_file_structure_shg.gif similarity index 100% rename from documentation/ach_file_structure_shg.gif rename to docs/ach_file_structure_shg.gif diff --git a/docs/api/index.html b/docs/api/index.html new file mode 100644 index 000000000..367754ba9 --- /dev/null +++ b/docs/api/index.html @@ -0,0 +1,15 @@ + + + + + Moov ACH Endpoints + + + + + + + diff --git a/docs/aws/lambda/building-go-for-lambda.md b/docs/aws/lambda/building-go-for-lambda.md new file mode 100644 index 000000000..251349a74 --- /dev/null +++ b/docs/aws/lambda/building-go-for-lambda.md @@ -0,0 +1,50 @@ +--- +layout: page +title: AWS + +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Build a Go file for use in AWS Lambda + +Lambdas require a Linux executable packaged in a zip folder. The following commands can be used to build a compatible executable (see [Lambda Packaging Docs](https://docs.aws.amazon.com/lambda/latest/dg/golang-package.html) for more info): + +### Mac/Linux: +``` +GOOS=linux GOARCH=amd64 go build -o main main.go +zip main.zip main +``` + +### Windows: +You must create a binary that is executable on Linux. Amazon makes a handy tool for this, you can get it with: +``` +set GO111MODULE=on +go.exe get -u github.com/aws/aws-lambda-go/cmd/build-lambda-zip +``` + +then build your file:
+ +PowerShell: +``` +$env:GOOS = "linux" +$env:GOARCH = "amd64" +$env:CGO_ENABLED = "0" +go build -o main main.go +~\Go\Bin\build-lambda-zip.exe -o main.zip main +``` + +Windows (cmd): +``` +set GOOS=linux +set GOARCH=amd64 +set CGO_ENABLED=0 +go build -o main main.go +%USERPROFILE%\Go\bin\build-lambda-zip.exe -o main.zip main +``` + +## Next Steps + +- [Convert JSON to NACHA format](./json_to_nacha/lambda_json_to_nacha.md) +- [Convert NACHA To JSON format](./nacha_to_json/lambda_nacha_to_json.md) diff --git a/docs/aws/lambda/json_to_nacha/configure-function-basic-settings.PNG b/docs/aws/lambda/json_to_nacha/configure-function-basic-settings.PNG new file mode 100644 index 000000000..c457c52fd Binary files /dev/null and b/docs/aws/lambda/json_to_nacha/configure-function-basic-settings.PNG differ diff --git a/docs/aws/lambda/json_to_nacha/configure-function-configure-parseEvent.PNG b/docs/aws/lambda/json_to_nacha/configure-function-configure-parseEvent.PNG new file mode 100644 index 000000000..c079ade37 Binary files /dev/null and b/docs/aws/lambda/json_to_nacha/configure-function-configure-parseEvent.PNG differ diff --git a/docs/aws/lambda/json_to_nacha/configure-function-create-parseEvent.PNG b/docs/aws/lambda/json_to_nacha/configure-function-create-parseEvent.PNG new file mode 100644 index 000000000..78526dbdb Binary files /dev/null and b/docs/aws/lambda/json_to_nacha/configure-function-create-parseEvent.PNG differ diff --git a/docs/aws/lambda/json_to_nacha/configure-function-upload-zip.PNG b/docs/aws/lambda/json_to_nacha/configure-function-upload-zip.PNG new file mode 100644 index 000000000..ed29b3f40 Binary files /dev/null and b/docs/aws/lambda/json_to_nacha/configure-function-upload-zip.PNG differ diff --git a/docs/aws/lambda/json_to_nacha/create-function-author-options.PNG b/docs/aws/lambda/json_to_nacha/create-function-author-options.PNG new file mode 100644 index 000000000..4a915217a Binary files /dev/null and b/docs/aws/lambda/json_to_nacha/create-function-author-options.PNG differ diff --git a/docs/aws/lambda/json_to_nacha/create-function-basic-info.PNG b/docs/aws/lambda/json_to_nacha/create-function-basic-info.PNG new file mode 100644 index 000000000..5cd2bb229 Binary files /dev/null and b/docs/aws/lambda/json_to_nacha/create-function-basic-info.PNG differ diff --git a/docs/aws/lambda/json_to_nacha/create-function-permissions-role.PNG b/docs/aws/lambda/json_to_nacha/create-function-permissions-role.PNG new file mode 100644 index 000000000..29cc233df Binary files /dev/null and b/docs/aws/lambda/json_to_nacha/create-function-permissions-role.PNG differ diff --git a/docs/aws/lambda/json_to_nacha/lambda_json_to_nacha.md b/docs/aws/lambda/json_to_nacha/lambda_json_to_nacha.md new file mode 100644 index 000000000..36896e763 --- /dev/null +++ b/docs/aws/lambda/json_to_nacha/lambda_json_to_nacha.md @@ -0,0 +1,232 @@ +--- +layout: page +title: AWS JSON to NACHA + +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Use AWS Lambda to parse JSON to a NACHA-formatted string +This walkthrough provides instructions on how to use the [`github.com/moov-io/ach`](https://pkg.go.dev/github.com/moov-io/ach) Go library to +parse a JSON object in a Lambda event and return the resulting NACHA-formatted string. + +The Lambda event could be triggered by a variety of sources: upload to S3 bucket, API Gateway HTTP request, SQS message, another Lambda, etc. Likewise, the response can be sent to a variety of destinations. This walkthrough doesn't demonstrate a particular trigger or destination, but it can easily be adapted for the use cases mentioned. + +Make sure you've built [your project](../building-go-for-lambda.md) for AWS Lambda. + + +## 1. Create the Go file +Create a new Go file named `main.go` and replace it's contents with the following: +```go +package main + +import ( + "bytes" + "context" + + "github.com/aws/aws-lambda-go/lambda" + "github.com/moov-io/ach" + "github.com/moov-io/base" +) + +type JsonParseEvent struct { + Json ach.File `json:"data"` +} + +func main() { + lambda.Start(HandleRequest) +} + +// logic to be executed when lambda starts goes here +func HandleRequest(ctx context.Context, event JsonParseEvent) (string, error) { + + // get file from lambda event, it has already been marshaled from json to ach.File by Go + file := event.Json + + // set file ID + file.ID = base.ID() + + // validate parsed file + err := file.Validate() + if err != nil { + return "", err + } + + // create buffer to contain NACHA text + buf := new(bytes.Buffer) + + // write ach.File to buffer + err = ach.NewWriter(buf).Write(&file) + if err != nil { + return buf.String(), err + } + + // get NACHA text from buffer + parseRes := buf.String() + + return parseRes, err +} +``` + +*`main()`* is invoked when the lambda is triggered
+*`HandleRequest()`* is the callback containing the business logic and accepts a lambda event as a parameter with the shape: +``` +{ + "data": { ...JSON representing ACH file } +} +``` +`JsonParseEvent` mirrors this shape and makes the contents accessible in `HandleRequest()`. This event can be changed to fit +your needs, all you need to do is update the `JsonParseEvent` struct and make sure the trigger for this lambda is passing in the expected event. + +

+## 2. Build your file for AWS Lambda +Now that you've saved your Go file, you need to build it for use with AWS Lambdas. See [Build a Go file for AWS Lambda](../building-go-for-lambda.md) for more details. +

+ +## 3. Create the Lambda function in AWS console +Sign into the AWS Console and head over to the Lambdas section and create a new function. + +Select *Author From Scratch* + +Authorship Details

+ +Under Basic Information, enter a name for your function (e.g. `parse-json-to-nacha`) and select Go as the Runtime + +Basic Info

+ +By default, AWS will create a new permissions role for your function with all lambda permissions. This is adequate for this tutorial, but if you plan to access other AWS services from this function you will need to add permissions. Click *Create Function* in the bottom right after you've selected your desired role. + +Permissions Role Selection

+ +## 4. Configure function and upload executable +AWS Lambdas don't currently support inline editing of Go files so you will need to upload the zip file you created in step 2. Click the *Actions* dropdown in the Function Code section, select *Upload a .zip file*, upload your zip and click *Save*. + +Upload Zip

+ +Now scroll down to the Basic Settings section. Handler needs to be set to the name of your executable file from step 2. In our case, the executable name is `main`. Click *Edit* and change the Handler from `hello` to `main`. + +Edit Basic Settings

+ +Finally, we will create two test events to confirm our function is working as expected. + +The first event will be used to confirm the JSON is parsed correctly. Click the dropdown to the left of the Test button at the top of the page and select *Configure test events*. + +Create parseEvent

+ +Leave the `hello-world` template selected and enter a name for your event (e.g. `parseEvent`). + +Configure parseEvent

+ +Replace the contents of the code editor with the following: + +```json +{ + "data": { + "id": "", + "fileHeader": { + "id": "", + "immediateDestination": "031300012", + "immediateOrigin": "231380104", + "fileCreationDate": "190816", + "fileCreationTime": "1055", + "fileIDModifier": "A", + "immediateDestinationName": "Federal Reserve Bank", + "immediateOriginName": "My Bank Name", + "referenceCode": "12345678" + }, + "batches": [ + { + "batchHeader": { + "id": "", + "serviceClassCode": 225, + "companyName": "Name on Account", + "companyIdentification": "231380104", + "standardEntryClassCode": "PPD", + "companyEntryDescription": "REG.SALARY", + "effectiveEntryDate": "190816", + "originatorStatusCode": 1, + "ODFIIdentification": "12104288", + "batchNumber": 1 + }, + "entryDetails": [ + { + "id": "", + "transactionCode": 27, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "123456789 ", + "amount": 200000000, + "identificationNumber": " ", + "individualName": "Debit Account ", + "discretionaryData": " ", + "traceNumber": "121042880000001" + } + ], + "batchControl": { + "id": "", + "serviceClassCode": 225, + "entryAddendaCount": 1, + "entryHash": 23138010, + "totalDebit": 200000000, + "totalCredit": 0, + "companyIdentification": "231380104", + "ODFIIdentification": "12104288", + "batchNumber": 1 + }, + "offset": null + } + ], + "IATBatches": null, + "fileControl": { + "id": "", + "batchCount": 1, + "blockCount": 1, + "entryAddendaCount": 1, + "entryHash": 23138010, + "totalDebit": 200000000, + "totalCredit": 0 + }, + "fileADVControl": { + "id": "", + "batchCount": 0, + "entryAddendaCount": 0, + "entryHash": 0, + "totalDebit": 0, + "totalCredit": 0 + }, + "NotificationOfChange": null, + "ReturnEntries": null + } +} +``` + +This JSON will be handled by `HandleRequest()`. Note the top level `data` field which matches the shape we covered in step 1. Click *Create* at the bottom when you're finished making changes. + +Now create your second event, which is designed to cause an error while parsing so you can see how the function returns errors from Go code. Create a new event with a different name (e.g. `parseEventError`) and modify the JSON. + +Change the JSON above by setting immediateDestination to be an empty string. This should result in an error stating that the immediateDestination should be 9 numeric digits. + +``` +{ + "data": { + "id": "", + "fileHeader": { + "id": "", + "immediateDestination": "", + ... +} +``` + +## 5. Run/Test your function +Now you can finally test your function. + +Go to the top of the page, select your successful test event (e.g. `parseEvent`) and click *Test*. You should receive a succcessful response similar to the following: + +Test parseEvent

+ +Now test your error event (e.g. `parseEventError`). You should receive an error response similar to the following: + +Test parseEventError

+ +And that's it! You've successfully created and tested a lambda function that parses JSON into a NACHA-formatted string. diff --git a/docs/aws/lambda/json_to_nacha/test-function-error-parseEventError.PNG b/docs/aws/lambda/json_to_nacha/test-function-error-parseEventError.PNG new file mode 100644 index 000000000..4b3e79014 Binary files /dev/null and b/docs/aws/lambda/json_to_nacha/test-function-error-parseEventError.PNG differ diff --git a/docs/aws/lambda/json_to_nacha/test-function-success-parseEvent.PNG b/docs/aws/lambda/json_to_nacha/test-function-success-parseEvent.PNG new file mode 100644 index 000000000..6e6b4d157 Binary files /dev/null and b/docs/aws/lambda/json_to_nacha/test-function-success-parseEvent.PNG differ diff --git a/docs/aws/lambda/nacha_to_json/configure-function-basic-settings.PNG b/docs/aws/lambda/nacha_to_json/configure-function-basic-settings.PNG new file mode 100644 index 000000000..c457c52fd Binary files /dev/null and b/docs/aws/lambda/nacha_to_json/configure-function-basic-settings.PNG differ diff --git a/docs/aws/lambda/nacha_to_json/configure-function-configure-parseEvent.PNG b/docs/aws/lambda/nacha_to_json/configure-function-configure-parseEvent.PNG new file mode 100644 index 000000000..52912150f Binary files /dev/null and b/docs/aws/lambda/nacha_to_json/configure-function-configure-parseEvent.PNG differ diff --git a/docs/aws/lambda/nacha_to_json/configure-function-create-parseEvent.PNG b/docs/aws/lambda/nacha_to_json/configure-function-create-parseEvent.PNG new file mode 100644 index 000000000..78526dbdb Binary files /dev/null and b/docs/aws/lambda/nacha_to_json/configure-function-create-parseEvent.PNG differ diff --git a/docs/aws/lambda/nacha_to_json/configure-function-upload-zip.PNG b/docs/aws/lambda/nacha_to_json/configure-function-upload-zip.PNG new file mode 100644 index 000000000..78fb19f4b Binary files /dev/null and b/docs/aws/lambda/nacha_to_json/configure-function-upload-zip.PNG differ diff --git a/docs/aws/lambda/nacha_to_json/create-function-author-options.PNG b/docs/aws/lambda/nacha_to_json/create-function-author-options.PNG new file mode 100644 index 000000000..4a915217a Binary files /dev/null and b/docs/aws/lambda/nacha_to_json/create-function-author-options.PNG differ diff --git a/docs/aws/lambda/nacha_to_json/create-function-basic-info.PNG b/docs/aws/lambda/nacha_to_json/create-function-basic-info.PNG new file mode 100644 index 000000000..76c1312dc Binary files /dev/null and b/docs/aws/lambda/nacha_to_json/create-function-basic-info.PNG differ diff --git a/docs/aws/lambda/nacha_to_json/create-function-permissions-role.PNG b/docs/aws/lambda/nacha_to_json/create-function-permissions-role.PNG new file mode 100644 index 000000000..29cc233df Binary files /dev/null and b/docs/aws/lambda/nacha_to_json/create-function-permissions-role.PNG differ diff --git a/docs/aws/lambda/nacha_to_json/lambda_nacha_to_json.md b/docs/aws/lambda/nacha_to_json/lambda_nacha_to_json.md new file mode 100644 index 000000000..0c83e454f --- /dev/null +++ b/docs/aws/lambda/nacha_to_json/lambda_nacha_to_json.md @@ -0,0 +1,153 @@ +--- +layout: page +title: AWS NACHA to JSON + +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Use AWS Lambda to parse a NACHA-formatted string to JSON +This walkthrough provides instructions on how to use the [`github.com/moov-io/ach`](https://pkg.go.dev/github.com/moov-io/ach) Go library to +parse a NACHA-formatted string in a Lambda event and return the resulting JSON response. + +The Lambda event could be triggered by a variety of sources: upload to S3 bucket, API Gateway HTTP request, SQS message, another Lambda, etc. Likewise, the response can be sent to a variety of destinations. This walkthrough doesn't demonstrate a particular trigger or destination, but it can easily be adapted for the use cases mentioned. + +Make sure you've built [your project](../building-go-for-lambda.md) for AWS Lambda. + +## 1. Create the Go file +Create a new Go file named `main.go` and replace it's contents with the following: +```go +package main + +import ( + "context" + "strings" + + "github.com/aws/aws-lambda-go/lambda" + "github.com/moov-io/ach" + "github.com/moov-io/base" +) + +type NachaParseEvent struct { + Nacha string `json:"data"` +} + +type NachaParseResponse struct { + File ach.File `json:"file"` +} + +func main() { + lambda.Start(HandleRequest) +} + +// logic to be executed when lambda starts goes here +func HandleRequest(ctx context.Context, event NachaParseEvent) (NachaParseResponse, error) { + + // get NACHA file text from lambda event + rd := strings.NewReader(event.Nacha) + + // create file from NACHA text + file, err := ach.NewReader(rd).Read() + if err != nil { + return NachaParseResponse{File: file}, err + } + + // set file ID + file.ID = base.ID() + + // validate parsed file + if err := file.Validate(); err != nil { + return NachaParseResponse{File: file}, err + } + + //create response object + parseRes := NachaParseResponse{File: file} + + return parseRes, err +} +``` + +*`main()`* is invoked when the lambda is triggered
+*`HandleRequest()`* is the callback containing the business logic and accepts a lambda event as a parameter with the shape: +``` +{ + "data": " ...NACHA formatted string " +} +``` +`NachaParseEvent` mirrors this shape and makes the contents accessible in `HandleRequest()`. This event can be changed to fit +your needs, all you need to do is update the `NachaParseEvent` struct and make sure the trigger for this lambda is passing in the expected event. + +

+## 2. Build your file for AWS Lambda +Now that you've saved your Go file, you need to build it for use with AWS Lambdas. See [Build a Go file for AWS Lambda](../building-go-for-lambda.md) for more details. +

+ +## 3. Create the Lambda function in AWS console +Sign into the AWS Console and head over to the Lambdas section and create a new function. + +Select *Author From Scratch* + +Authorship Details

+ +Under Basic Information, enter a name for your function (e.g. `parse-nacha-to-json`) and select Go as the Runtime + +Basic Info

+ +By default, AWS will create a new permissions role for your function with all lambda permissions. This is adequate for this tutorial, but if you plan to access other AWS services from this function you will need to add permissions. Click *Create Function* in the bottom right after you've selected your desired role. + +Permissions Role Selection

+ +## 4. Configure function and upload executable +AWS Lambdas don't currently support inline editing of Go files so you will need to upload the zip file you created in step 2. Click the *Actions* dropdown in the Function Code section, select *Upload a .zip file*, upload your zip and click *Save*. + +Upload Zip

+ +Now scroll down to the Basic Settings section. Handler needs to be set to the name of your executable file from step 2. In our case, the executable name is `main`. Click *Edit* and change the Handler from `hello` to `main`. + +Edit Basic Settings

+ +Finally, we will create two test events to confirm our function is working as expected. + +The first event will be used to confirm the NACHA-formatted string is parsed correctly. Click the dropdown to the left of the Test button at the top of the page and select *Configure test events*. + +Create parseEvent

+ +Leave the `hello-world` template selected and enter a name for your event (e.g. `parseEvent`). + +Configure parseEvent

+ +Replace the contents of the code editor with the following: + +```json +{ + "data": "101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678\n5225Name on Account 231380104 PPDREG.SALARY 190816 1121042880000001\n627231380104123456789 0200000000 Debit Account 0121042880000001\n82250000010023138010000200000000000000000000231380104 121042880000001\n9000001000001000000010023138010000200000000000000000000 \n9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999\n9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999\n9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999\n9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999\n9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +} +``` + +This JSON will be handled by `HandleRequest()`. Note the top level `data` field which matches the shape we covered in step 1. Click *Create* at the bottom when you're finished making changes. + + + +Now create your second event, which is designed to cause an error while parsing so you can see how the function returns errors from Go code. Create a new event with a different name (e.g. `parseEventError`) and modify the NACHA string. + +You can change the string above by putting a '1' in position 4 (`1011...`). This should result in an error stating that the immediateDestination should be 9 numeric digits. + +``` +{ + "data": "101103130001202313801041908161055A094101Federal Reserve..." +} +``` +

+## 5. Run/Test your function +Now you can finally test your function. + +Go to the top of the page, select your successful test event (e.g. `parseEvent`) and click *Test*. You should receive a succcessful response similar to the following: + +Test parseEvent

+ +Now test your error event (e.g. `parseEventError`). You should receive an error response similar to the following: + +Test parseEventError

+ +And that's it! You've successfully created and tested a lambda function that parses a NACHA-formatted string into JSON. diff --git a/docs/aws/lambda/nacha_to_json/test-function-error-parseEventError.PNG b/docs/aws/lambda/nacha_to_json/test-function-error-parseEventError.PNG new file mode 100644 index 000000000..6acfe5866 Binary files /dev/null and b/docs/aws/lambda/nacha_to_json/test-function-error-parseEventError.PNG differ diff --git a/docs/aws/lambda/nacha_to_json/test-function-success-parseEvent.PNG b/docs/aws/lambda/nacha_to_json/test-function-success-parseEvent.PNG new file mode 100644 index 000000000..0f723d63f Binary files /dev/null and b/docs/aws/lambda/nacha_to_json/test-function-success-parseEvent.PNG differ diff --git a/docs/balanced-offset.md b/docs/balanced-offset.md new file mode 100644 index 000000000..af7f6d7d8 --- /dev/null +++ b/docs/balanced-offset.md @@ -0,0 +1,38 @@ +--- +layout: page +title: Balanced offset +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Balanced offsets + +ACH files can have "offset records" which are [EntryDetail](https://godoc.org/github.com/moov-io/ach#EntryDetail) records that balance a file's debits and credits. This means if there are debits, there is a credit offset matching the sum of the debits. If there are credits, there is a debit offset matching the sum of the credits. They are mutually exclusive. + +Offset files are used to offset transactions from a single account inside of the ODFI. The Offset is the last record which settles the funds into a single ODFI account. + +Note: Balanced offset files (via `WithOffset`) are only in ACH v1.1.0 and later. + +## Setting offset metadata + +An ACH [Batch](https://godoc.org/github.com/moov-io/ach#Batch) supports calling [WithOffset](https://godoc.org/github.com/moov-io/ach#Batch.WithOffset) to set offset information such as routing number, account number, account type, and a description. + +```go +bh := ach.NewBatchHeader() +// fill in the fields + +ed := ach.NewEntryDetail() +// fill in the fields + +batch := ach.NewBatch(bh) +batch.AddEntry(ed) +batch.WithOffset(&ach.Offset{ + RoutingNumber: "...", + AccountNumber: "...", + AccountType: OffsetSavings, + Description: "...", +}) + +// On each batch.Create() call the offset record will be re-tabulated +``` diff --git a/docs/changes.md b/docs/changes.md new file mode 100644 index 000000000..3c5ffcd0f --- /dev/null +++ b/docs/changes.md @@ -0,0 +1,83 @@ +--- +layout: page +title: Change files +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Change files + +A Notification of Change (NOC) is a non-dollar entry transmitted from a receiving depository financial institution (RDFI) to the originating depository financial institution (ODFI). These are sent in response to outdated or erroneous information in an initial entry. An NOC often occurs due to bank mergers or acquisitions that change account and/or routing numbers. If an RDFI sends an NOC, the ODFI will need to inform their originator promptly. + +The Standard Entry Class code for an NOC is "COR". There are a few possible reasons for an NOC, each defined by a "change code". The most common codes are `C01` for an incorrect account number and `C02` for an outdated routing number. We have [a list of supported change codes](#change-codes) below. + +NOCs are identified by an [Addenda98](https://pkg.go.dev/github.com/moov-io/ach?tab=doc#Addenda98) record on the EntryDetail with a [ChangeCode](https://pkg.go.dev/github.com/moov-io/ach?tab=doc#ChangeCode) that can be processed. + +### Processing + +The RDFI must send an NOC within two banking days of the original entry settlement date (to which the NOC is in response to). + +The ODFI is responsible for forwarding an NOC to the Originator within two banking days of the NOC settlement date. They must provide the Originator with the following information at a minimum: + +- Company name +- Company identification +- Company Entry description +- Effective Entry date +- DFI account number +- Individual name/receiving company name +- Individual identification number + +- Change code +- Original Entry trace number +- Original RDFI indentification +- Corrected data + +The Originator must make specified changes within six banking days of receiving the above information or prior to initiating another entry to the receiver's account (whichever is later). + +### Creation + +When creating an NOC entry, add an [Addenda98](https://pkg.go.dev/github.com/moov-io/ach?tab=doc#Addenda98) record onto the EntryDetail with the appropriate change code. + +```go +addenda98 := ach.NewAddenda98() +addenda98.ChangeCode = "C01" +addenda98.OriginalTrace = "121042880000001" +addenda98.OriginalDFI = "121042882" +addenda98.CorrectedData = "1918171614" +addenda98.TraceNumber = "91012980000088" + +//entry.Addenda98 = addenda98 +``` + +### Change codes + +| Code | Reason | Description | +|----|-----|------| +| `C01` | Incorrect bank account number | Bank account number incorrect or formatted incorrectly | +| `C02` | Incorrect transit/routing number | Once valid transit/routing number must be changed | +| `C03` | Incorrect transit/routing number and bank account number | Once valid transit/routing number must be changed and causes a change to bank account number structure | +| `C04` | Bank account name change | Customer has changed name or ODFI submitted name incorrectly | +| `C05` | Incorrect payment code | Entry posted to demand account should contain savings payment codes or vice versa | +| `C06` | Incorrect bank account number and transit code | Bank account number must be changed and payment code should indicate posting to another account type (demand/savings) | +| `C07` | Incorrect transit/routing number, bank account number and payment code | Changes required in three fields indicated | +| `C09` | Incorrect individual ID number | Individual's ID number is incorrect | +| `C10` | Incorrect company name | Company name is no longer valid and should be changed | +| `C11` | Incorrect company identification | Company ID is no longer valid and should be changed | +| `C12` | Incorrect company name and company ID | Both the company name and company id are no longer valid and must be changed | + +#### Refused Notification of Change + +When ODFIs cannot forward entries to the Originator or a NOC is malformed, invalid or otherwise unable to be processed a Refused NOC may be issued. This will indicate a Refused Change Code to be handled and must be initiated within 15 days of receipt of the NOC. + +| Code | Description | +|----|-----| +| `C61` | Misrouted Notification of Change | +| `C62` | Incorrect Trace Number | +| `C63` | Incorrect Company Identification Number | +| `C64` | Incorrect Individual Identification Number or Identification Number | +| `C65` | Incorrectly Formatted Corrected Data | +| `C66` | Incorrect Discretionary Data | +| `C67` | Routing Number not from Original Entry Detail Record | +| `C68` | DFI Account Number not from Original Entry Detail Record | +| `C69` | Incorrect Transaction Code | diff --git a/docs/create-file.md b/docs/create-file.md new file mode 100644 index 000000000..eda585840 --- /dev/null +++ b/docs/create-file.md @@ -0,0 +1,90 @@ +--- +layout: page +title: ACH file setup +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Create a file + +Creating an Automated Clearing House (ACH) file can be done several ways: + +- [Using Go and our generated client](#go-client) +- [Uploading a JSON representation](#upload-a-json-representation) +- [Uploading a raw ACH file](#upload-a-json-representation) + +## Go client + +We have an example of [using our Go client and uploading the JSON representation](https://github.com/moov-io/ach/blob/master/examples/http/main.go). The basic idea follows this structure: + +1. Create a [BatchHeader](https://godoc.org/github.com/moov-io/ach#BatchHeader) record with `ach.NewBatchHeader()`. +1. Create an [EntryDetail](https://godoc.org/github.com/moov-io/ach#EntryDetail) record with `ach.NewEntryDetail()`. +1. Create a [Batch](https://godoc.org/github.com/moov-io/ach#Batch) from our `BatchHeader` and `EntryDetail`. + 1. Using a constructor like `batch := ach.NewBatchPPD(batchHeader)` and adding the batch with `batch.AddEntry(entry)`. + 1. Call and verify `batch.Create()` returns no error. +1. Create our ACH File record `file := ach.NewFile()` and [FileHeader](https://godoc.org/github.com/moov-io/ach#FileHeader) with `ach.NewFileHeader()` +1. Add the `FileHeader` (via `file.SetHeader(fileHeader)`) and `Batch` records to the file (via `file.AddBatch(batch)`). + 1. Call and verify `file.Create()` returns no error. +1. Encode the `File` to JSON (via `json.NewEncoder(&buf).Encode(&file)`) for a `net/http` request. + +## Upload a JSON representation + +In Ruby we have an example of [creating an ACH file from JSON](https://github.com/moov-io/ruby-ach-demo/blob/master/main.rb). The JSON structure corresponds to our [API endpoint for creating files](https://api.moov.io/#operation/createFile) that the ACH HTTP server expects. + +We have [example ACH files](https://github.com/moov-io/ach/blob/master/test/testdata/ppd-valid.json) in JSON. + +Note: The header `Content-Type: application/json` must be set to parse the file as JSON, otherwise Nacha's format will be assumed. + +### Validate options + +When creating a file the server supports query parameters for setting `ValidateOpts` values. + +Example: `POST /files/create?requireABAOrigin=true&bypassDestination=true` + +| Query Param | Validation Option | +|------------------------------------|------------------------------------| +| `allowMissingFileControl` | `AllowMissingFileControl` | +| `allowMissingFileHeader` | `AllowMissingFileHeader` | +| `allowZeroBatches` | `AllowZeroBatches` | +| `bypassCompanyIdentificationMatch` | `BypassCompanyIdentificationMatch` | +| `bypassDestination` | `BypassDestinationValidation` | +| `bypassOrigin` | `BypassOriginValidation` | +| `customReturnCodes` | `CustomReturnCodes` | +| `customTraceNumbers` | `CustomTraceNumbers` | +| `requireABAOrigin` | `RequireABAOrigin` | +| `unequalServiceClassCode` | `UnequalServiceClassCode` | +| `unorderedBatchNumbers` | `AllowUnorderedBatchNumbers` | + +## Upload a raw ACH file + +Our ACH HTTP server also handles [uploading raw ACH files](https://api.moov.io/#operation/createFile) which is the NACHA text format. We have example files in their NACHA format and example code for creating and reading the files. + +| SEC Code | Description | Example ACH File | Read | Write | +|----------|---------------------------------------|------------------------------------------|-----------------------------------|------------------------------------| +| ACK | Acknowledgment Entry for CCD | [Credit](https://github.com/moov-io/ach/blob/master/test/ach-ack-read/ack-read.ach) | [ACK Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-AckRead) | [ACK Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-AckWrite) | +| ADV | Automated Accounting Advice | [Prenote Debit](https://github.com/moov-io/ach/blob/master/test/ach-adv-read/adv-read.ach) | [ADV Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-AdvRead) | [ADV Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-AdvWrite) | +| ARC | Accounts Receivable Entry | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-arc-read/arc-debit.ach) | [ARC Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-ArcReadDebit) | [ARC Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-ArcWriteDebit) | +| ATX | Acknowledgment Entry for CTX | [Credit](https://github.com/moov-io/ach/blob/master/test/ach-atx-read/atx-read.ach) | [ATX Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-AtxRead) | [ATX Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-AtxWrite) | +| BOC | Back Office Conversion | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-boc-read/boc-debit.ach) | [BOC Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-BocReadDebit) | [BOC Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-BocWriteDebit) | +| CCD | Corporate credit or debit | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-ccd-read/ccd-debit.ach) | [CCD Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CcdReadDebit) | [CCD Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CcdWriteDebit) | +| CIE | Customer-Initiated Entry | [Credit](https://github.com/moov-io/ach/blob/master/test/ach-cie-read/cie-credit.ach) | [CIE Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CieRead) | [CIE Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CieWrite) | +| COR | Automated Notification of Change(NOC) | [NOC](https://github.com/moov-io/ach/blob/master/test/ach-cor-read/cor-read.ach) | [COR Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CorReadCredit) | [COR Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CorWriteCredit) | +| CTX | Corporate Trade Exchange | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-ctx-read/ctx-debit.ach) | [CTX Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CtxReadDebit) | [CTX Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CtxWriteDebit) | +| DNE | Death Notification Entry | [DNE](https://github.com/moov-io/ach/blob/master/test/ach-dne-read/dne-read.ach) | [DNE Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-DneRead) | [DNE Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-DneWrite) | +| ENR | Automatic Enrollment Entry | [ENR](https://github.com/moov-io/ach/blob/master/test/ach-enr-read/enr-read.ach) | [ENR Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-EnrRead) | [ENR Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-EnrWrite) | +| IAT | International ACH Transactions | [Credit](https://github.com/moov-io/ach/blob/master/test/ach-iat-read/iat-credit.ach) | [IAT Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-IatReadMixedCreditDebit) | [IAT Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-IatWriteMixedCreditDebit) | +| MTE | Machine Transfer Entry | [Credit](https://github.com/moov-io/ach/blob/master/test/ach-mte-read/mte-read.ach) | [MTE Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-MteReadDebit) | [MTE Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-MteWriteDebit) | +| POP | Point of Purchase | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-pop-read/pop-debit.ach) | [POP Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PopReadDebit) | [POP Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PopWriteDebit) | +| POS | Point of Sale | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-pos-read/pos-debit.ach) | [POS Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PosReadDebit) | [POS Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PosWriteDebit) | +| PPD | Prearranged payment and deposits | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-ppd-read/ppd-debit.ach) [Credit](https://github.com/moov-io/ach/blob/master/test/ach-ppd-read/ppd-credit.ach) | [PPD Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PpdReadCredit) | [PPD Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PpdWriteCredit) | +| RCK | Represented Check Entries | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-rck-read/rck-debit.ach) | [RCK Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-RckReadDebit) | [RCK Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-RckWriteDebit) | +| SHR | Shared Network Entry | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-shr-read/shr-debit.ach) | [SHR Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-ShrReadDebit) | [SHR Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-ShrWrite) | +| TEL | Telephone-Initiated Entry | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-tel-read/tel-debit.ach) | [TEL Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-TelReadDebit) | [TEL Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-TelWriteDebit) | +| TRC | Truncated Check Entry | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-trc-read/trc-debit.ach) | [TRC Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-TrcReadDebit) | [TRC Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-TrcWriteDebit) | +| TRX | Check Truncation Entries Exchange | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-trx-read/trx-debit.ach) | [TRX Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-TrxReadDebit) | [TRX Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-TrxWriteDebit) | +| WEB | Internet-initiated Entries | [Credit](https://github.com/moov-io/ach/blob/master/test/ach-web-read/web-credit.ach) | [WEB Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-WebReadCredit) | [WEB Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-WebWriteCredit) | +| XCK | Destroyed Check Entry | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-xck-read/xck-debit.ach) | [XCK Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-XckReadDebit) | [XCK Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-XckWriteDebit) | + + +Note: The header `Content-Type: text/plain` should be set. diff --git a/docs/custom-validation.md b/docs/custom-validation.md new file mode 100644 index 000000000..160f5c47c --- /dev/null +++ b/docs/custom-validation.md @@ -0,0 +1,145 @@ +--- +layout: page +title: Custom validation +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Custom validation + +ACH files can vary sometimes from the official NACHA guidelines due to vendor changes. Moov ACH defaults to NACHA guidelines, so to handle this there's an exported `ValidateWith(opts)` method on some structures (`File`, `FileHeader`, etc). + +The [`ValidateOpts`](https://godoc.org/github.com/moov-io/ach#ValidateOpts) struct can have fields added in minor releases without breaking API compatibility with callers. The default values in this struct match with NACHA's guidelines. + +## Validation options + +The following options can be used with `File.ValidateWith` and `File.SetValidation` to alter the default NACHA validation rules. + +### Origin + +`RequireABAOrigin bool` can be set to enable routing number validation over the `ImmediateOrigin` file header field. Often the origin can be another value which is significant to the Financial Institution you're uploading ACH Files to. + +``` +// One-time change to the NACHA validation rules +opts := &ValidateOpts{RequireABAOrigin: true} +if err := file.Header.ValidateWith(opts); err != nil { + // do something... +} +``` + +`BypassOriginValidation bool` can be set to skip all validation for the `ImmediateOrigin` file header field. This also allows for custom TraceNumbers which aren't prefixed with a routing number as required by the NACHA specification. + +``` +// Override the default validation rules on an *ach.File object. +file.SetValidation(&ValidateOpts{ + BypassOriginValidation: true, +}) +if err := file.Validate(); err != nil { + // do something... +} +``` + +### Destination + +`BypassDestinationValidation bool` can be set to skip validation for the `ImmediateDestination` file header field. + +### Transaction Codes + +``` +// CheckTransactionCode allows for custom validation of TransactionCode values +CheckTransactionCode func(code int) error +``` + +### Trace Numbers + +The Nacha/ACH spec requires that trace numbers follow a few rules. This validation option disables them. + +- Ascending order of trace numbers within batches +- Trace numbers beginning with their ODFI's routing number +- `AddendaRecordIndicator` is set correctly + +``` +// CustomTraceNumbers disables validation of TraceNumbers +CustomTraceNumbers bool `json:"customTraceNumbers"` +``` + +### Batches + +``` +// AllowZeroBatches allows the file to have zero batches +AllowZeroBatches bool `json:"allowZeroBatches"` + +// BypassCompanyIdentificationMatch allows batches in which the Company Identification field +// in the batch header and control do not match. +BypassCompanyIdentificationMatch bool `json:"bypassCompanyIdentificationMatch"` + +// UnequalServiceClassCode skips equality checks for the ServiceClassCode in each pair of BatchHeader +// and BatchControl records. +UnequalServiceClassCode bool `json:"unequalServiceClassCode"` + +// AllowUnorderedBatchNumebrs allows a file to be read with unordered batch numbers. +AllowUnorderedBatchNumbers bool `json:"allowUnorderedBatchNumbers"` +``` + +### File Header + +``` +// AllowMissingFileHeader allows a file to be read without a FileHeader record. +AllowMissingFileHeader bool `json:"allowMissingFileHeader"` +``` + +### File Control + +``` +// AllowMissingFileControl allows a file to be read without a FileControl record. +AllowMissingFileControl bool `json:"allowMissingFileControl"` +``` + +### Returns + +``` +// CustomReturnCodes can be set to skip validation for the Return Code field in an Addenda99 +// This allows for non-standard/deprecated return codes (e.g. R97) +CustomReturnCodes bool `json:"customReturnCodes"` +``` + +## Reader + +An `ach.Reader` can have custom validation rules as well, simply set them prior to reading. + +``` +r := ach.NewReader(fd) // create an ach.Reader from an os.File +r.SetValidation(&ach.ValidateOpts{ + BypassDestinationValidation: true, +}) +file, err := r.Read() +if err != nil { + // do something... +} +``` + +## JSON Options + +The JSON representation includes the `ValidateOpts` if specified on the `*ach.File` instance. + +## HTTP server + +The ACH HTTP server can accept `ValidateOpts` when [Validating a file](https://moov-io.github.io/ach/api/#get-/files/{fileID}/validate). This will leverage the above one-off validation methods and return any errors. + +**Create a file** +``` +curl -X POST --data-binary @test/testdata/ppd-debit.ach http://localhost:8080/files/create +``` +``` +{"id":"b1910446fd904abc8b2cee358ffb3673c2cb8a62","error":null} +``` + +**Apply custom validation rules** + +``` +curl -X POST --data-binary '{"requireABAOrigin": true}' http://localhost:8080/files/b1910446fd904abc8b2cee358ffb3673c2cb8a62/validate +``` +``` +{"error":null} +``` diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 000000000..4f699b7f7 --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,103 @@ +--- +layout: page +title: Kubernetes +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Kubernetes + +The following snippet runs the ACH Server on [Kubernetes](https://kubernetes.io/docs/tutorials/kubernetes-basics/) in the `apps` namespace. You could reach the ach instance at the following URL from inside the cluster. + +``` +# Needs to be ran from inside the cluster +$ curl http://ach.apps.svc.cluster.local:8080/ping +PONG + +$ curl http://localhost:8080/files +{"files":[],"error":null} +``` + +Kubernetes manifest - save in a file (`ach.yaml`) and apply with `kubectl apply -f ach.yaml`. + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: apps +--- +apiVersion: v1 +kind: Service +metadata: + name: ach + namespace: apps +spec: + type: ClusterIP + selector: + app: ach + ports: + - name: http + protocol: TCP + port: 8080 + targetPort: 8080 + - name: metrics + protocol: TCP + port: 9090 + targetPort: 9090 +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: ach + namespace: apps + labels: + app: ach +spec: + replicas: 1 + selector: + matchLabels: + app: ach + template: + metadata: + labels: + app: ach + spec: + containers: + - image: moov/ach:v1.0.0 + imagePullPolicy: Always + name: ach + args: + - -http.addr=:8080 + - -admin.addr=:9090 + env: + - name: ACH_FILE_TTL + value: 30m # 30 minutes + ports: + - containerPort: 8080 + name: http + protocol: TCP + - containerPort: 9090 + name: metrics + protocol: TCP + resources: + limits: + cpu: 0.1 + memory: 50Mi + requests: + cpu: 25m + memory: 10Mi + readinessProbe: + httpGet: + path: /ping + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /ping + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 + restartPolicy: Always +``` diff --git a/docs/favicon.png b/docs/favicon.png new file mode 100644 index 000000000..3cc8938c7 Binary files /dev/null and b/docs/favicon.png differ diff --git a/docs/file-structure.md b/docs/file-structure.md new file mode 100644 index 000000000..2fffbf184 --- /dev/null +++ b/docs/file-structure.md @@ -0,0 +1,971 @@ +--- +layout: page +title: File structure +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +## Sequence of records and description + +Each NACHA formatted file you originate consists of the following records: + +* A File Header Record +* One or more Company/Batch Header Record(s) +* Entry Detail Record(s) +* Addenda Record(s), if allowed and you choose to include them, or if required +* One or more Company/Batch Control Record(s) +* A File Control Record + +Each file begins with a File Header record. Following the File Header Record may be any number of batches. Each batch is identified by a Batch Header Record and contains one or more Entry Detail Records. At the end of each batch is a Batch Control Record. Each file is ended with a File Control Record. +This [diagram](https://raw.githubusercontent.com/moov-io/ach/master/docs/ach_file_structure_shg.gif) illustrates the Sequence of Records for ACH entries. The sequence of records will always be the same, regardless of SEC code. Out-of-sequence records or lack of a mandatory record will cause all or portions of the file to reject. Padding with “9” records at the end of the file is optional. + +## Input file descriptions + +### File Header + +The File Header Record designates physical file characteristics and identifies the immediate origin of the entries contained within the file or within the transmitted batched data. In addition, this record includes date, time, and file identification fields that can be used to uniquely identify the file. + +### Company/Batch Header Record + +The Company/Batch Header Record identifies the Originator and briefly describes the purpose of the entries that are contained within the batch. For example, “GAS BILL” or “REG SALARY” indicates the reason for the transaction originated by the Originator. It also indicates the intended effective entry date of all transactions within the batch. The information contained in the Company/Batch Header Record applies uniformly to all subsequent Entry Detail Records in the batch. +If you wish to vary any of this information, you must create a separate batch. For example, if you are making regular payroll payments and bonus payments then you should create one batch described as “REG SALARY” and another as “BONUS”. + +### Entry Detail Record + +Entry Detail Records contain information that relate the specific entry to the Receiver, such as the Receiving Depository Financial Institution account, routing transit number, and the debit or credit amount. +Prenotifications (prenotes) are special zero-dollar entries used to test the validity of the account number and transit routing number provided by the Receiver. Prenotes are identical to the basic Entry Detail format but contain appropriate Transaction Codes and zeroes in the amount field. Prenotes can be batched with other dollar entries or batched separately. +Zero-dollar entries used in corporate trade payments to deliver remittance information contain appropriate Transaction Codes and zeros in the Amount field but otherwise are formatted the same as other entries. Zero-dollar entries can be batched with other CCD dollar entries or batched separately. One Addenda Record must accompany a CCD zero-dollar entry. + +### Addenda Records + +For non-IAT entries, Addenda Records are used by the Originator to supply additional information about Entry Detail Records to the Receiver. For many types of entries, such as payroll, addenda records are optional. Addenda Records are usually required for tax payments. + +### Company/Batch Control Record + +The Company/Batch Control Record contains the counts, hash totals, and total dollar controls for the preceding detail entries within the indicated batch. +All Entry Detail Records are hashed. (The method for calculating hash totals is provided in the Entry Information column in the Record Layouts.) Both Entry Detail Records and Addenda Records are included in the entry/addenda counts; Batch Header and Batch Control Records are not included. + +### File Control Record + +The File Control Record contains dollar, entry, and hash total accumulations from the Company/Batch Control Records in the file. This record also contains counts of the number of blocks and the number of batches within the file (or batched data transmitted to a single destination). + +## NACHA Data Entry Specifications + +All alphanumeric and alphabetic fields must be left justified and space filled. All numeric fields must be right justified, unsigned, and zero filled. Characters used in ACH records are restricted to 0-9, A-Z, space, and those special characters which have an EBCDIC value greater than hexadecimal "3F" or an ASCII value greater than hexadecimal "1F.” Occurrences of values EBCDIC "00" - "3F" and ASCII "00" - "1F" are not valid. +Do not use characters that do not meet these requirements. + +### Field Inclusion Requirements + +The following information defines the requirement for inclusion of certain data fields in ACH entries. These designations are: Mandatory (M), Required (R), and Optional (O). + +* **Mandatory.** A “Mandatory” field contains information necessary to ensure the proper routing and/or posting of an ACH entry. The ACH Operator will reject any entry or batch, which does not have appropriate values in a Mandatory field. +* **Required.** The omission of a “Required” field will not cause an entry reject at the ACH Operator, but may cause a reject at the RDFI. For example, if the DFI Account Number field in the Entry Detail Record is omitted, the RDFI may return the entry because it cannot be posted. You should include appropriate values in “Required” fields to avoid processing and control problems at the RDFI. +* **Optional.** The inclusion or omission of an “Optional” data field is at the discretion of the Originator. If you do include optional fields, the RDFI must include them in any return. + +## Annotated NACHA Record Formats + +### File Header Record - All Formats + +The File Header Record designates physical file characteristics. It also identifies the Bank as the immediate destination and your company as the immediate origin of the file. + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '1' | Record Type Code | Code Identifying the File Header Record is '1' | M | +| *2* | 02-03 | 2 | '01' | Priority Code | Currently, only "01" is used | R | +| *3* | 04-13 | 10 | bNNNNNNNNN | Immediate Destination | The Immediate Destination Field identifies the party to which the file is being delivered. Usually the Routing Number of the ACH operator. | M | +| *4* | 14-23 | 10 | NNNNNNNNNN | Immediate Origin | The Immediate Origin Field identifies the sender of the file. | M | +| *5* | 24-29 | 6 | YYMMDD | File Creation Date | The date you create or transmit the input file:
“YY” = Last two digits of the Year
“MM” = Month in two digits
“DD” = Day in two digits. | M | +| *6* | 30-33 | 4 | HHMM | File Creation Time | Time of day you create or transmit the input file. This field is used to distinguish among input files if you submit more than one per day:
“HH = Hour based on a 24 hr clock
“MM” = Minutes in two digits. | O | +| *7* | 34-34 | 1 | UPPER CASE A-Z (or 0-9) | File ID Modifier | Code to distinguish among multiple input files sent per day. Label the first (or only) file “A” (or “0”) and continue in sequence. | M | +| *8* | 35-37 | 3 | "094" | Record Size | Number of bytes per record-always 94. | M | +| *9* | 38-39 | 2 | "10" | Blocking Factor | Number of records per block. | M | +| *10* | 40-40 | 1 | "1" | Format Code | Currently only “1” is used. | M | +| *11* | 41-63 | 23 | Alphameric | Immediate Destination Name | This field contains the name of the ACH Operator or Receiving Point for which that File is destined. | M | +| *12* | 64-86 | 23 | Alphameric | Immediate Origin or Company Name | This field contains the name of the ACH Operator or Sending Point that is Transmitting the File. | M | +| *13* | 87-94 | 8 | Alphameric | Reference Code | You may use this field to describe the input file for internal accounting purposes or fill with spaces. | O | + +## Company/Batch Header Record Formats + +A batch is a collection of like entries within a file. You must use a separate batch if any of the batch-level information, such as effective date or company name or company description changes. + +**Batch Header Record - All SECs Except IAT** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '5' | Record Type Code | Code Identifying the Company /Batch Header Record is '5'. | M | +| *2* | 02-04 | 3 | Numeric | Service Class Code | Identifies the type of entries in the batch 200 - ACH Entries Mixed Debits and Credits. | M | +| *3* | 05-20 | 16 | Alpha-Numeric | Company Name | Company Name | O | +| *4* | 21-40 | 20 | Alpha-Numeric | Company Discretionary Data | The use of this field is defined by the ODFI. | O | +| *5* | 41-50 | 10 | Numeric | Company Identification | 10 Digit Company Number. | M | +| *6* | 51-53 | 3 | Alpha | Standard Entry Class Code | Standard Entry Class Code. | M | +| *7* | 54-63 | 10 | Alpha-Numeric | Company Entry Description | Transaction Description. | M | +| *8* | 64-69 | 6 | Alpha-Numeric | Company Descriptive Date | The date you choose to identify the transaction. | O | +| *9* | 70-75 | 6 | YYMMDD | Effective Entry Date| Date transactions are to be posted to the participants account. | R | +| *10* | 76-78 | 3 | Blanks | Settlement Date (Julian) | Inserted by ACH Operator | Inserted by ACH Operator. | +| *11* | 79-79 | 1 | Numeric | Originator Status Code | "1" | M | +| *12* | 80-87 | 8 | Numeric | Originating DFI Identification | Standard Entry Detail Trace Number. | M | +| *13* | 88-94 | 9 | Numeric | Batch Number | Number Batches Sequentially. | M | + +## Company/Batch Control Record Formats + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '8' | Record Type Code | Code Identifying the Company /Batch Header Record is '8' | M | +| *2* | 02-04 | 3 | '200'
'220'
'225' | Service Class Code | Identifies the type of entries in the batch. Must match the value used in the Batch Header Record. | M | +| *3* | 05-10 | 6 | Numeric | Entry/Addenda Count | Total Number of Entry Detail Records plus addenda records (Record Types "6" and "7") in the batch. Requires 6 positions, right-justify, left zero fill. | M | +| *4* | 11-20 | 20 | Numeric | Entry Hash | Total of eigh-character Transit Routing/ACA numbers in the batch. Do not include the Transit Routing Check Digit. Enter the ten low-order (right most) digits of this number. | M | +| *5* | 21-32 | 12 | $$$$$$$$$$¢¢ | Total Debit Entry Dollar Amount in Batch | Dollar total of debit entries in the batch. If none, zer-fill the field. Do not enter a decimal point. Right-justify, left zero-fill. | M | +| *6* | 33-44 | 12 | $$$$$$$$$$¢¢ | Total Credit Entry Dollar Amount in Batch | Dollar total of credit entries in the batch. If none, zer-fill the field. Do not enter a decimal point. Right-justify, left zero-fill. | M | +| *7* | 45-54 | 10 | NNNNNNNNNN | Company Identification | 10 Digit Company Number. | M | +| *8* | 55-73 | 19 | Alpha-Numeric | Message Authentication Code | The MAC is an-eight character code derived from a special key used in conjunction with the DES algorithm. The MAC is used to validate the authenticity of ACH Entries. The DES algorithm and key message standards must be in accordance with standards adopted by the American National Standards Institute. The remaining eleven characters of this field are blank. | O | +| *9* | 74-79 | 6 | Alpha-Numeric | Reserved| Leave Blank | n/a | +| *10* | 80-87 | 8 | NNNNNNNN | Originating DFI Identification | Standard Entry Detail Trace Number. | M | +| *11* | 88-94 | 9 | Numeric | Batch Number | Number Batches Sequentially. Must match that of the Batch Header. | M | + +## ACK Acknowledgement Record (CCD) + +The ACK entry is an acknowledgement by the Receiving Depository Financial Institution (RDFI) that a Corporate Credit (CCD) has been received. + +**ACK Entry Detail Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alpha-Numeric | DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. | M | +| *7* | 40-54 | 15 | Alpha-Numeric | Original Entry trace Number | The original Detail Record trace number. | M | +| *8* | 55-76 | 22 | Alpha-Numeric | Receiving Company Name | Receiver's Company Name. | M | +| *9* | 77-78 | 2 | Alpha-Numeric | Discretionary Data | The use of this field is defined by the ODFI. | O | +| *10* | 79-79 | 1 | Numeric | Addenda Record Indicator | "0" = no addenda
"1" = one addenda included. | M | +| *11* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +**ACK Addenda Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Entry Detail Record is '7' | M | +| *2* | 02-03 | 2 | '05' | Addenda Type Code | The Addenda Type Code defines the specific interpretation and format for the addenda information contained in the Entry. | M | +| *3* | 04-83 | 80 | Alpha-Numeric | Payment Related Information | This field contains payment related ANSI ASC X12 data segments to further identify the payment or Transmit additional remittance information. | O | +| *4* | 84-87 | 4 | Numeric | Addenda Sequence Number | This number is consecutively assigned to each Addenda Record following an Entry Detail Record. The first addenda sequence number must always be a “1.” | M | +| *5* | 88-94 | 7 | Numeric | Entry Detail Sequence Number | This field contains the ascending sequence number section of the Entry Detail or Corporate Entry Detail Record’s trace number. This number is the same as the last seven digits of the trace number of the related Entry Detail Record or Corporate Entry Detail Record. | M | + +## ADV Automated Accounting Advice + +The ADV entry identifies a Non-Monetary Entry that is used by an ACH Operator to provide accounting information regarding an entry to participating DFI's. It's an optional service provided by ACH operators and must be requested by a DFI wanting the service. + +**ADV Batch Control Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '8' | Record Type Code | Code Identifying the Company /Batch Header Record is '8'. | M | +| *2* | 02-04 | 3 | '200'
'220'
'225' | Service Class Code | Identifies the type of entries in the batch. Must match the value used in the Batch Header Record. | M | +| *3* | 05-10 | 6 | Numeric | Entry/Addenda Count | Total Number of Entry Detail Records plus addenda records (Record Types "6" and "7") in the batch. Requires 6 positions, right-justify, left zero fill. | M | +| *4* | 11-20 | 10 | Numeric | Entry Hash | Total of eight-character Transit Routing/ACA numbers in the batch. Do not include the Transit Routing Check Digit. Enter the ten low-order (right most) digits of this number. | M | +| *5* | 21-40 | 20 | $$$$$$$$$$¢¢ | Total Debit Entry Dollar Amount in Batch | Dollar total of debit entries in the batch. If none, zero-fill the field. Do not enter a decimal point. Right-justify, left zero-fill. | M | +| *6* | 41-60 | 20 | $$$$$$$$$$¢¢ | Total Credit Entry Dollar Amount in Batch | Dollar total of credit entries in the batch. If none, zero-fill the field. Do not enter a decimal point. Right-justify, left zero-fill. | M | +| *7* | 61-79 | 19 | Alpha-Numeric | ACH Operator Data | This field is used as specified by the ACH operator. | O | +| *8* | 80-87 | 8 | NNNNNNNN | Originating DFI Identification | Originating DFI Identification. | M | +| *9* | 88-94 | 7 | Numeric | Batch Number | Number Batches Sequentially. Must match that of the Batch Header. | M + +**ADV File Control Record** + +The File Control record contains dollar, entry, and hash totals from the file's Company/Batch Control Records. This record also contains counts of the blocks and batches in the file. + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '9' | Record Type Code | Code Identifying the File Control Record is '9'. | M | +| *2* | 02-07 | 6 | Numeric | Batch Count | Total number of Company/Batch Header Records (Record Type “5”) in the file. | M | +| *3* | 08-13 | 6 | Numeric | Block Count | Total number of physical blocks in the file, including the File Header and File Control Records. | M | +| *4* | 14-21 | 8 | Numeric | Entry / Addenda Count | Total number of Entry Detail and Addenda Records (Record Types “6” and “7”) in the file. | M | +| *5* | 22-31 | 10 | Numeric | Entry Hash | Total of eight character Transit Routing/ABA numbers in the file (Field 3 of the Entry Detail Record). Do not include the Transit Routing Check Digit. Enter the 10 low-order (right most) digits of this number. For example,if this sum is 998877665544, enter 8877665544. | M | +| *6* | 32-51 | 20| $$$$$$$$$$$$$$$$$$$$¢¢ | Total Debit Entry Dollar Amount in File | Dollar total of debit entries in the file. If none, zero-fill the field. Do not enter a decimal point. Right-justify, left zero-fill. | M | +| *7* | 52-71 | 20 | $$$$$$$$$$$$$$$$$$$¢¢ | Total Credit Entry Dollar Amount in File | Dollar total of credit entries in the file. If none, zero-fill the field. Do not enter a decimal point. Right-justify, left zero-fill. | M | +| *8* | 72-94 | 23 | blank | Reserved | Leave this field blank. | n/a | + +**ADV Detail Entry** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-27 | 15 | Alpha-Numeric | DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 28-39 | 12 | $$$$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. | M | +| *7* | 40-48 | 9 | Numeric | Advice Routing Number | This field contains the Routing Number and Check Digit of the DFI or Correspondent, as defined by the ACH Operator. | M | +| *8* | 49-53 | 5 | Alpha-Numeric | File Identification | This field contains the File Creation Date and File ID Modifier. | O | +| *9* | 54-54 | 1 | Alpha-Numeric | ACH Operator Data | This field is used as specified by the ACH operator. | O | +| *10* | 55-76 | 22 | Alpha-Numeric| Individual Name | The name associated with the advice routing number. | R | +| *11* | 77-78 | 2 | Alpha-Numeric | Discretionary Data | The use of this field is defined by the ODFI. | O | +| *12* | 79-79 | 1 | Numeric | Addenda Record Indicator | "0" = no addenda
"1" = one addenda included. | M | +| *13* | 80-87 | 8 | TTTTAAAA | Routing Number of ACH Operator | The Routing number of th ACH operator. | M | +| *14* | 87-90 | 3 | Numeric| Julian Date | Julian Date on which this advice is created. | M | +| *15* | 91-94 | 4 | Numeric | Sequence Number | Sequence Number Within Batch. | M | + +## ARC Accounts Receivable Entry + +**Accounts Receivable Entry** A consumer check converted to a one-time ACH debit. The difference between ARC and POP is that ARC can result from a check mailed in where as POP is in-person. + +**ARC Accounts Receivable Detail Record** + +The Accounts Receivable (ARC) Entry provides billers the opportunity to initiate single-entry ACH +debits to customer accounts by converting checks at the point of receipt through the U.S. mail, at +a drop box location or in-person for payment of a bill at a manned location. The biller is required +to provide the customer with notice prior to the acceptance of the check that states the receipt of +the customer’s check will be deemed as the authorization for an ARC debit entry to the customer’s +account. The provision of the notice and the receipt of the check together constitute authorization +for the ARC entry. The customer’s check is solely be used as a source document to obtain the routing +number, account number and check serial number. + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alpha-Numeric | DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. | M | +| *7* | 40-54 | 9 | Alpha-Numeric | Check Serial Number |The serial number of the check being represented. | M | +| *8* | 55-76 | 22 | Alpha-Numeric | Individual Name | Receiver's Name. | O | +| *9* | 77-78 | 2 | Blank | Discretionary Data | The use of this field is defined byu the ODFI. | O | +| *10* | 79-79 | 1 | Numeric | Addenda Record Indicator | "0" = no addenda
"1" = one addenda included. | O | +| *11* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +## ATX Acknowledgment Record (CTX) + +The ATX entry is an acknowledgment by the Receiving Depository Financial Institution (RDFI) that a Corporate Credit (CTX) has been received. + +**ATX Entry Detail Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6' | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. Valid codes are:
22 = Automated deposit (checking credit)
23 = Prenote of checking credit
24 = Zero-dollar checking credit with remittance data (CCD & CTX entries only)
27 = Automated payment (checking debit)
28 = Prenote of checking debit
29 = Zero-dollar checking debit with remittance data (CCD & CTX entries only)
32 = Automated deposit (savings credit)
33 = Prenote of savings credit
34 = Zero-dollar savings credit with remittance data (CCD & CTX entries only)
37 = Automated payment (savings debit)
38 = Prenote of savings debit
39 = Zero-dollar savings debit with remittance data (CCD & CTX entries only). | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alpha-Numeric | DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check.| R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. Right-justified, left zero-filled, without a decimal point. Enter 10 zeros for non-dollar prenote entries. | M | +| *7* | 40-54 | 15 | Alpha-Numeric | Identification Number | This field contains the accounting number by which the Receiver is known to the Originator for descriptive purposes. NACHA Rules recommend but do not require the RDFI to print the contents of this field on the receiver‟s statement. | R | +| *8* | 55-58 | 4 | Numeric | Number of Addenda Records | Number of addenda records associated with this transaction. | M | +| *9* | 59-74 | 16 | Alpha-Numeric | Receiving Company Name/ID Number | Name of Receiver. | M | +| *10* | 75-76 | 2 | blank | reserved | Leave blank | n/a | +| *11* | 77-78 | 2 | blank | Discretionary Data Field | The use of this field is defined by the ODFI. | O | +| *12* | 79-79 | 1 | Numeric | Addenda Record Indicator | '1' = addenda included. | O | +| *13* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +**ATX Addenda Record** + +Up to 9,999 Addenda Records may be included with a ATX Entry Detail Record. The addenda should not be used with pre notes. + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Entry Detail Record is '7'. | M | +| *2* | 02-03 | 2 | '05' | Addenda Type Code | The Addenda Type Code defines the specific interpretation and format for the addenda information contained in the Entry. | M | +| *3* | 04-83 | 80 | Alpha-Numeric | Payment Related Information | This field contains payment related ANSI ASC X12 data segments to further identify the payment or Transmit additional remittance information. | O | +| *4* | 84-87 | 4 | Numeric | Addenda Sequence Number | This number is consecutively assigned to each Addenda Record following an Entry Detail Record. The first addenda sequence number must always be a “1.” | M | +| *5* | 88-94 | 7 | Numeric | Entry Detail Sequence Number | This field contains the ascending sequence number section of the Entry Detail or Corporate Entry Detail Record’s trace number. This number is the same as the last seven digits of the trace number of the related Entry Detail Record or Corporate Entry Detail Record. | M | + +## BOC Back Office Conversion + +**Back Office Conversion Entry** A single entry debit initiated at the point of purchase or at a manned bill payment location to transfer funds through conversion to an ACH debit entry during back office processing. Unlike ARC entries, BOC conversions require the customer to be present and a notice that checks may be converted to BOC ACH entries be posted. + +**BOC Back Office Conversation Detail Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alpha-Numeric | DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. | M | +| *7* | 40-54 | 9 | Alpha-Numeric | Check Serial Number |The serial number of the check being represented. | M | +| *8* | 55-76 | 22 | Alpha-Numeric | Individual Name | Receiver's Name. | O | +| *9* | 77-78 | 2 | Blank | Discretionary Data | The use of this field is defined byu the ODFI. | O | +| *10* | 79-79 | 1 | Numeric | Addenda Record Indicator | "0" = no addenda
"1" = one addenda included. | O | +| *11* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +## CCD Corporate Credit or Debit + +**CCD Corporate Credit or Debit** is an entry used to facilitate business-to-business (B2B) ACH Payments. + +**CCD Corporate Credit or Debit Detail Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6' | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alpha-Numeric | DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. | M | +| *7* | 40-54 | 15 | Alpha-Numeric | Identification Number | Receiver's identification number. | O | +| *8* | 55-76 | 22 | Alpha-Numeric | Receiving Company Name | Receiver's Name. | R | +| *9* | 77-78 | 2 | Alpha-Numeric | Discretionary Data | For your company's internal use if desired. No specific format is required. | O | +| *10* | 79-79 | 1 | Numeric | Addenda Record Indicator | "0" = no addenda
"1" = one addenda included. | M | +| *11* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +**CCD Corporate Credit or Debit Addenda Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Entry Detail Record is '7' | M | +| *2* | 02-03 | 2 | '05' | Addenda Type Code | The Addenda Type Code defines the specific interpretation and format for the addenda information contained in the Entry. | M | +| *3* | 04-83 | 7 | Alpha-Numeric | Payment Related Information | This field contains payment related ANSI ASC X12 data segments to further identify the payment or Transmit additional remittance information. | O | +| *4* | 84-87 | 4 | Numeric | Addenda Sequence Number | This number is consecutively assigned to each Addenda Record following an Entry Detail Record. The first addenda sequence number must always be a “1”. | M | +| *5* | 88-94 | 7 | Numeric | Entry Detail Sequence Number | This field contains the ascending sequence number section of the Entry Detail or Corporate Entry Detail Record’s trace number. This number is the same as the last seven digits of the trace number of the related Entry Detail Record or Corporate Entry Detail Record. | M | + +## CIE Customer-Initiated Entries + +**Customer-Initiated Entry** is a credit entry initiated on behalf of, and upon the instruction of, a consumer to transfer funds to a non-consumer Receiver. CIE entries are usually transmitted to a company for payment of funds that the consumer owes to that company and are initiated by the consumer through some type of online banking product or bill payment service provider. With CIEs, funds owed by the consumer are “pushed” to the biller in the form of an ACH credit, as opposed to the billers use of a debit application (e.g., PPD, WEB) to “pull” the funds from a customer’s account. + +**CIE Entry Detail Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alpha-Numeric | DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. | M | +| *7* | 40-54 | 15 | Alpha-Numeric | Individual Name | This field is entered by the ODFI to provide additional identification for the Receiver and may be helpful in identifying returned Entries. Field 7, rather than Field 8, of the Entry Detail Record is used for the Individual Name. | R | +| *8* | 55-76 | 22 | Alpha-Numeric | Individual Identification Number | This field contains the accounting number by which the Originator (payor) is known to the Receiver (payee). It is used by the Receiver to update accounts receivable Records. It should be the number shown on an invoice, statement, billhead, notice, or other communication as the reference. Numbers may be policy, customer, invoice, meter, sequence, and/or alphanumeric combinations. Field 8, rather than Field 7, of the Entry Detail Record is used for the Individual Identification Number. | M | +| *9* | 77-78 | 2 | Alpha-Numeric | Discretionary Data | This field in the Entry Detail Record allows ODFIs to include codes, of significance to them, to enable specialized handling of the Entry. There is no standardized interpretation for the value of this field. It can either be a single two-character code, or two distinct one-character codes, according to the needs of the ODFI and/or Originator involved. This field must be returned intact for any returned Entry. | O | +| *10* | 79-79 | 1 | Numeric | Addenda Record Indicator | "0" = no addenda
"1" = one addenda included. | M | +| *11* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +**CIE Addenda Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Entry Detail Record is '7'. | M | +| *2* | 02-03 | 2 | '05' | Addenda Type Code | The Addenda Type Code defines the specific interpretation and format for the addenda information contained in the Entry. | M | +| *3* | 04-83 | 80 | Alpha-Numeric | Payment Related Information | This field contains payment related ANSI ASC X12 data segments to further identify the payment or Transmit additional remittance information. | O | +| *4* | 84-87 | 4 | Numeric | Addenda Sequence Number | This number is consecutively assigned to each Addenda Record following an Entry Detail Record. The first addenda sequence number must always be a “1”. | M | +| *5* | 88-94 | 7 | Numeric | Entry Detail Sequence Number | This field contains the ascending sequence number section of the Entry Detail or Corporate Entry Detail Record’s trace number. This number is the same as the last seven digits of the trace number of the related Entry Detail Record or Corporate Entry Detail Record. | M | + +## CTX Corporate Trade Exchange + +The Corporate Trade Exchange (CTX) application provides the ability to collect and disburse funds and information between companies. Generally it is used by businesses paying one another for goods or services. These payments replace checks with an electronic process of debiting and crediting invoices between the financial institutions of participating companies. + +**CTX Entry Detail Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. Valid codes are:
22 = Automated deposit (checking credit)
23 = Prenote of checking credit
24 = Zero-dollar checking credit with remittance data (CCD & CTX entries only)
27 = Automated payment (checking debit)
28 = Prenote of checking debit
29 = Zero-dollar checking debit with remittance data (CCD & CTX entries only)
32 = Automated deposit (savings credit)
33 = Prenote of savings credit
34 = Zero-dollar savings credit with remittance data (CCD & CTX entries only)
37 = Automated payment (savings debit)
38 = Prenote of savings debit
39 = Zero-dollar savings debit with remittance data (CCD & CTX entries only). | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alpha-Numeric | DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. Right-justified, left zero-filled, without a decimal point. Enter 10 zeros for non-dollar prenote entries. | M | +| *7* | 40-54 | 15 | Alpha-Numeric | Identification Number | This field contains the accounting number by which the Receiver is known to the Originator for descriptive purposes. NACHA Rules recommend but do not require the RDFI to print the contents of this field on the receiver‟s statement. | R | +| *8* | 55-58 | 4 | Numeric | Number of Addenda Records | Number of addenda records associated with this transaction. | M | +| *9* | 59-74 | 16 | Alpha-Numeric | Receiving Company Name/ID Number | Name of Receiver. | M | +| *10* | 75-76 | 2 | blank | reserved | Leave blank | n/a | +| *11* | 77-78 | 2 | blank | Discretionary Data Field | The use of this field is defined by the ODFI. | O | +| *12* | 79-79 | 1 | Numeric | Addenda Record Indicator | '1' = addenda included. | O | +| *13* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +**CTX Addenda Record** + +Up to 9,999 Addenda Records may be included with a CTX Entry Detail Record. The addenda should not be used with prenotes. + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Entry Detail Record is '7'. | M | +| *2* | 02-03 | 2 | '05' | Addenda Type Code | The Addenda Type Code defines the specific interpretation and format for the addenda information contained in the Entry. | M | +| *3* | 04-83 | 80 | Alpha-Numeric | Payment Related Information | This field contains payment related ANSI ASC X12 data segments to further identify the payment or Transmit additional remittance information. | O | +| *4* | 84-87 | 4 | Numeric | Addenda Sequence Number | This number is consecutively assigned to each Addenda Record following an Entry Detail Record. The first addenda sequence number must always be a “1”. | M | +| *5* | 88-94 | 7 | Numeric | Entry Detail Sequence Number | This field contains the ascending sequence number section of the Entry Detail or Corporate Entry Detail Record’s trace number. This number is the same as the last seven digits of the trace number of the related Entry Detail Record or Corporate Entry Detail Record. | M | + +## DNE Death Notification Entry + +Death Notification Entry (DNE) is a batch file sent from United States Federal agencies (e.g. Social Security) notifying depository financial institutions to the death of a government benefits receiver. + +**DNE Entry Detail** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alpha-Numeric | DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. Must be 10 zeros for DNE. | M | +| *7* | 40-54 | 15 | Alpha-Numeric | Identification Number | This field contains the accounting number by which the Receiver is known to the Originator for descriptive purposes. NACHA Rules recommend but do not require the RDFI to print the contents of this field on the receiver‟s statement. | O | +| *8* | 55-76 | 22 | Alpha-Numeric | Individual Name | Receiver's Name. | R | +| *11* | 77-78 | 2 | blank | Discretionary Data Field | The use of this field is defined by the ODFI. | O | +| *12* | 79-79 | 1 | Numeric | Addenda Record Indicator | '1' = addenda included. | M | +| *13* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +**DNE Addenda** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Entry Detail Record is '7' | M | +| *2* | 02-03 | 2 | '05' | Addenda Type Code | The Addenda Type Code defines the specific interpretation and format for the addenda information contained in the Entry. | M | +| *3* | 04-83 | 80 | Alpha-Numeric | Payment Related Information | This field contains payment related ANSI ASC X12 data segments to further identify the payment or Transmit additional remittance information. | O | +| *4* | 84-87 | 4 | Numeric | Addenda Sequence Number | This number is consecutively assigned to each Addenda Record following an Entry Detail Record. The first addenda sequence number must always be a “1”. | M | +| *5* | 88-94 | 7 | Numeric | Entry Detail Sequence Number | This field contains the ascending sequence number section of the Entry Detail or Corporate Entry Detail Record’s trace number. This number is the same as the last seven digits of the trace number of the related Entry Detail Record or Corporate Entry Detail Record. | M | + +## ENR Automated Enrollment Entry + +ENR (Automated Enrollment Entry) is a non-monetary entry that enrolls a person with an agency of the US government for a depository financial institution. + +**ENR Entry Detail** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alpha-Numeric | DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. Must be 10 zeros for ENR. | M | +| *7* | 40-54 | 15 | Alpha-Numeric | Identification Number | This field contains the accounting number by which the Receiver is known to the Originator for descriptive purposes. NACHA Rules recommend but do not require the RDFI to print the contents of this field on the receiver‟s statement. | O | +| *8* | 55-58 | 4 | Numeric | Number of Addenda Records | How many Addenda05 records are on this entry. | M | +| *9* | 59-74 | 35 | Alphameric | Receiving Company Name/Individual Name | Receiver's name. | M | +| *10* | 75-76 | 2 | Blank | Reserved | N/A | M | +| *11* | 77-78 | 2 | blank | Discretionary Data Field | The use of this field is defined by the ODFI. | O | +| *12* | 79-79 | 1 | Numeric | Addenda Record Indicator | '1' = addenda included. | M | +| *13* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +**ENR Addenda** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Entry Detail Record is '7' | M | +| *2* | 02-03 | 2 | '05' | Addenda Type Code | The Addenda Type Code defines the specific interpretation and format for the addenda information contained in the Entry. | M | +| *3* | 04-83 | 80 | Alpha-Numeric | Payment Related Information | This field contains payment related ANSI ASC X12 data segments to further identify the payment or Transmit additional remittance information. | R | +| *4* | 84-87 | 4 | Numeric | Addenda Sequence Number | This number is consecutively assigned to each Addenda Record following an Entry Detail Record. The first addenda sequence number must always be a “1”. | M | +| *5* | 88-94 | 7 | Numeric | Entry Detail Sequence Number | This field contains the ascending sequence number section of the Entry Detail or Corporate Entry Detail Record’s trace number. This number is the same as the last seven digits of the trace number of the related Entry Detail Record or Corporate Entry Detail Record. | M | + +## MTE Machine Transfer Entry + +Machine Transfer Entry (MTE) supports the clearing of transactions from automated teller machines. + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alpha-Numeric | DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. Must be 10 zeros for ENR. | M | +| *7* | 40-54 | 15 | Alpha-Numeric | Individual Name | Receiver's Name. | M | +| *8* | 55-76 | 22 | Alpha-Numeric | Identification Number | This field contains the accounting number by which the Receiver is known to the Originator for descriptive purposes. NACHA Rules recommend but do not require the RDFI to print the contents of this field on the receiver‟s statement. | M | +| *9* | 77-78 | 2 | Alphameric | Discretionary Data Field | The use of this field is defined by the ODFI. | O | +| *10* | 79-79 | 1 | Numeric | Addenda Record Indicator | '1' = addenda included. | M | +| *11* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + + +**MTE Addenda** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Entry Detail Record is '7' | M | +| *2* | 02-03 | 2 | '02' | Addenda Type Code | The Addenda Type Code defines the specific interpretation and format for the addenda information contained in the Entry. | M | +| *3* | 04-10 | 7 | Alphameric | Transaction Description | This field describes the transaction in accordance with Regulation E. Possible descriptions include: CHK–DEP (Checking Deposit) SAV–DEP (Savings Deposit) PAYMENT CHK–SAV (Transfer: checking to savings) SAV–CHK (Transfer: savings to checking) CHK–WDL (Checking Withdrawal) SAV–WDL (Savings Withdrawal) ADVANCE (Credit Card Cash Advance). | R | +| *4* | 11-13 | 4 | Alphameric | Network Identification Code | This field uniquely identifies an ATM network and allows for processing of MTE transactions between DFIs belonging to different networks. | O | +| *5* | 14-19 | 6 | Alphameric | Terminal Identification code | This field identifies an Electronic terminal with a unique code that allows a terminal owner and/ or switching network to identify the terminal at which an Entry originated. | R | +| *6* | 20-25 | 6 | Alpha-Numeric | Transaction Serial Number | Entry amount in dollars with two decimal places. | R | +| *7* | 26-29 | 4 | MMDD | Transaction Date | This date, expressed MMDD, identifies the date on which the transaction occurred. | R | +| *8* | 30-35 | 6 | HHMMSS | Transaction Time | This time, expressed HHMMSS, identifies the time on which the transaction occurred. | R | +| *9* | 36-62 | 27 | Alpha-Numeric | Terminal Location | This field identifies the specific location of a terminal. (i.e., street names of an intersection, address, etc.) in accordance with the requirements of Regulation E. | R | +| *10* | 63-77 | 15 | Alpha-Numeric | Terminal City | Identifies the city in which the electronic terminal is located. | R | +| *11* | 78-79 | 2 | Alpha-Numeric | Terminal State | Identifies the state in which the electronic terminal is located. | R | +| *12* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +## POP Point-of-Purchase + +**Point-of-Purchase Entry** A check presented in-person to a merchant for purchase is presented as an ACH entry instead of a physical check. + +This ACH debit application is used by originators as a method of payment for the in-person purchase of goods or services by consumers. These Single Entry debit entries are initiated by the originator based on a written authorization and account information drawn from the source document (a check) obtained from the consumer at the point-of-purchase. The source document, which is voided by the merchant and returned to the consumer at the point-of-purchase, is used to collect the consumer’s routing number, account number and check serial number that will be used to generate the debit entry to the consumer’s account. + +**POP Point-of-Purchase Detail Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alphameric| DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. | M | +| *7* | 40-48 | 9 | Alphameric| Check Serial Number | This field contains the Check Serial Number of a Check. | M | +| *8* | 49-52 | 4 | Alphameric| Terminal City | This field contains a truncated name or abbreviation to identify the city, town, village, or township in which the Electronic terminal is located. | M | +| *9* | 53-54 | 2 | Alphameric| Terminal State | This field identifies the state of the United States in which an Electronic terminal is located. | M | +| *10* | 55-76 | 22 | Alphameric| Individual Name | Receiver's Name. | R | +| *11* | 77-78 | 2 | Alphameric| Discretionary Data | The use of this field is defined by the ODFI. | O | +| *12* | 79-79 | 1 | Numeric | Addenda Record Indicator | "0" = no addenda
"1" = one addenda included. | M | +| *13* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +## POS Point-of-Sale + +**Point of Sale Entry** A POS Entry is a debit Entry initiated at an “electronic terminal” to a Consumer Account of the Receiver to pay an obligation incurred in a point- of-sale transaction, or to effect a point-of-sale terminal cash withdrawal. + +Point-of-Sale Entries (POS) are ACH debit entries typically initiated by the use of a merchant-issued plastic card to pay an obligation at the point-of-sale. Much like a financial institution issued debit card, the merchant- issued debit card is swiped at the point-of-sale and approved for use; however, the authorization only verifies the card is open, active and within the card’s limits—it does not verify the Receiver’s account balance or debit the account at the time of the purchase. Settlement of the transaction moves from the card network to the ACH Network through the creation of a POS entry by the card issuer to debit the Receiver’s account. + +**POS Entry Detail Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alphameric| DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check| R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. | M | +| *7* | 40-54 | 9 | Alphameric| Individual Identification Number |The serial number of the check being represented. | O | +| *8* | 55-76 | 22 | Alphameric| Individual Name | Receiver's Name. | R | +| *9* | 77-78 | 2 | Alphameric| Card Transaction Type | This code is used by card processors to identify the type of transaction, such as a purchase, cash advance, or reversal. Values for this field are assigned by the major card Organizations. Code Values:
01 Purchase of goods or services
02 Cash
03 Return Reversal
11 Purchase Reversal
12 Cash Reversal
13 Return
21 Adjustment
99 Miscellaneous Transaction. | M | +| *10* | 79-79 | 1 | Numeric | Addenda Record Indicator | "0" = no addenda
"1" = one addenda included. | M | +| *11* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +**POS Addenda Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Entry Detail Record is '7' | M | +| *2* | 02-03 | 2 | '02' | Addenda Type Code | The Addenda Type Code defines the specific interpretation and format for the addenda information contained in the Entry. | M | +| *3* | 04-10 | 7 | Alphameric| Reference Information #1 | This field may be used for additional reference numbers, identification numbers, or codes that the merchant needs to identify the particular transaction or customer. | O | +| *4* | 11-13 | 3 | Alphameric| Reference Information #2 | This field may be used for additional reference numbers, identification numbers, or codes that the merchant needs to identify the particular transaction or customer. | O | +| *5* | 14-19 | 6 | Alphameric| Terminal Identification Code | This field identifies an Electronic terminal with a unique code that allows a terminal owner and/or switching network to identify the terminal at which an Entry originated. | R | +| *6* | 20-25 | 6 | Alphameric| Transaction Serial Number | Entry amount in dollars with two decimal places. | R | +| *7* | 26-29 | 4 | MMDD | Transaction Date | This date, expressed MMDD, identifies the date on which the transaction occurred. | R | +| *8* | 30-35 | 6 | Alphameric| Authorization Code or Card Expiration Date | This field indicates the code that a card authorization center has furnished to the merchant. | O | +| *9* | 36-62 | 27 | Alphameric| Terminal Location | This field identifies the specific location of a terminal (i.e., street names of an intersection, address, etc.) in accordance with the requirements of Regulation E. | R | +| *10* | 63-77 | 15 | Alphameric| Terminal City | Identifies the city in which the electronic terminal is located. | R | +| *11* | 78-79 | 2 | Alphameric| Terminal State | Identifies the state in which the electronic terminal is located. | R | +| *12* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +## PPD Prearranged Payment and Deposit Entries + +**Prearranged Payment and Deposit Entries** is a recurring entry for direct deposit of payroll, pension, etc., or for direct payment of recurring bills such as utilities, loans, etc. + +**Prearranged Payment and Deposit Detail Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alphameric| DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. | M | +| *7* | 40-54 | 15 | Alphameric| Individual Identification Number | Receiver's identification number. This number may be printed on the receiver's bank statement by the Receiving Financial Institution. | M | +| *8* | 55-76 | 22 | Alphameric| Individual Name | Receiver's Name. | M | +| *9* | 77-78 | 2 | Alphameric| Discretionary Data | The use of this field is defined by the ODFI. | O | +| *10* | 79-79 | 1 | Numeric | Addenda Record Indicator | "0" = no addenda
"1" = one addenda included. | M | +| *11* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +## RCK Represented Check Entries + +**Represented Check** entry is a physical check that was presented but returned because of insufficient funds may be represented as an ACH entry. + +**RCK Represented Check Detail Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alphameric| DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. | M | +| *7* | 40-54 | 15 | Alphameric| Check Serial Number |The serial number of the check being represented. | M | +| *8* | 55-76 | 22 | Alphameric| Individual Name | Receiver's Name. | M | +| *9* | 77-78 | 2 | Alphameric| Discretionary Data | The use of this field is defined byu the ODFI. | O | +| *10* | 79-79 | 1 | Numeric | Addenda Record Indicator | "0" = no addenda
"1" = one addenda included. | M | +| *11* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +## SHR Shared Network Entry + +**Shared Network Entry** A SHR entry a debit Entry initiated at an “electronic terminal,” as that term is defined in Regulation E, to a Consumer Account of the Receiver to pay an obligation incurred in a point-of-sale transaction, or to effect a point-of-sale terminal cash withdrawal. Also an adjusting or other credit Entry related to such debit Entry, transfer of funds, or obligation. SHR Entries are initiated in a shared network where the ODFI and RDFI have an agreement in addition to these Rules to process such Entries. + +**SHR Entry Detail Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alphameric| DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. | M | +| *7* | 40-43 | 4 | MMDD | Card Expiration Date | This code is used by cardholder processors and cardholder Financial Institutions to verify that the card remains valid and that certain security procedures required by various card authorization systems have been met. | R | +| *8* | 44-54 | 11 | Numeric | Document Reference Number | This field further defines the transaction in the event of a Receiver’s inquiry. An example is an Electronic sequence number. | R | +| *9* | 55-76 | 22 | Numeric | Individual Card Account Number | The Individual Card Account Number is the number assigned by the card issuer and is obtained from the card itself. | R | +| *10* | 77-78 | 2 | Alphameric| Card Transaction Type | This code is used by card processors to identify the type of transaction, such as a purchase, cash advance, or reversal. Values for this field are assigned by the major card Organizations. Code Values:
01 Purchase of goods or services
02 Cash
03 Return Reversal
11 Purchase Reversal
12 Cash Reversal
13 Return
21 Adjustment
99 Miscellaneous Transaction. | M | +| *11* | 79-79 | 1 | Numeric | Addenda Record Indicator | "0" = no addenda
"1" = one addenda included. | M | +| *12* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +**SHR Addenda Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Entry Detail Record is '7' | M | +| *2* | 02-03 | 2 | '02' | Addenda Type Code | The Addenda Type Code defines the specific interpretation and format for the addenda information contained in the Entry. | M | +| *3* | 04-10 | 7 | Alphameric| Reference Information #1 | This field may be used for additional reference numbers, identification numbers, or codes that the merchant needs to identify the particular transaction or customer. | O | +| *4* | 11-13 | 3 | Alphameric| Reference Information #2 | This field may be used for additional reference numbers, identification numbers, or codes that the merchant needs to identify the particular transaction or customer. | O | +| *5* | 14-19 | 6 | Alphameric| Terminal Identification Code | This field identifies an Electronic terminal with a unique code that allows a terminal owner and/or switching network to identify the terminal at which an Entry originated. | R | +| *6* | 20-25 | 6 | Alphameric| Transaction Serial Number | Entry amount in dollars with two decimal places. | R | +| *7* | 26-29 | 4 | MMDD | Transaction Date | This date, expressed MMDD, identifies the date on which the transaction occurred. | R | +| *8* | 30-35 | 6 | MMDD | Authorization Code or Card Expiration Date | This field indicates the code that a card authorization center has furnished to the merchant. | O | +| *9* | 36-62 | 27 | Alphameric| Terminal Location | This field identifies the specific location of a terminal (i.e., street names of an intersection, address, etc.) in accordance with the requirements of Regulation E. | R | +| *10* | 63-77 | 2 | Alphameric| Terminal City | Identifies the city in which the electronic terminal is located. | R | +| *11* | 78-79 | 2 | Alphameric| Terminal State | Identifies the state in which the electronic terminal is located. | R | +| *12* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number | M | + +## TEL Telephone-Initiated Entry + +**Telephone-Initiated Entry Detail Record** is used for the origination of a Single Entry debit transaction to a consumer’s account pursuant to an oral authorization obtained from the consumer via the telephone. + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alphameric| DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. | M | +| *7* | 40-54 | 15 | Alphameric| Individual Identification Number | Receiver's identification number. This number may be printed on the receiver's bank statement by the Receiving Financial Institution. | M | +| *8* | 55-76 | 22 | Alphameric| Individual Name | Receiver's Name. | M | +| *9* | 77-78 | 2 | Alphameric| Payment Type Code | Input 'R' for Recurring payments, and 'S' for single-entry payment. | O | +| *10* | 79-79 | 1 | Numeric | Addenda Record Indicator | "0" = no addenda
"1" = one addenda included. | M | +| *11* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number | M | + +Note: Use [`SetPaymentType()`](https://pkg.go.dev/github.com/moov-io/ach#EntryDetail.SetPaymentType) and [`PaymentTypeField()`](https://pkg.go.dev/github.com/moov-io/ach#EntryDetail.PaymentTypeField) to update Payment Type Code. This code uses the `DiscretionaryData` field. + +## TRC/TRC Truncated Entries + +**Truncated Entries** This Standard Entry Class Code is used to identify a debit entry of a truncated check. + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alphameric| DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. | M | +| *7* | 40-54 | 9 | Alphameric| Check Serial Number |The serial number of the check being represented. | O | +| *8* | 55-60 | 6 | Alphameric| Process Control Field | Code which identifies the document type. | R | +| *9* | 55-60 | 16 | Alphameric| Item Research Number | The MICR location number for check item research. | R | +| *10* | 77-78 | 2 | Blank | Item Type Indicator | Indicates the type of items being truncated. Code: 01 - Value: NACS Truncated Items. | O | +| *11* | 79-79 | 1 | Numeric | Addenda Record Indicator | "0" = no addenda
"1" = one addenda included. | O | +| *12* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number | M | + +## TRX Truncated Entries + +**Check Truncation Entries Exchange** This Standard Entry Class Code is used to identify a debit entry of a truncated checks (multiple). + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6' | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. Valid codes are:
22 = Automated deposit (checking credit)
23 = Prenote of checking credit
24 = Zero-dollar checking credit with remittance data (CCD & CTX entries only)
27 = Automated payment (checking debit)
28 = Prenote of checking debit
29 = Zero-dollar checking debit with remittance data (CCD & CTX entries only)
32 = Automated deposit (savings credit)
33 = Prenote of savings credit
34 = Zero-dollar savings credit with remittance data (CCD & CTX entries only)
37 = Automated payment (savings debit)
38 = Prenote of savings debit
39 = Zero-dollar savings debit with remittance data (CCD & CTX entries only). | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alphameric| DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. Right-justified, left zero-filled, without a decimal point. Enter 10 zeros for non-dollar prenote entries. | M | +| *7* | 40-54 | 15 | Alphameric| Identification Number | This field contains the accounting number by which the Receiver is known to the Originator for descriptive purposes. NACHA Rules recommend but do not require the RDFI to print the contents of this field on the receiver‟s statement. | R | +| *8* | 55-58 | 4 | Numeric | Number of Addenda Records | Number of addenda records associated with this transaction. | M | +| *9* | 59-74 | 16 | Alphameric| Receiving Company Name/ID Number | Name of Receiver. | M | +| *10* | 75-76 | 2 | blank | reserved | Leave blank | n/a | +| *11* | 77-78 | 2 | blank | Item Type Indicator | Indicates the type of items being truncated. Code: 01 - Value: NACS Truncated Items. | O | +| *12* | 79-79 | 1 | Numeric | Addenda Record Indicator | '1' = addenda included. | O | +| *13* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +**TRX Addenda Record** + +Up to 9,999 Addenda Records may be included with a TRX Entry Detail Record. + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Entry Detail Record is '7' | M | +| *2* | 02-03 | 2 | '05' | Addenda Type Code | The Addenda Type Code defines the specific interpretation and format for the addenda information contained in the Entry. | M | +| *3* | 04-83 | 80 | Alphameric| Payment Related Information | This field contains payment related ANSI ASC X12 data segments to further identify the payment or Transmit additional remittance information. | O | +| *4* | 84-87 | 4 | Numeric | Addenda Sequence Number | This number is consecutively assigned to each Addenda Record following an Entry Detail Record. The first addenda sequence number must always be a “1”. | M | +| *5* | 88-94 | 7 | Numeric | Entry Detail Sequence Number | This field contains the ascending sequence number section of the Entry Detail or Corporate Entry Detail Record’s trace number. This number is the same as the last seven digits of the trace number of the related Entry Detail Record or Corporate Entry Detail Record. | M | + +### WEB Internet Authorized Entries + +**Internet Authorized Entries** Entry submitted pursuant to an authorization obtained via the the internet, mobile device, or wireless network. + +**WEB Detail Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alphameric| DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. | M | +| *7* | 40-54 | 15 | Alphameric| Individual Identification Number | Receiver's identification number. This number may be printed on the receiver's bank statement by the Receiving Financial Institution. | M | +| *8* | 55-76 | 22 | Alphameric| Individual Name | Receiver's Name. | M | +| *9* | 77-78 | 2 | Alphameric| Payment Type Code | Input 'R' for Recurring payments, and 'S' for single-entry payment. | R | +| *10* | 79-79 | 1 | Numeric | Addenda Record Indicator | "0" = no addenda
"1" = one addenda included. | M | +| *11* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +Note: Use [`SetPaymentType()`](https://pkg.go.dev/github.com/moov-io/ach#EntryDetail.SetPaymentType) and [`PaymentTypeField()`](https://pkg.go.dev/github.com/moov-io/ach#EntryDetail.PaymentTypeField) to update Payment Type Code. This code uses the `DiscretionaryData` field. + +## XCK Destroyed Check Entry + +**Destroyed Check Entry** This application can be utilized by a collecting institution for the collection of certain checks when those checks have been destroyed. + +**XCK Entry Detail Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-29 | 17 | Alphameric| DFI Account Number | Receiver's account number at the RDFI, a value found on the MICR line of a check. | R | +| *6* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. | M | +| *7* | 40-54 | 15 | Alphameric| Check Serial Number |The serial number of the check being represented. | M | +| *8* | 55-60 | 6 | Alphameric| Process Control Field | This field contains an optional code, as obtained from a check or share draft, which generally identifies the document type. The field is usually located to the right of the account number in the on-us field of the MICR line and is sometimes called a transaction code. | R | +| *9* | 61-76 | 16 | Alphameric| Item Research Number | This field contains the MICR locator number for Check item research. | R | +| *10* | 77-78 | 2 | Alphameric| Discretionary Data | The use of this field is defined by the ODFI. | 0 | +| *11* | 79-79 | 1 | Numeric | Addenda Record Indicator | "0" = no addenda
"1" = one addenda included. | M | +| *12* | 80-94 | 15 | Numeric | Trace Number | Standard Entry Detail Trace Number. | M | + +## IAT International ACH Transaction + +**International ACH Transaction** entry is a credit or debit ACH entry that is part of a payment transaction involving a financial agency’s office (i.e., depository financial institution or business issuing money orders) that is not located in the territorial jurisdiction of the United States. IAT entries can be made to or from a corporate or consumer account and must be accompanied by seven (7) mandatory addenda records identifying the name and physical address of the Originator, name and physical address of the Receiver, Receiver’s account number, Receiver’s bank identity and reason for the payment. + +**IAT Batch Header Record** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '5' | Record Type Code | The code identifying the Company/Batch Header Record. | M | +| *2* | 02-04 | 3 | '200'
'220'
'225' | Service Class Code | Identifies the type of entries in the batch.
“200” = mixed debits and credits
“220” = credits only
“225” = debits only. | M | +| *3* | 05-20 | 16 | blank | IAT Indicator | Leave blank | O | +| *4* | 21-22 | 2 | 'FF' | Foreign Exchange Indicator | Fixed-to-Fixed - No currency conversion. Entry is originated in a fixed-value amount and is to be received in the same fixed-value amount in the same currency. Fixed-value entries will have spaces in the Foreign Exchange Reference field. | M | +| *5* | 23-23 | 1 | '3' | Foreign Exchange Reference Indicator | Code used to indicate the content of the Foreign Exchange Reference Field.
“3” = Space filled. | R | +| *6* | 24-38 | 15 | blank | Foreign Exchange Reference | Space filled | R | +| *7* | 39-40 | 2 | Alphameric | ISO Destination Country Code | This field contains the two-character code, as approved by the International Organization for Standardization (ISO), to identify the country in which the entry is to be received. Values can be found on the International Organization for Standardization website: www.iso.org. | M | +| *8* | 41-50 | 10 | Alphameric | Originator Identification | For U.S. entities: the number assigned will be your tax ID
For non-U.S. entities: the number assigned will be your DDA number, or the last 9 characters of your account number if it exceeds 9 characters. | M | +| *9* | 51-53 | 3 | 'IAT' | Standard Entry Class Code | A mnemonic, designated by NACHA, for International ACH Transactions | M | +| *10* | 54-63 | 10 | Alphameric | Company Entry Description | You establish the value of this field to provide a description to be displayed to the Receiver. Should describe the purpose of the entry, such as “PAYROLL” or “TRADE PAY. | M | +| *11* | 64-66 | 3 | Alphameric | ISO Originating Currency Code (Account Currency) | This field contains the three-character code, as approved by the International Organization for Standardization (ISO), to identify the currency denomination in which the entry was first originated. If the source of funds is within the territorial jurisdiction of the U.S., enter 'USD', otherwise refer to International Organization for Standardization website for value: www.iso.org. | M | +| *12* | 67-69 | 3 | Alphameric | ISO Destination Currency Code (Payment Currency) | This field contains the three-character code, as approved by the International Organization for Standardization (ISO), to identify the currency denomination in which the entry will ultimately be settled. If the final destination of funds is within the territorial jurisdiction of the U.S., enter “USD”, otherwise refer to International Organization for Standardization website for value: www.iso.org. | M | +| *13* | 70-75 | 6 | YYMMDD | Effective Entry Date | Date you desire funds to post to receiver‟s account. | M | +| *14* | 76-78 | 3 | blanks | Settlement Date (Julian) | The ACH Operator will populate the actual settlement date in this field. | M | +| *15* | 79-79 | 1 | '1' | Originator Status Code | Identifies the Originator as a non-Federal Government entity. | M | +| *16* | 80-87 | 8 | TTTTAAAA | Gateway Operator Identification/ ODFI Identification | For Inbound IAT Entries, this field contains the routing number of the U.S. Gateway Operator. For Outbound IAT Entries, this field contains the standard routing number, as assigned by Acuity, that identifies the U.S. ODFI initiating the Entry. | M | +| *17* | 88-94 | 7 | Numeric | Batch Number | Assign batch number in ascending order in each batch. | M | + +**NOTE**: For IAT Return Entries, each field of the Company Batch Header Record remains unchanged from the original record, except: + +1) Foreign Exchange Reference - For the return of an outbound International ACH transaction originated by a U.S. ODFI, this field will contain the foreign exchange rate that is applicable at the time of the return entry if a foreign exchange rate is provided within this field on the forward entry. + +2) Originator Status Code - Changed to reflect the Originator Status Code of the institution initiating the Return Entry (i.e., the RDFI of the original entry). + +3) Gateway Operator Identification/ ODFI Identification - Changed to reflect the Routing number of the institution initiating the Return Entry (i.e., the RDFI of the original entry). + +4) Batch Number - Changed to the batch number assigned by the institution preparing the Automated Return Entry. + +**IAT Entry Detail** + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '6' | Record Type Code | Code Identifying the Entry Detail Record is '6'. | M | +| *2* | 02-03 | 2 | Numeric | Transaction Code | Two-digit code that identifies checking account credits/debits. | M | +| *3* | 04-11 | 8 | TTTTAAAA | Receiving DFI Identification | Routing Transit number of the receivers financial institution. | M | +| *4* | 12-12 | 1 | Numeric | Check Digit | The ninth character of the RDFI Routing Transit Number. Used to check for transpositions. | M | +| *5* | 13-16 | 4 | Numeric | Number of Addenda Records | Number of Addenda Records. | M | +| *6* | 17-29 | 13 | blank | Reserved | Leave blank | M | +| *7* | 30-39 | 10 | $$$$$$$$¢¢ | Amount | Entry amount in dollars with two decimal places. | M | +| *8* | 40-74 | 35 | Alphameric | Foreign Receiver's Account Number/DFI Account Number | Receiver‟s account number at the RDFI domiciled in the territorial jurisdiction of the U.S. A value generally found on the MICR line of a check. Enter the MICR Dash Cue Symbol as a hyphen (“-”). Account numbers vary in format. If the account number has less than 35 characters, left-justify, blank-fill. Ignore any blank spaces within the account number. | M | +| *9* | 75-76 | 2 | blank | Reserved | Leave Blank | n/a | +| *10* | 77-77 | 1 | blank | Gateway Operator OFAC screening indicator | Assigned by the ACH Gateway operator. | O | +| *11* | 78-78 | 1 | blank | Secondary OFAC screening indicator | Assigned by the ACH Gateway operator. | O | +| *12* | 79-79 | 1 | "1" | Addenda Record indicator | "1" = Addenda records follow this entry. | M | +| *13* | 80-94 | 15 | Numeric | Trace Number | The field is constructed as follows:
Positions 80-87 should be the same as Field 16 of the IAT Company/Batch Header. Positions 88- 94 are filled with the Entry Detail Sequence Number. This number must be assigned in ascending order to entries within each batch, although the numbers need not be continuous. | M | + +**NOTE**: For IAT Return Entries, each field of the Entry Detail Record remains unchanged from the original entry, except: +1) Transaction Code -Changed to the appropriate Return Entry Transaction Code. +2) Receiving DFI Identification - Changed to the routing number of the institution receiving the return entry (i.e., OFI of original entry). +3) Check Digit - Changed to the check digit according to NACH standards and based on the Routing Number contained in position 04-11. +4) Amount - For the return of an outbound International ACH Transaction originated by a U.S ODFI, this amount will be different from the amount reflected in the original forward entry if the exchange rate is different at the time of the return. +5) Trace Number - Changed to the trace number assigned by the institution preparing the Automated Return Entry. + +**IAT Addenda Records** + +For IAT entries, the first seven Addenda Records are mandatory and contain additional information about the receiver, the ODFI and the RDFI as required by OFAC. The type '717' remittance addenda is optional and is used by the Originator to supply additional information about Entry Detail Record to the Receiver; a maximum of two type '717' addenda are allowed. The type '718' Foreign Correspondent Bank addenda must be included if a Foreign Correspondent Bank is involved in the processing of the IAT entry; a maximum of five type '718' addenda are allowed. +Each Addenda Record must be included in the item count that is entered into the Company/Batch Control Record. + +**IAT First Addenda Record (710)** + +The First Addenda Record identifies the Receiver of the transaction and the dollar amount of the payment. + + Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Addenda Record. | M | +| *2* | 02-03 | 2 | '10' | Addenda Type Code | First Addenda Record for IAT. | M | +| *3* | 04-06 | 3 | Alphameric | Transaction Type Code | Describes the type of payment:
ANN = Annuity
BUS = Business/Commercial
DEP = Deposit
LOA = Loan
MIS = Miscellaneous
MOR = Mortgage
PEN = Pension
RLS = Rent/Lease
REM = Remittance2
SAL = Salary/Payroll
TAX = Tax
TEL = Telephone-Initiated Transaction
WEB = Internet-Initiated Transaction
ARC = Accounts Receivable Entry
BOC = Back Office Conversion Entry
POP = Point of Purchase Entry
RCK = Re-presented Check Entry. | R | +| *4* | 07-24 | 18 | $$$$$$$$$$$$$$$$¢¢ | Foreign Payment Amount | For inbound IAT payments this field should contain the USD amount or may be blank. | R | +| *5* | 25-46 | 22 | Alphameric | Foreign Trace Number | Insert blanks or zeros. | O | +| *6* | 47-81 | 35 | Alphameric | Receiving Company Name/Individual Name | Receiver's name. | M | +| *7* | 82-87 | 6 | blank | Reserved | Leave blank | n/a | +| *8* | 88-94 | 7 | Alphameric | Entry Detail Sequence Number | This field contains the ascending sequence number section of the Entry Detail Record‟s trace number. This number is the same as the last seven digits of the trace number (Field 13) of the related Entry Detail Record. | M | + +**NOTE**: For IAT Return Entries, each field of the 1st Addenda Record remains unchanged from the original 1st Addenda Record, except Entry Detail Sequence Number which reflects the Entry Detail Sequence Number assocoiated with the trace number assigned by the institution preparing the Automated Return Entry. + +**IAT Second Addenda Record (711)** + +The Second and Third Addenda Records identify key information related to the Originator of the entry. + + Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Addenda Record. | M | +| *2* | 02-03 | 2 | '11' | Addenda Type Code | Second Addenda Record for IAT. | M | +| *3* | 04-38 | 35 | Alphameric | Originator Name | Contains the originators name (your company name). | M | +| *4* | 39-73 | 35 | Alphameric | Originator Street Address | Contains the originators street address (your company's address). | m | +| *5* | 74-87 | 14 | blank | Reserved | Leave blank | n/a | +| *6* | 88-94 | 7 | Alphameric | Entry Detail Sequence Number | This field contains the ascending sequence number section of the Entry Detail Record‟s trace number. This number is the same as the last seven digits of the trace number (Field 13) of the related Entry Detail Record. | M | + +**NOTE**: For IAT Return Entries, each field of the 2nd Addenda Record remains unchanged from the original 2nd Addenda Record, except Entry Detail Sequence Number which reflects the Entry Detail Sequence Number associated with the trace number assigned by the institution preparing the Automated Return Entry. + +**IAT Third Addenda Record (712)** + + Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Addenda Record. | M | +| *2* | 02-03 | 2 | '12' | Addenda Type Code | Third Addenda Record for IAT. | M | +| *3* | 04-38 | 35 | Alphameric | Originator City & State / Province | City and State should be separated with an asterisk (*) as a delimiter and the field should end with a backslash (\).
For example: San Francisco*CA\. | M | +| *4* | 39-73 | 35 | Alphameric | Originator Country & Postal Code | Data elements must be separated by an asterisk (*) and must end with a backslash (\)
For example: US*10036\. | M | +| *5* | 74-87 | 14 | blank | Reserved | Leave blank | n/a | +| *6* | 88-94 | 7 | Alphameric | Entry Detail Sequence Number | This field contains the ascending sequence number section of the Entry Detail Record‟s trace number. This number is the same as the last seven digits of the trace number (Field 13) of the related Entry Detail Record. | M | + +**NOTE**: For IAT Return Entries, each field of the 3rd Addenda Record remains unchanged from the original 3rd Addenda Record, except Entry Detail Sequence Number which reflects the Entry Detail Sequence Number associated with the trace number assigned by the institituion preparing the Automated Return Entry. + +**IAT Fourth Addenda Record (713)** + +The Fourth Addenda Record contains information related to the financial institution originating the entry. For inbound IAT entries, the Fourth Addenda Record must contain information to identify the foreign financial institution that is providing the funding and payment instruction for the IAT entry. + + Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Addenda Record. | M | +| *2* | 02-03 | 2 | '13' | Addenda Type Code | Fourth Addenda Record for IAT. | M | +| *3* | 04-38 | 35 | Alphameric | Originating DFI Name | For Outbound IAT Entries, this field must contain the name of the U.S. ODFI.
For Inbound IATs: Name of the foreign bank providing funding for the payment transaction. | M | +| *4* | 39-40 | 2 | "01" | Originating DFI Identification Number Qualifier | “01” = National Clearing System
*For Inbound IATs:* The 2-digit code that identifies the numbering scheme used in the Foreign DFI Identification Number field:
01 = National Clearing System
02 = BIC Code
03 = IBAN Code.| M | +| *5* | 41-74 | 34 | Alphameric | Originating DFI Identification | This field contains the routing number that identifies the U.S. ODFI initiating the entry.
*For Inbound IATs:* This field contains the bank ID number of the Foreign Bank providing funding for the payment transaction. | M | +| *6* | 75-77 | 3 | Alphameric | Originating DFI Branch Country Code | USb” = United States
(“b” indicates a blank space)
*For Inbound IATs:* This 3 position field contains a 2-character code as approved by the International Organization for Standardization (ISO) used to identify the country in which the branch of the bank that originated the entry is located. Values for other countries can be found on the International Organization for Standardization website: www.iso.org. | M | +| *7* | 78-87 | 10 | blank | Reserved | Leave blank | n/a | +| *8* | 88-94 | 7 | Numeric | Entry Detail Sequence Number | This field contains the ascending sequence number section of the Entry Detail Record‟s trace number. This number is the same as the last seven digits of the trace number (Field 13) of the related Entry Detail Record. | M | + +**NOTE**: For IAT Return Entries, each field of the 4th Addenda Record remains unchanged from the original 4th Addenda Record, except Entry Detail Sequence Number whihch reflects the Entry Detail Sequence Number associated with the trace number assigned by the institution preparing the Automated Return Entry. + +**IAT Fifth Addenda Record (714)** + +The Fifth Addenda Record identifies the Receiving financial institution holding the Receiver's account. + + Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Addenda Record. | M | +| *2* | 02-03 | 2 | '14' | Addenda Type Code | Fifth Addenda Record for IAT. | M | +| *3* | 04-38 | 35 | Alphameric | Receiving DFI Name | Name of the Receiver's bank. | M | +| *4* | 39-40 | 2 | Numeric | Receiving DFI Identification Number Qualifier | The 2-digit code that identifies the numbering scheme used in the Receiving DFI Identification Number field:
01 = National Clearing System
02 = BIC Code
03 = IBAN Code. | M | +| *5* | 41-74 | 34 | Alphameric | Receiving DFI Identification Number | The bank identification number of the DFI at which the Receiver maintains his account. | M | +| *6* | 75-77 | 3 | Alphameric | Receiving DFI Branch Country Code | This 3 position field contains a 2-character code as approved by the International Organization for Standardization (ISO) used to identify the country in which the branch of the bank that receives the entry is located. Values for other countries can be found on the International Organization for Standardization website: www.iso.org
“USb” = United States
(“b” indicates a blank space). | M | +| *7* | 78-87 | 10 | blank | Reserved | Leave blank | n/a | +| *8* | 88-94 | 7 | Numeric | Entry Detail Sequence Number | This field contains the ascending sequence number section of the Entry Detail Record‟s trace number. This number is the same as the last seven digits of the trace number (Field 13) of the related Entry Detail Record. | M | + +**NOTE**: For IAT Return Entries, each field of the 5th Addenda Record remains unchanged from the original 5th Addenda Record, except Entry Detail Sequence Number whihch reflects the Entry Detail Sequence Number associated with the trace number assigned by the institituion preparing the Automated Return Entry. + +**IAT Sixth Addenda Record (715)** + +The Sixth and Seventh Addenda Records identify information related to the Receiver. + +Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Addenda Record. | M | +| *2* | 02-03 | 2 | '15' | Addenda Type Code | Sixth Addenda Record for IAT. | M | +| *3* | 04-18 | 15 | Alphameric | Receiver Identification Number | This field contains the accounting number by which the Originator is known to the Receiver for descriptive purposes. NACHA Rules recommend but do not require the RDFI to print the contents of this field on the receiver's statement. | O | +| *4* | 19-53 | 35 | Alphameric | Receiver Street Address | Receiver‟s physical address. | M | +| *5* | 54-87 | 34 | blank | Reserved | Leave blank | n/a | +| *6* | 88-94 | 7 | Numeric | Entry Detail Sequence Number | This field contains the ascending sequence number section of the Entry Detail Record‟s trace number. This number is the same as the last seven digits of the trace number (Field 13) of the related Entry Detail Record. | M | + +**NOTE**: For IAT Return Entries, each field of the 6th Addenda Record remains unchanged from the original 6th Addenda Record, except Entry Detail Sequence Number whihch reflects the Entry Detail Sequence Number associated with the trace number assigned by the institution preparing the Automated Return Entry. + +**IAT Seventh Addenda Record (716)** + +Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Addenda Record. | M | +| *2* | 02-03 | 2 | '16' | Addenda Type Code | Seventh Addenda Record for IAT. | M | +| *3* | 04-38 | 35 | Alphameric | Receiver City, State/Province | City and State should be separated with an asterisk (*) as a delimiter and the field should end with a backslash (\).
For example: San Francisco*CA\. | M | +| *4* | 39-73 | 35 | Alphameric | Receiver Country & Postal Code| Data elements must be separated by an asterisk (*) and must end with a backslash (\)
For example: US*10036\. | M | +| *5* | 74-87 | 34 | blank | Reserved | Leave blank | n/a | +| *6* | 88-94 | 7 | Numeric | Entry Detail Sequence Number | This field contains the ascending sequence number section of the Entry Detail Record‟s trace number. This number is the same as the last seven digits of the trace number (Field 13) of the related Entry Detail Record. | M | + +**NOTE**: For IAT Return Entries, each field of the 7th Addenda Record remains unchanged from the original 7th Addenda Record, except Entry Detail Sequence Number whihch reflects the Entry Detail Sequence Number associated with the trace number assigned by the institution preparing the Automated Return Entry. + +**IAT Addenda Record for Remittance Information (717) (Optional)** + +This is an optional Addenda Record used to provide payment-related data. You may include up to two of these Addenda Records with each IAT entry. + +Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Addenda Record. | M | +| *2* | 02-03 | 2 | '17' | Addenda Type Code | Seventh Addenda Record for IAT. | M | +| *3* | 04-83 | 80 | Alphameric | Payment Related Information | Payment information associated with the preceding Entry Detail Record. Must contain NACHA endorsed ANSI ASC X12 data segments or NACHA endorsed banking conventions. The asterisk (“*”) must be the delimiter between the data elements, and the back slash (“\”) must be the terminator between the data segments. | O | +| *4* | 84-87 | 4 | Numeric | Addenda Sequence Number | Sequence number of each type “17” remittance addenda in ascending order beginning with 0001. | M | +| *5* | 88-94 | 7 | Numeric | Entry Detail Sequence Number | This field contains the ascending sequence number section of the Entry Detail Record‟s trace number. This number is the same as the last seven digits of the trace number (Field 13) of the related Entry Detail Record. | M | + +**IAT Addenda Record for Foreign Correspondent Bank Information (718)** + +This Addenda Record is used to provide information on each Foreign Correspondent Bank involved in the processing of the IAT entry. If no Foreign Correspondent Bank is involved,the record should not be included.
+NOTE: A maximum of five 'Type 18' addenda records may be included with each IAT entry. + +Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Addenda Record. | M | +| *2* | 02-03 | 2 | '18' | Addenda Type Code | Addenda Record for Foreign Correspondent Bank Information for IAT. | M | +| *3* | 04-38 | 35 | Alphameric | Foreign Correspondent Bank Name | This field contains the name of the Foreign Correspondent Bank. | M | +| *4* | 39-40 | 2 | Alphameric | Foreign Correspondent Bank Identification Number Qualifier | This field contains a 2-digit code that identifies the numbering scheme used in the Foreign Correspondent Bank Identification Number field. Code values for this field are:
“01” = National Clearing System
“02” = BIC Code
“03” =IBAN Code | M | +| *5* | 41-74 | 34 | Alphameric | Foreign Correspondent Bank Identification Number | This field contains the bank ID number of the Foreign Correspondent Bank. | M | +| *6* | 75-77 | 3 | Alphameric | Foreign Correspondent Bank Branch Country Code | This field contains the two-character code, as approved by the International Organization for Standardization (ISO), to identify the country in which the branch of the Foreign Correspondent Bank is located. Values can be found on the International Organization for Standardization website: www.iso.org. | M | +| *7* | 78-83 | 6 | blank | Reserved | Leave blank | n/a | +| *8* | 84-87 | 4 | Numeric | Addenda Sequence Number | Sequence number of each „Type 18” Foreign Correspondent Bank Identification addenda in ascending order beginning with “0001”. | M | +| *9* | 88-94 | 7 | Numeric | Entry Detail Sequence Number | This field contains the ascending sequence number section of the Entry Detail Record‟s trace number. This number is the same as the last seven digits of the trace number (Field 13) of the related Entry Detail Record. | M | + +**IAT Addenda Record for IAT Returns (799)** + +This Addenda Record is used to provide return data for a returned IAT entry + +Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '7' | Record Type Code | Code Identifying the Addenda Record. | M | +| *2* | 02-03 | 2 | '99' | Addenda Type Code | Addenda Record IAT Returns. | M | +| *3* | 04-06 | 3 | Alphameric | Return Reason Code | This field contains code associated with the reason for the return. | M | +| *4* | 07-21 | 15 | Numeric | Original Trace Number | This field contains the Trace Number as originally included on the forward Entry. | M | +| *5* | 22-27 | 6 | YYMMDD | Date of Death | This field is used for Return Reason Code R14 or R15. | O | +| *6* | 28-35 | 8 | Alphameric | Original DFI | This field contains the Receiving DFI Identification as originally included on the forward Entry that the RDFI is returning or correcting. | R | +| *7* | 36-45 | 10 | Numeric | Original Payment Amount | Original Payment of the forward Entry. | R | +| *8* | 46-79 | 34 | Alphameric| Addenda Information | Addenda Information. | O | +| *9* | 80-94 | 15 | Numeric | Trace Number | The Trace Number of the entry being returned. | M | + + +## File Control + +The File Control record contains dollar, entry, and hash totals from the file's Company/Batch Control Records. This record also contains counts of the blocks and batches in the file. + +| Field | Position | Size | Contents | Field Name | Entry Information | M,R,O | +| :---: | :---: | :---: | :--- | :--- | :--- | :---: | +| *1* | 01-01 | 1 | '9' | Record Type Code | Code Identifying the File Control Record is '9'. | M | +| *2* | 02-07 | | Numeric | Batch Count | Total number of Company/Batch Header Records (Record Type “5”) in the file. | M | +| *3* | 08-13 | 6 | Numeric | Block Count | Total number of physical blocks in the file, including the File Header and File Control Records. | M | +| *4* | 14-21 | 8 | Numeric | Entry / Addenda Count | Total number of Entry Detail and Addenda Records (Record Types “6” and “7”) in the file. | M | +| *5* | 22-31 | 10 | Numeric | Entry Hash | Total of eight character Transit Routing/ABA numbers in the file (Field 3 of the Entry Detail Record). Do not include the Transit Routing Check Digit. Enter the 10 low-order (right most) digits of this number. For example,if this sum is 998877665544, enter 8877665544. | M | +| *6* | 32-43 | 12 | $$$$$$$$$$¢¢ | Total Debit Entry Dollar Amount in File | Dollar total of debit entries in the file. If none, zero-fill the field. Do not enter a decimal point. Right-justify, left zero-fill. | M | +| *7* | 44-55 | 12 | $$$$$$$$$$¢¢ | Total Credit Entry Dollar Amount in File | Dollar total of credit entries in the file. If none, zero-fill the field. Do not enter a decimal point. Right-justify, left zero-fill. | M | +| *8* | 56-94 | 39 | blank | Reserved | Leave this field blank. | n/a | diff --git a/docs/flatten-batches.md b/docs/flatten-batches.md new file mode 100644 index 000000000..95af64562 --- /dev/null +++ b/docs/flatten-batches.md @@ -0,0 +1,51 @@ +--- +layout: page +title: Flatten batches +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Flatten batches + +File creation honors the order and number of Batch's that are created by the client. This is not optimized for the smallest file size and cost, that could be sent to the Federal Reserve for processing. + +Flattening batches is a post file creation process that takes an input of an ACH.File and returns an ACH.File which has been optimized by flattening (merging) Batch Headers that have the same information and appending all Entries into a single Batch. + +Note: FlattenBatches (via `FlattenBatches()`) is only in ACH v1.2.0 and later. + +## Creating flattened batches + +An ACH [File](https://godoc.org/github.com/moov-io/ach#File) supports calling [FlattenBatches](https://godoc.org/github.com/moov-io/ach#File.FlattenBatches): + +```go +// Open a file for reading. Any io.Reader can be used. +f, err := os.Open(filepath.Join("test", "testdata", "flattenBatchesOneBatchHeader.ach")) + +if err != nil { + t.Fatal(err) +} +r := NewReader(f) +achFile, err := r.Read() +if err != nil { + t.Fatalf("Issue reading file: %+v \n", err) +} + +of, err := achFile.FlattenBatches() + +if err != nil { + t.Fatalf("Could not flatten the file: %+v \n", err) +} + +if err := of.Validate(); err != nil { + t.Fatalf("Flattened file did not validate: %+v \n", err) +} + +// Write the file to stdout. Any io.Writer can be used. +w := ach.NewWriter(os.Stdout) +if err := w.Write(of); err != nil { + log.Fatalf("Unexpected error: %s\n", err) +} + +w.Flush() +``` diff --git a/docs/images/gcp-run-button/1-image-trust-settings.png b/docs/images/gcp-run-button/1-image-trust-settings.png new file mode 100644 index 000000000..775a84f84 Binary files /dev/null and b/docs/images/gcp-run-button/1-image-trust-settings.png differ diff --git a/docs/images/gcp-run-button/2-confirm-prompt.png b/docs/images/gcp-run-button/2-confirm-prompt.png new file mode 100644 index 000000000..5566dea3b Binary files /dev/null and b/docs/images/gcp-run-button/2-confirm-prompt.png differ diff --git a/docs/images/gcp-run-button/3-web-preview.png b/docs/images/gcp-run-button/3-web-preview.png new file mode 100644 index 000000000..22516163e Binary files /dev/null and b/docs/images/gcp-run-button/3-web-preview.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..a3476a7a1 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,17 @@ +--- +layout: page +title: Overview +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Overview + +![Moov ACH Logo](https://repository-images.githubusercontent.com/76497520/263dab00-c6d9-11ea-8bf0-8070d91f9135) + +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. + +ACH implements a reader, writer, and validator for Automated Clearing House ([ACH](https://en.wikipedia.org/wiki/Automated_Clearing_House)) files. ACH is the primary method of electronic money movement throughout the United States. The HTTP server is available in a [Docker image](/usage-docker.md) and the Go package `github.com/moov-io/ach` is available. + +If you're looking for an event driven ACH engine for uploading/downloading files and operations we have built [moov-io/achgateway](https://github.com/moov-io/achgateway) and run it in production. Our article [How and When to use the Moov ACH Library](https://moov.io/blog/education/how-and-when-to-use-the-moov-ach-library/) will help to generate ACH files for upload to your ODFI. diff --git a/docs/intro.md b/docs/intro.md new file mode 100644 index 000000000..22192e293 --- /dev/null +++ b/docs/intro.md @@ -0,0 +1,38 @@ +--- +layout: page +title: What is ACH? +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# What is ACH? +Automated Clearing House (ACH) is an electronic network for financial transactions in the United States. ACH processes large volumes of credit and debit transactions in batches. ACH credit transfers include direct deposit, payroll and vendor payments. ACH direct debit transfers include consumer payments on insurance premiums, mortgage loans, and other kinds of bills. Debit transfers also include new applications such as the point-of-purchase (POP) check conversion pilot program sponsored by the National Automated Clearing House Association (NACHA). Both the government and the commercial sectors use ACH payments. Businesses increasingly use ACH online to have customers pay, rather than via credit or debit cards. + +[Source: Wikipedia - Automated Clearing House](https://en.wikipedia.org/wiki/Automated_Clearing_House) + +## How does ACH work? +NACHA includes Members in the process of establishing Rules for the ACH Network, working collaboratively to create a clear picture of participant roles and responsibilities in the following ACH transaction process. + + +1. An Originator– whether that’s an individual, a corporation or another entity– initiates either a Direct Deposit or Direct Payment transaction using the ACH Network. ACH transactions can be either debit or credit payments and commonly include Direct Deposit of payroll, government and Social Security benefits, mortgage and bill payments, online banking payments, person-to-person (P2P) and business-to-business (B2B) payments, to name a few. +2. Instead of using paper checks, ACH entries are entered and transmitted electronically, making transactions quicker, safer and easier. +3. The Originating Depository Financial institution (ODFI) enters the ACH entry at the request of the Originator. +4. The ODFI aggregates payments from customers and transmits them in batches at regular, predetermined intervals to an ACH Operator. +5. ACH Operators (two central clearing facilities: The Federal Reserve or The Clearing House) receive batches of ACH entries from the ODFI. +6. The ACH transactions are sorted and made available by the ACH Operator to the Receiving Depository Financial Institution (RDFI). +7. The Receiver’s account is debited or credited by the RDFI, according to the type of ACH entry. Individuals, businesses and other entities can all be Receivers. +8. Each ACH credit transaction settles in one to two business days, and each debit transaction settles in just one business day, as per the Rules. + +[Source: ACH Network: How it Works](https://www.nacha.org/ach-network) + +## Developer overview + +Nacha has published an [ACH Guide for Developers](https://dev-ach-guide.pantheonsite.io/) that covers the basics of ACH payment flow, entry types, and file structure. + +Additionally, Gusto has provided a great technical overview of how ACH works for developers: + +- [How ACH works: A developer perspective - Part 1](http://engineering.gusto.com/how-ach-works-a-developer-perspective-part-1/) +- [How ACH works: A developer perspective - Part 2](http://engineering.gusto.com/how-ach-works-a-developer-perspective-part-2/) +- [How ACH works: A developer perspective - Part 3](http://engineering.gusto.com/how-ach-works-a-developer-perspective-part-3/) +- [How ACH works: A developer perspective - Part 4](http://engineering.gusto.com/how-ach-works-a-developer-perspective-part-4/) diff --git a/docs/merging-files.md b/docs/merging-files.md new file mode 100644 index 000000000..c35203dd9 --- /dev/null +++ b/docs/merging-files.md @@ -0,0 +1,65 @@ +--- +layout: page +title: Merging files +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Merging files + +Moov ACH supports merging an arbitrary set of ACH files together. This is useful for optimizing cost and network efficiency. NACHA files are limited to 10,000 lines (in their text format) and so Moov ACH merges valid files together where the FileHeaders match the same ABA routing numbers. + +An example of merging ACH files can be seen below. Assuming we have two ACH files to merge (`first.ach` and `second.ach`) on disk, let's read them and produce a merged file. + + +```go +package main + +import ( + "log" + "os" + "github.com/moov-io/ach" +) + +func main() { + // first.ach and second.ach need to have the same Origin and Destination ABA routing numbers + // in order to be merged into one ACH file, otherwise two ACH files would be returned. + + first, err := readACHFilepath("first.ach") + if err != nil { + log.Fatalf("first.ach parsing error: %v", err) + } + second, err := readACHFilepath("second.ach") + if err != nil { + log.Fatalf("second.ach parsing error: %v", err) + } + + mergedFiles, err := ach.MergeFiles([]*ach.File{first, second}) + if err != nil { + log.Fatalf("problem merging ACH files: %v", err) + } + log.Printf("merged into %d ACH files", len(mergedFiles)) +} + +func readACHFilepath(path string) (*ach.File, error) { + fd, err := os.Open(path) + if err != nil { + return nil, err + } + defer fd.Close() + + file, err := ach.NewReader(fd).Read() + if err != nil { + return nil, err + } + return &file, nil +} +``` + +Running the following code would produce (saved as `merge.go`): + +``` +$ go run merge.go +2019/05/23 13:07:37 merged into 1 ACH files +``` diff --git a/docs/metrics.md b/docs/metrics.md new file mode 100644 index 000000000..16464dc23 --- /dev/null +++ b/docs/metrics.md @@ -0,0 +1,30 @@ +--- +layout: page +title: Prometheus metrics +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Metrics + +The Moov ACH HTTP server reports Prometheus metrics when running. These are available on the admin server at `:9090/metrics` in the Prometheus metric format. + +## Files + +There are two metrics for ACH files: + +- `ach_files_created{destination="...", origin="..."}` +- `ach_files_deleted` + +Example: + +``` +$ curl localhost:9090/metrics +# HELP ach_files_created The number of ACH files created +# TYPE ach_files_created counter +ach_files_created{destination="231380104",origin="121042882"} 5 +# HELP ach_files_deleted The number of ACH files deleted +# TYPE ach_files_deleted counter +ach_files_deleted 1 +``` diff --git a/docs/nacha-format-excel-sample.xls b/docs/nacha-format-excel-sample.xls new file mode 100644 index 000000000..0a4269ccf Binary files /dev/null and b/docs/nacha-format-excel-sample.xls differ diff --git a/documentation/nacha_file_layout_guide.pdf b/docs/nacha_file_layout_guide.pdf similarity index 100% rename from documentation/nacha_file_layout_guide.pdf rename to docs/nacha_file_layout_guide.pdf diff --git a/docs/returns.md b/docs/returns.md new file mode 100644 index 000000000..081207136 --- /dev/null +++ b/docs/returns.md @@ -0,0 +1,132 @@ +--- +layout: page +title: Return files +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Return files + +ACH files and their EntryDetail records may be returned for a variety of reasons defined by "return codes". Either the originating (ODFI) or receiving (RDFI) financial institution can issue a return. Some of the most common reasons include: insufficient funds (`R01`) and account information not found (`R03`). + +Returns are identified by an [Addenda99](https://pkg.go.dev/github.com/moov-io/ach?tab=doc#Addenda99) record on the EntryDetail with a [ReturnCode](https://pkg.go.dev/github.com/moov-io/ach?tab=doc#ReturnCode) that can be processed. + +### Processing + +An Originator must have a process set up with their ODFI to handle returns. This may involve manual verification of actions to take, retrying entries (only in the case of an `R01` or `R09`), or revocation of future transfers for the customer (`R07` - Authorization revoked). + +Fees may be applied by the ODFI/RDFI as a result of returned files. Written authorization may be requested as a result of returned files as well. Such authorization requests may be prompted if the description, amount, or effective date is different than what the customer had authorized. + +The `R05` return code is used when an unauthorized corporate debit (standard entry class code of `CCD` or `CTX`) was used + +### Creation + +When creating a return entry add an [Addenda99](https://pkg.go.dev/github.com/moov-io/ach?tab=doc#Addenda99) record onto the EntryDetail with the appropriate return code. You'll need to copy the original `TraceNumber` and populate a new value when returning the file. + +```go +addenda99 := NewAddenda99() +addenda99.ReturnCode = "R07" +addenda99.OriginalTrace = "99912340000015" +addenda99.AddendaInformation = "Authorization Revoked" +addenda99.OriginalDFI = "9101298" + +// entry.Addenda99 = addenda99 +``` + +### Return codes + +| Code | Reason | Description | +|----|-----|------| +| `R01` | Insufficient Funds | Available balance is not sufficient to cover the dollar value of the debit entry | +| `R02` | Account Closed | Previously active account has been closed by customer or RDFI | +| `R03` | No Account/Unable to Locate Account | Account number structure is valid and passes editing process, but does not correspond to individual or is not an open account | +| `R04` | Invalid Account Number | Account number structure not valid; entry may fail check digit validation or may contain an incorrect number of digits. | +| `R05` | Improper Debit to Consumer Account | A CCD, CTX, or CBR debit entry was transmitted to a Consumer Account of the Receiver and was not authorized by the Receiver | +| `R06` | Returned per ODFI's Request | ODFI has requested RDFI to return the ACH entry (optional to RDFI - ODFI indemnifies RDFI) | +| `R07` | Authorization Revoked by Customer | Consumer, who previously authorized ACH payment, has revoked authorization from Originator (must be returned no later than 60 days from settlement date and customer must sign affidavit) | +| `R08` | Payment Stopped | Receiver of a recurring debit transaction has stopped payment to a specific ACH debit. RDFI should verify the Receiver's intent when a request for stop payment is made to insure this is not intended to be a revocation of authorization | +| `R09` | Uncollected Funds | Sufficient book or ledger balance exists to satisfy dollar value of the transaction, but the dollar value of transaction is in process of collection (i.e., uncollected checks) or cash reserve balance below dollar value of the debit entry. | +| `R10` | Customer Advises Originator is Not Known to Receiver and/or Originator is Not Authorized by Receiver to Debit Receiver’s Account | The receiver does not know the Originator’s identity and/or has not authorized the Originator to debit. Alternatively, for ARC, BOC, and POP entries, the signature is not authentic or authorized. | +| `R11` | Customer Advises Entry Not in Accordance with the Terms of the Authorization | The Originator and Receiver have a relationship, and an authorization to debit exists, but there is an error or defect in the payment such that the entry does not conform to the terms of the authorization. The Originator may correct the error and submit a new entry within 60 days of the return entry settlement date without the need for re-authorization by the Receiver. | +| `R12` | Branch Sold to Another DFI | Financial institution receives entry destined for an account at a branch that has been sold to another financial institution. | +| `R13` | RDFI not qualified to participate | Financial institution does not receive commercial ACH entries | +| `R14` | Representative payee deceased or unable to continue in that capacity | The representative payee authorized to accept entries on behalf of a beneficiary is either deceased or unable to continue in that capacity | +| `R15` | Beneficiary or bank account holder | (Other than representative payee) deceased* - (1) the beneficiary entitled to payments is deceased or (2) the bank account holder other than a representative payee is deceased | +| `R16` | Bank account frozen | Funds in bank account are unavailable due to action by RDFI or legal order | +| `R17` | File record edit criteria | Fields rejected by RDFI processing (identified in return addenda) | +| `R18` | Improper effective entry date | Entries have been presented prior to the first available processing window for the effective date. | +| `R19` | Amount field error | Improper formatting of the amount field | +| `R20` | Non-payment bank account | Entry destined for non-payment bank account defined by reg. | +| `R21` | Invalid company ID number | The company ID information not valid (normally CIE entries) | +| `R22` | Invalid individual ID number | Individual id used by receiver is incorrect (CIE entries) | +| `R23` | Credit entry refused by receiver | Receiver returned entry because minimum or exact amount not remitted, bank account is subject to litigation, or payment represents an overpayment, originator is not known to receiver or receiver has not authorized this credit entry to this bank account | +| `R24` | Duplicate entry | RDFI has received a duplicate entry | +| `R25` | Addenda error | Improper formatting of the addenda record information | +| `R26` | Mandatory field error | Improper information in one of the mandatory fields | +| `R27` | Trace number error | Original entry trace number is not valid for return entry; or addenda trace numbers do not correspond with entry detail record | +| `R28` | Transit routing number check digit error | Check digit for the transit routing number is incorrect | +| `R29` | Corporate customer advises not authorized | RDFI has bee notified by corporate receiver that debit entry of originator is not authorized | +| `R30` | RDFI not participant in check truncation program | Financial institution not participating in automated check safekeeping application | +| `R31` | Permissible return entry (CCD and CTX only) | RDFI has been notified by the ODFI that it agrees to accept a CCD or CTX return entry | +| `R32` | RDFI non-settlement | RDFI is not able to settle the entry | +| `R33` | Return of XCK entry | RDFI determines at its sole discretion to return an XCK entry; an XCK return entry may be initiated by midnight of the sixtieth day following the settlement date if the XCK entry | +| `R34` | Limited participation RDFI | RDFI participation has been limited by a federal or state supervisor | +| `R35` | Return of improper debit entry | ACH debit not permitted for use with the CIE standard entry class code (except for reversals) | +| `R37` | Source Document Presented for Payment (Adjustment Entry) | The source document to which an ARC, BOC or POP entry relates has been presented for payment. RDFI must obtain a Written Statement and return the entry within 60 days following Settlement Date | +| `R38` | Stop Payment on Source Document (Adjustment Entry) | A stop payment has been placed on the source document to which the ARC or BOC entry relates. RDFI must return no later than 60 days following Settlement Date. No Written Statement is required as the original stop payment form covers the return | +| `R39` | Improper Source Document | The RDFI has determined the source document used for the ARC, BOC or POP entry to its Receiver's account is improper. | + +#### Used for ENR entries and are initiated by a Federal Government Agency + +| Code | Reason | Description | +|----|-----|------| +| `R40` | Return of ENR Entry by Federal Government Agency (ENR Only) | This return reason code may only be used to return ENR entries and is at the federal Government Agency's Sole discretion | +| `R41` | Invalid Transaction Code (ENR only) | Either the Transaction Code included in Field 3 of the Addenda Record does not conform to the ACH Record Format Specifications contained in Appendix Three (ACH Record Format Specifications) or it is not appropriate with regard to an Automated Enrollment Entry. | +| `R42` | Routing Number/Check Digit Error (ENR Only) | The Routing Number and the Check Digit included in Field 3 of the Addenda Record is either not a valid number or it does not conform to the Modulus 10 formula. | +| `R43` | Invalid DFI Account Number (ENR Only) | The Receiver's account number included in Field 3 of the Addenda Record must include at least one alphameric character. | +| `R44` | Invalid Individual ID Number/Identification Number (ENR only) | The Individual ID Number/Identification Number provided in Field 3 of the Addenda Record does not match a corresponding ID number in the Federal Government Agency's records. | +| `R45` | Invalid Individual Name/Company Name (ENR only) | The name of the consumer or company provided in Field 3 of the Addenda Record either does not match a corresponding name in the Federal Government Agency's records or fails to include at least one alphameric character. | +| `R46` | Invalid Representative Payee Indicator (ENR Only) | The Representative Payee Indicator Code included in Field 3 of the Addenda Record has been omitted or it is not consistent with the Federal Government Agency's records. | +| `R47` | Duplicate Enrollment (ENR Only) | The Entry is a duplicate of an Automated Enrollment Entry previously initiated by a DFI. | + +#### Used for RCK entries only and are initiated by an RDFI + +| Code | Reason | Description | +|----|-----|------| +| `R50` | State Law Affecting RCK Acceptance | RDFI is located in a state that has not adopted Revised Article 4 of the UCC or the RDFI is located in a state that requires all canceled checks to be returned within the periodic statement | +| `R51` | Item Related to RCK Entry is Ineligible or RCK Entry is Improper | The item to which the RCK entry relates was not eligible, Originator did not provide notice of the RCK policy, signature on the item was not genuine, the item has been altered or amount of the entry was not accurately obtained from the item. RDFI must obtain a Written Statement and return the entry within 60 days following Settlement Date | +| `R52` | Stop Payment on Item (Adjustment Entry) | A stop payment has been placed on the item to which the RCK entry relates. RDFI must return no later than 60 days following Settlement Date. No Written Statement is required as the original stop payment form covers the return. | +| `R53` | Item and RCK Entry Presented for Payment (Adjustment Entry) | Both the RCK entry and check have been presented for payment. RDFI must obtain a Written Statement and return the entry within 60 days following Settlement Date | + +#### Used by the ODFI for dishonored return entries + +| Code | Reason | Description | +|----|-----|------| +| `R61` | Misrouted Return | The financial institution preparing the Return Entry (the RDFI of the original Entry) has placed the incorrect Routing Number in the Receiving DFI Identification field. | +| `R67` | Duplicate Return | The ODFI has received more than one Return for the same Entry. | +| `R68` | Untimely Return | The Return Entry has not been sent within the time frame established by these Rules. | +| `R69` | Field Error(s) | One or more of the field requirements are incorrect. | +| `R70` | Permissible Return Entry Not Accepted/Return Not Requested by ODFI | The ODFI has received a Return Entry identified by the RDFI as being returned with the permission of, or at the request of, the ODFI, but the ODFI has not agreed to accept the Entry or has not requested the return of the Entry. | + +#### Used by the RDFI for contested dishonored return entries + +| Code | Reason | Description | +|----|-----|------| +| `R71` | Misrouted Dishonored Return | The financial institution preparing the dishonored Return Entry (the ODFI of the original Entry) has placed the incorrect Routing Number in the Receiving DFI Identification field. | +| `R72` | Untimely Dishonored Return | The dishonored Return Entry has not been sent within the designated time frame. | +| `R73` | Timely Original Return | The RDFI is certifying that the original Return Entry was sent within the time frame designated in these Rules. | +| `R74` | Corrected Return | The RDFI is correcting a previous Return Entry that was dishonored using Return Reason Code R69 (Field Error(s)) because it contained incomplete or incorrect information. | +| `R75` | Return Not a Duplicate | The Return Entry was not a duplicate of an Entry previously returned by the RDFI. | +| `R76` | No Errors Found | The original Return Entry did not contain the errors indicated by the ODFI in the dishonored Return Entry. | + +#### Used by Gateways for the return of international payments + +| Code | Reason | Description | +|----|-----|------| +| `R80` | IAT Entry Coding Error | The IAT Entry is being returned due to one or more of the following conditions: Invalid DFI/Bank Branch Country Code, invalid DFI/Bank Identification Number Qualifier, invalid Foreign Exchange Indicator, invalid ISO Originating Currency Code, invalid ISO Destination Currency Code, invalid ISO Destination Country Code, invalid Transaction Type Code | +| `R81` | Non-Participant in IAT Program | The IAT Entry is being returned because the Gateway does not have an agreement with either the ODFI or the Gateway's customer to transmit Outbound IAT Entries. | +| `R82` | Invalid Foreign Receiving DFI Identification | The reference used to identify the Foreign Receiving DFI of an Outbound IAT Entry is invalid. | +| `R83` | Foreign Receiving DFI Unable to Settle | The IAT Entry is being returned due to settlement problems in the foreign payment system. | +| `R84` | Entry Not Processed by Gateway | For Outbound IAT Entries, the Entry has not been processed and is being returned at the Gateway's discretion because either (1) the processing of such Entry may expose the Gateway to excessive risk, or (2) the foreign payment system does not support the functions needed to process the transaction. | +| `R85` | Incorrectly Coded Outbound International Payment | The RDFI/Gateway has identified the Entry as an Outbound international payment and is returning the Entry because it bears an SEC Code that lacks information required by the Gateway for OFAC compliance. | diff --git a/docs/rtn-check-digit calculator.xls b/docs/rtn-check-digit calculator.xls new file mode 100644 index 000000000..b6d9cc6da Binary files /dev/null and b/docs/rtn-check-digit calculator.xls differ diff --git a/docs/sec-codes-table.md b/docs/sec-codes-table.md new file mode 100644 index 000000000..75f15b9e2 --- /dev/null +++ b/docs/sec-codes-table.md @@ -0,0 +1,56 @@ +--- +layout: page +title: SEC Codes Table +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +## Picking which Standard Entry Class code to use + +The [NACHA Corporate Rules and Guidelines](https://github.com/moov-io/ach/blob/master/docs/2013-Corporate-Rules-and-Guidelines.pdf) offer a helpful table for choosing the correct Standard Entry Class (SEC) Code to use for a given enrollment and transaction. The table has been re-written below: + +### Point of sale transactions + +#### Physical enrollment + +- Customer uses their debit card at a POS terminal. + +

Proper SEC Code: POS

+ +#### Internet enrollment + +- Customer uses their debit card at a POS terminal. + +

Proper SEC Code: POS

+ +### Internet transactions + +#### Physical enrollment + +- Customer initiates debits via the bank’s web site or individual bill payments at a merchant web site. +- Customer uses their ACH-based debit card to make a purchase at a web site. + +

Proper SEC Code: PPD

+ +#### Internet enrollment + +- Customer authorizes a transfer of funds into a savings account. +- Customer initiates bill payments at a merchant web site on their debit card. +- Customer uses their ACH-based debit card to make a purchase at a web site. + +

Proper SEC Code: WEB

+ +### ATM transactions + +#### Physical enrollment + +- Customer uses the card at an ATM to withdraw cash. + +

Proper SEC Code: MTE

+ +#### Internet enrollment + +- Customer uses an ATM to withdraw cash: + +

Proper SEC Code: MTE

diff --git a/docs/segment-file.md b/docs/segment-file.md new file mode 100644 index 000000000..437d1476d --- /dev/null +++ b/docs/segment-file.md @@ -0,0 +1,51 @@ +--- +layout: page +title: Segmenting files +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Segmenting files + +ACH File creation should default to NACHA rules by supporting both credits and debits in the same ACH file. Some legacy systems require the file to be segmented and contain only debits or only credits in a file. + +Segmenting files is a post file creation process that takes an input of an ACH.File and returns two ACH.Files that have been segmented (debits and credits). + +Note: Segmented files (via `SegmentFile()`) are only in ACH v1.1.0 and later. + +## Creating segmented files + +An ACH [File](https://godoc.org/github.com/moov-io/ach#File) supports calling [SegmentFile](https://godoc.org/github.com/moov-io/ach#File.SegmentFile) to create a debit ach file and credit ach file from an ach file that contains mixed debits and credits. + +```go +// open an ACH file(from the ./examples/ directory) +f, err := os.Open(filepath.Join("testdata", "ppd-mixedDebitCredit.ach")) +if err != nil { + log.Fatal(err) +} + +// read file +r := ach.NewReader(f) +achFile, err := r.Read() +if err != nil { + fmt.Printf("Issue reading file: %+v \n", err) +} + +// Segment File +creditFile, debitFile, err := achFile.SegmentFile() +if err != nil { + fmt.Printf("Could not segment the file: %v", err) +} + +// write the file to std out. Anything io.Writer +w := ach.NewWriter(os.Stdout) +if err := w.Write(creditFile); err != nil { + log.Fatalf("Unexpected error: %s\n", err) +} +if err := w.Write(debitFile); err != nil { + log.Fatalf("Unexpected error: %s\n", err) +} + +w.Flush() +``` diff --git a/docs/server-config.md b/docs/server-config.md new file mode 100644 index 000000000..078617380 --- /dev/null +++ b/docs/server-config.md @@ -0,0 +1,21 @@ +--- +layout: page +title: API configuration +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Configuration settings + +| Environmental Variable | Description | Default | +|-----|-----|-----| +| `ACH_FILE_TTL` | Time to live (TTL) for `*ach.File` objects stored in the in-memory repository. | 0 = No TTL / Never delete files (Example: `240m`) | +| `LOG_FORMAT` | Format for logging lines to be written as. | Options: `json`, `plain` - Default: `plain` | +| `HTTP_BIND_ADDRESS` | Address for ACH to bind its HTTP server on. This overrides the command-line flag `-http.addr`. | Default: `:8080` | +| `HTTP_ADMIN_BIND_ADDRESS` | Address for ACH to bind its admin HTTP server on. This overrides the command-line flag `-admin.addr`. | Default: `:9090` | +| `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 | +| `HTTPS_KEY_FILE` | Filepath of a private key matching the leaf certificate from `HTTPS_CERT_FILE`. | Empty | + +## Data persistence +By design ACH **does not persist** (save) any data about the files, batches, or entry details created. The only storage occurs in memory of the process and upon restart ACH will have no files, batches, or data saved. Also, no in memory encryption of the data is performed. \ No newline at end of file diff --git a/docs/usage-command-line.md b/docs/usage-command-line.md new file mode 100644 index 000000000..365fca8d3 --- /dev/null +++ b/docs/usage-command-line.md @@ -0,0 +1,33 @@ +--- +layout: page +title: Command line +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Command line + +On each release there's an `achcli` utility released. This tool can display ACH files in a human-readable format which is easier to read than their plaintext format. It also allows masking `DFIAccountNumber` values with the `-mask` flag. + +``` +$ wget -O achcli https://github.com/moov-io/ach/releases/download/v1.6.1/achcli-darwin-amd64 && chmod +x achcli + +$ achcli test/testdata/ppd-debit.ach +Describing ACH file 'test/testdata/ppd-debit.ach' + + Origin OriginName Destination DestinationName FileCreationDate FileCreationTime + 121042882 My Bank Name 231380104 Federal Reserve Bank 190624 0000 + + BatchNumber SECCode ServiceClassCode CompanyName DiscretionaryData Identification EntryDescription DescriptiveDate + 1 PPD 225 (Debits Only) Name on Account 121042882 REG.SALARY + + TransactionCode RDFIIdentification AccountNumber Amount Name TraceNumber Category + 27 (Checking Debit) 23138010 12345678 100000000 Receiver Account Name 121042880000001 + + ServiceClassCode EntryAddendaCount EntryHash TotalDebits TotalCredits MACCode ODFIIdentification BatchNumber + 225 (Debits Only) 1 23138010 100000000 0 12104288 1 + + BatchCount BlockCount EntryAddendaCount TotalDebitAmount TotalCreditAmount + 1 1 1 100000000 0 +``` \ No newline at end of file diff --git a/docs/usage-docker.md b/docs/usage-docker.md new file mode 100644 index 000000000..4cfb3f3c5 --- /dev/null +++ b/docs/usage-docker.md @@ -0,0 +1,41 @@ +--- +layout: page +title: Docker +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Docker + +We publish a [public Docker image `moov/ach`](https://hub.docker.com/r/moov/ach/) from Docker Hub or use this repository. No configuration is required to serve on `:8080` and metrics at `:9090/metrics` in Prometheus format. We also have Docker images for [OpenShift](https://quay.io/repository/moov/ach?tab=tags) published as `quay.io/moov/ach`. + +Pull & start the Docker image: +``` +docker pull moov/ach:latest +docker run -p 8080:8080 -p 9090:9090 moov/ach:latest +``` + +List files stored in-memory: +``` +curl localhost:8080/files +``` +``` +{"files":[],"error":null} +``` + +Create a file on the HTTP server: +``` +curl -X POST --data-binary "@./test/testdata/ppd-debit.ach" http://localhost:8080/files/create +``` +``` +{"id":"","error":null} +``` + +Read the ACH file (in JSON form): +``` +curl http://localhost:8080/files/ +``` +``` +{"file":{"id":"","fileHeader":{"id":"","immediateDestination":"231380104","immediateOrigin":"121042882", ... +``` \ No newline at end of file diff --git a/docs/usage-go.md b/docs/usage-go.md new file mode 100644 index 000000000..0153ff1a2 --- /dev/null +++ b/docs/usage-go.md @@ -0,0 +1,57 @@ +--- +layout: page +title: Go library +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Go library + +This project uses [Go Modules](https://github.com/golang/go/wiki/Modules) and Go v1.14 or higher. See [Golang's install instructions](https://golang.org/doc/install) for help in setting up Go. You can download the source code and we offer [tagged and released versions](https://github.com/moov-io/ach/releases/latest) as well. We highly recommend you use a tagged release for production. + +``` +$ git@github.com:moov-io/ach.git + +# Pull down into the Go Module cache +$ go get -u github.com/moov-io/ach + +$ go doc github.com/moov-io/ach BatchHeader +``` + +The package [`github.com/moov-io/ach`](https://pkg.go.dev/github.com/moov-io/ach) offers a Go-based ACH file reader and writer. To get started, check out a specific example: + +### Supported Standard Entry Class (SEC) codes + +| SEC Code | Description | Example | Read | Write | +|----------|---------------------------------------|------------------------------------------|-----------------------------------|------------------------------------| +| ACK | Acknowledgment Entry for CCD | [Credit](https://github.com/moov-io/ach/blob/master/test/ach-ack-read/ack-read.ach) | [ACK Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-AckRead) | [ACK Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-AckWrite) | +| ADV | Automated Accounting Advice | [Prenote Debit](https://github.com/moov-io/ach/blob/master/test/ach-adv-read/adv-read.ach) | [ADV Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-AdvRead) | [ADV Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-AdvWrite) | +| ARC | Accounts Receivable Entry | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-arc-read/arc-debit.ach) | [ARC Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-ArcReadDebit) | [ARC Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-ArcWriteDebit) | +| ATX | Acknowledgment Entry for CTX | [Credit](https://github.com/moov-io/ach/blob/master/test/ach-atx-read/atx-read.ach) | [ATX Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-AtxRead) | [ATX Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-AtxWrite) | +| BOC | Back Office Conversion | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-boc-read/boc-debit.ach) | [BOC Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-BocReadDebit) | [BOC Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-BocWriteDebit) | +| CCD | Corporate credit or debit | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-ccd-read/ccd-debit.ach) | [CCD Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CcdReadDebit) | [CCD Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CcdWriteDebit) | +| CIE | Customer-Initiated Entry | [Credit](https://github.com/moov-io/ach/blob/master/test/ach-cie-read/cie-credit.ach) | [CIE Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CieRead) | [CIE Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CieWrite) | +| COR | Automated Notification of Change(NOC) | [NOC](https://github.com/moov-io/ach/blob/master/test/ach-cor-read/cor-read.ach) | [COR Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CorReadCredit) | [COR Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CorWriteCredit) | +| CTX | Corporate Trade Exchange | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-ctx-read/ctx-debit.ach) | [CTX Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CtxReadDebit) | [CTX Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-CtxWriteDebit) | +| DNE | Death Notification Entry | [DNE](https://github.com/moov-io/ach/blob/master/test/ach-dne-read/dne-read.ach) | [DNE Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-DneRead) | [DNE Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-DneWrite) | +| ENR | Automatic Enrollment Entry | [ENR](https://github.com/moov-io/ach/blob/master/test/ach-enr-read/enr-read.ach) | [ENR Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-EnrRead) | [ENR Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-EnrWrite) | +| IAT | International ACH Transactions | [Credit](https://github.com/moov-io/ach/blob/master/test/ach-iat-read/iat-credit.ach) | [IAT Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-IatReadMixedCreditDebit) | [IAT Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-IatWriteMixedCreditDebit) | +| MTE | Machine Transfer Entry | [Credit](https://github.com/moov-io/ach/blob/master/test/ach-mte-read/mte-read.ach) | [MTE Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-MteReadDebit) | [MTE Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-MteWriteDebit) | +| POP | Point of Purchase | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-pop-read/pop-debit.ach) | [POP Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PopReadDebit) | [POP Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PopWriteDebit) | +| POS | Point of Sale | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-pos-read/pos-debit.ach) | [POS Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PosReadDebit) | [POS Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PosWriteDebit) | +| PPD | Prearranged payment and deposits | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-ppd-read/ppd-debit.ach) [Credit](https://github.com/moov-io/ach/blob/master/test/ach-ppd-read/ppd-credit.ach) | [PPD Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PpdReadCredit) | [PPD Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PpdWriteCredit) | +| RCK | Represented Check Entries | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-rck-read/rck-debit.ach) | [RCK Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-RckReadDebit) | [RCK Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-RckWriteDebit) | +| SHR | Shared Network Entry | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-shr-read/shr-debit.ach) | [SHR Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-ShrReadDebit) | [SHR Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-ShrWrite) | +| TEL | Telephone-Initiated Entry | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-tel-read/tel-debit.ach) | [TEL Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-TelReadDebit) | [TEL Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-TelWriteDebit) | +| TRC | Truncated Check Entry | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-trc-read/trc-debit.ach) | [TRC Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-TrcReadDebit) | [TRC Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-TrcWriteDebit) | +| TRX | Check Truncation Entries Exchange | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-trx-read/trx-debit.ach) | [TRX Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-TrxReadDebit) | [TRX Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-TrxWriteDebit) | +| WEB | Internet-initiated Entries | [Credit](https://github.com/moov-io/ach/blob/master/test/ach-web-read/web-credit.ach) | [WEB Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-WebReadCredit) | [WEB Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-WebWriteCredit) | +| XCK | Destroyed Check Entry | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-xck-read/xck-debit.ach) | [XCK Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-XckReadDebit) | [XCK Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-XckWriteDebit) | + +### Segment files + +| SEC Code | Name | Example | Read | Write | +|----------|---------------------------------------|------------------------------------------|-----------------------------------|------------------------------------| +| IAT | International ACH Transactions | [Credit](https://github.com/moov-io/ach/blob/master/test/ach-iat-read/iat-credit.ach) | [IAT Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-IatReadMixedCreditDebit) | [IAT Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-IatWriteMixedCreditDebit) | +| PPD | Prearranged payment and deposits | [Debit](https://github.com/moov-io/ach/blob/master/test/ach-ppd-read/ppd-debit.ach) [Credit](https://github.com/moov-io/ach/blob/master/test/ach-ppd-read/ppd-credit.ach) | [PPD Read](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PpdReadSegmentFile) | [PPD Write](https://pkg.go.dev/github.com/moov-io/ach/examples#example-package-PpdWriteSegmentFile) | \ No newline at end of file diff --git a/docs/usage-google-cloud.md b/docs/usage-google-cloud.md new file mode 100644 index 000000000..6f34a94b7 --- /dev/null +++ b/docs/usage-google-cloud.md @@ -0,0 +1,79 @@ +--- +layout: page +title: Google Cloud Run +hide_hero: true +show_sidebar: false +menubar: docs-menu +--- + +# Google Cloud Run + +To get started in a hosted environment you can deploy this project to the Google Cloud Platform. + +From your [Google Cloud dashboard](https://console.cloud.google.com/home/dashboard) create a new project and call it: +``` +moov-ach-demo +``` + +Click the button below to deploy this project to Google Cloud. + +[![Run on Google Cloud](https://deploy.cloud.run/button.svg)](https://deploy.cloud.run/?git_repo=https://github.com/moov-io/ach&revision=master) + + +In the cloud shell you should be prompted with: +``` +Choose a project to deploy this application: +``` + +Using the arrow keys select: +``` +moov-ach-demo +``` + + +You'll then be prompted to choose a region, use the arrow keys to select the region closest to you and hit enter. +``` +Choose a region to deploy this application: +``` + + + +Upon a successful build you will be given a URL where the API has been deployed: +``` +https://YOUR-ACH-APP-URL.a.run.app +``` + +From the cloud shell you need to cd into the `ach` folder: +``` +cd ach +``` + +Now you can list files stored in-memory: +``` +curl https://YOUR-ACH-APP-URL.a.run.app/files +``` +You should get this response: +``` +{"files":[],"error":null} +``` + + +Create a file on the server: +``` +curl -X POST --data-binary "@./test/testdata/ppd-debit.ach" https://YOUR-ACH-APP-URL.a.run.app/files/create +``` +You should get this response: +``` +{"id":"","error":null} +``` + + +Finally, read the contents of the file you've just posted: +``` +curl https://YOUR-ACH-APP-URL.a.run.app/files/ +``` + +You should get this response: +``` +{"file":{"id":"","fileHeader":{"id":"...","immediateDestination":"231380104","immediateOrigin":"121042882", ... +``` \ No newline at end of file diff --git a/entryDetail.go b/entryDetail.go index c7de18676..f84544399 100644 --- a/entryDetail.go +++ b/entryDetail.go @@ -1,53 +1,54 @@ -// Copyright 2017 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. package ach import ( "fmt" + "sort" "strconv" + "strings" + "unicode/utf8" ) // EntryDetail contains the actual transaction data for an individual entry. // Fields include those designating the entry as a deposit (credit) or -// withdrawal (debit), the transit routing number for the entry recipient’s financial +// withdrawal (debit), the transit routing number for the entry recipient's financial // institution, the account number (left justify,no zero fill), name, and dollar amount. type EntryDetail struct { - // RecordType defines the type of record in the block. 6 - recordType string - // TransactionCode if the receivers account is: - // Credit (deposit) to checking account ‘22’ - // Prenote for credit to checking account ‘23’ - // Debit (withdrawal) to checking account ‘27’ - // Prenote for debit to checking account ‘28’ - // Credit to savings account ‘32’ - // Prenote for credit to savings account ‘33’ - // Debit to savings account ‘37’ - // Prenote for debit to savings account ‘38’ - TransactionCode int - - // rdfiIdentification is the RDFI's routing number without the last digit. + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // TransactionCode if the receivers account is checking, savings, general ledger (GL) or loan. + TransactionCode int `json:"transactionCode"` + // RDFIIdentification is the RDFI's routing number without the last digit. // Receiving Depository Financial Institution - RDFIIdentification int - + RDFIIdentification string `json:"RDFIIdentification"` // CheckDigit the last digit of the RDFI's routing number - CheckDigit int - + CheckDigit string `json:"checkDigit"` // DFIAccountNumber is the receiver's bank account number you are crediting/debiting. // It important to note that this is an alphanumeric field, so its space padded, no zero padded - DFIAccountNumber string - + DFIAccountNumber string `json:"DFIAccountNumber"` // Amount Number of cents you are debiting/crediting this account - Amount int - - // IdentificationNumber n internal identification (alphanumeric) that + Amount int `json:"amount"` + // IdentificationNumber an internal identification (alphanumeric) that // you use to uniquely identify this Entry Detail Record - IdentificationNumber string - + IdentificationNumber string `json:"identificationNumber,omitempty"` // IndividualName The name of the receiver, usually the name on the bank account - IndividualName string - + IndividualName string `json:"individualName"` // DiscretionaryData allows ODFIs to include codes, of significance only to them, // to enable specialized handling of the entry. There will be no // standardized interpretation for the value of this field. It can either @@ -55,14 +56,12 @@ type EntryDetail struct { // according to the needs of the ODFI and/or Originator involved. This // field must be returned intact for any returned entry. // - // WEB uses the Discretionary Data Field as the Payment Type Code - DiscretionaryData string - + // WEB and TEL batches use the Discretionary Data Field as the Payment Type Code + DiscretionaryData string `json:"discretionaryData,omitempty"` // AddendaRecordIndicator indicates the existence of an Addenda Record. // A value of "1" indicates that one ore more addenda records follow, // and "0" means no such record is present. - AddendaRecordIndicator int - + AddendaRecordIndicator int `json:"addendaRecordIndicator,omitempty"` // TraceNumber assigned by the ODFI in ascending sequence, is included in each // Entry Detail Record, Corporate Entry Detail Record, and addenda Record. // Trace Numbers uniquely identify each entry within a batch in an ACH input file. @@ -71,64 +70,143 @@ type EntryDetail struct { // For addenda Records, the Trace Number will be identical to the Trace Number // in the associated Entry Detail Record, since the Trace Number is associated // with an entry or item rather than a physical record. - TraceNumber int - - // Addendum a list of Addenda for the Entry - // keeping separarte lists for different types of addenda... - Addendum []Addenda - ReturnAddendum []ReturnAddenda + // + // Use TraceNumberField for a properly formatted string representation. + TraceNumber string `json:"traceNumber,omitempty"` + // Addenda02 for use with StandardEntryClassCode MTE, POS, and SHR + Addenda02 *Addenda02 `json:"addenda02,omitempty"` + // Addenda05 for use with StandardEntryClassCode: ACK, ATX, CCD, CIE, CTX, DNE, ENR, WEB, PPD, TRX. + Addenda05 []*Addenda05 `json:"addenda05,omitempty"` + // Addenda98 for user with NOC + Addenda98 *Addenda98 `json:"addenda98,omitempty"` + // Addenda99 for use with Returns + Addenda99 *Addenda99 `json:"addenda99,omitempty"` + // Addenda99Contested for use with Contested Dishonored Returns + Addenda99Contested *Addenda99Contested `json:"addenda99Contested,omitempty"` + // Addenda99Dishonored for use with Dishonored Returns + Addenda99Dishonored *Addenda99Dishonored `json:"addenda99Dishonored,omitempty"` + // Category defines if the entry is a Forward, Return, or NOC + Category string `json:"category,omitempty"` // validator is composed for data validation validator // converters is composed for ACH to golang Converters converters -} -// EntryParam is the minimal fields required to make a ach entry -type EntryParam struct { - ReceivingDFI string `json:"receiving_dfi"` - RDFIAccount string `json:"rdfi_account"` - Amount string `json:"amount"` - IDNumber string `json:"id_number,omitempty"` - IndividualName string `json:"individual_name,omitempty"` - ReceivingCompany string `json:"receiving_company,omitempty"` - DiscretionaryData string `json:"discretionary_data,omitempty"` - TransactionCode string `json:"transaction_code"` + validateOpts *ValidateOpts } -// NewEntryDetail returns a new EntryDetail with default values for none exported fields -func NewEntryDetail(params ...EntryParam) *EntryDetail { - entry := &EntryDetail{ - recordType: "6", - } - if len(params) > 0 { - entry.SetRDFI(entry.parseNumField(params[0].ReceivingDFI)) - entry.DFIAccountNumber = params[0].RDFIAccount - entry.Amount = entry.parseNumField(params[0].Amount) - entry.IdentificationNumber = params[0].IDNumber - if params[0].IndividualName != "" { - entry.IndividualName = params[0].IndividualName - } else { - entry.IndividualName = params[0].ReceivingCompany - } - entry.DiscretionaryData = params[0].DiscretionaryData - entry.TransactionCode = entry.parseNumField(params[0].TransactionCode) +const ( + // CategoryForward defines the entry as being sent to the receiving institution + CategoryForward = "Forward" + // CategoryReturn defines the entry as being a return of a forward entry back to the originating institution + CategoryReturn = "Return" + // CategoryNOC defines the entry as being a notification of change of a forward entry to the originating institution + CategoryNOC = "NOC" + // CategoryDishonoredReturn defines the entry as being a dishonored return initiated by the ODFI to the RDFI that + // submitted the return entry + CategoryDishonoredReturn = "DishonoredReturn" + // CategoryDishonoredReturnContested defines the entry as a contested dishonored return initiated by the RDFI to + // the ODFI that submitted the dishonored return + CategoryDishonoredReturnContested = "DishonoredReturnContested" + + // TransactionCode Values + + // CheckingCredit is a credit to the receiver's checking account + CheckingCredit = 22 + // CheckingReturnNOCCredit is a return that credits the receiver's checking account + CheckingReturnNOCCredit = 21 + // CheckingPrenoteCredit is a pre-notification of a credit to the receiver's checking account + CheckingPrenoteCredit = 23 + // CheckingZeroDollarRemittanceCredit is a zero dollar remittance data credit to a checking account for CCD, CTX, + // ACK, and ATX entries + CheckingZeroDollarRemittanceCredit = 24 + // CheckingDebit is a debit to the receivers checking account + CheckingDebit = 27 + // CheckingReturnNOCDebit is a return that debits the receiver's checking account + CheckingReturnNOCDebit = 26 + // CheckingPrenoteDebit is a pre-notification of a debit to the receiver's checking account + CheckingPrenoteDebit = 28 + // CheckingZeroDollarRemittanceDebit is a zero dollar remittance data debit to a checking account for CCD, CTX, + // ACK, and ATX entries + CheckingZeroDollarRemittanceDebit = 29 + + // SavingsCredit is a credit to the receiver's savings account + SavingsCredit = 32 + // SavingsReturnNOCCredit is a return that credits the receiver's savings account + SavingsReturnNOCCredit = 31 + // SavingsPrenoteCredit is a pre-notification of a credit to the receiver's savings account + SavingsPrenoteCredit = 33 + // SavingsZeroDollarRemittanceCredit is a zero dollar remittance data credit to a savings account for CCD + // and CTX entries + SavingsZeroDollarRemittanceCredit = 34 + // SavingsDebit is a debit to the receivers savings account + SavingsDebit = 37 + // SavingsReturnNOCDebit is a return that debits the receiver's savings account + SavingsReturnNOCDebit = 36 + // SavingsPrenoteDebit is a pre-notification of a debit to the receiver's savings account + SavingsPrenoteDebit = 38 + // SavingsZeroDollarRemittanceDebit is a zero dollar remittance data debit to a savings account for CCD + // and CTX entries + SavingsZeroDollarRemittanceDebit = 39 + + // GLCredit is a credit to the receiver's general ledger (GL) account + GLCredit = 42 + // GLReturnNOCCredit is a return that credits the receiver's general ledger (GL) account + GLReturnNOCCredit = 41 + // GLPrenoteCredit is a pre-notification of a credit to the receiver's general ledger (GL) account + GLPrenoteCredit = 43 + // GLZeroDollarRemittanceCredit is a zero dollar remittance data credit to the receiver's general ledger (GL) account + GLZeroDollarRemittanceCredit = 44 + // GLDebit is a debit to the receiver's general ledger (GL) account + GLDebit = 47 + // GLReturnNOCDebit is a return that debits the receiver's general ledger (GL) account + GLReturnNOCDebit = 46 + // GLPrenoteDebit is a pre-notification of a debit to the receiver's general ledger (GL) account + GLPrenoteDebit = 48 + // GLZeroDollarRemittanceDebit is a zero dollar remittance data debit to the receiver's general ledger (GL) account + GLZeroDollarRemittanceDebit = 49 + + // LoanCredit is a credit to the receiver's loan account + LoanCredit = 52 + // LoanReturnNOCCredit is a return that credits the receiver's loan account + LoanReturnNOCCredit = 51 + // LoanPrenoteCredit is a pre-notification of a credit to the receiver's loan account + LoanPrenoteCredit = 53 + // LoanZeroDollarRemittanceCredit is a zero dollar remittance data credit to the receiver's loan account + LoanZeroDollarRemittanceCredit = 54 + // LoanDebit is a debit (Reversal's Only) to the receiver's loan account + LoanDebit = 55 + // LoanReturnNOCDebit is a return that debits the receiver's loan account + LoanReturnNOCDebit = 56 + // LoanPrenoteDebit is N/A + // LoanZeroDollarRemittanceDebit is N/A + + // End of TransactionCode Values +) - entry.setTraceNumber(entry.RDFIIdentification, 1) - return entry +// NewEntryDetail returns a new EntryDetail with default values for non exported fields +func NewEntryDetail() *EntryDetail { + entry := &EntryDetail{ + Category: CategoryForward, } return entry } // Parse takes the input record string and parses the EntryDetail values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. func (ed *EntryDetail) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + // 1-1 Always "6" - ed.recordType = "6" // 2-3 is checking credit 22 debit 27 savings credit 32 debit 37 ed.TransactionCode = ed.parseNumField(record[1:3]) // 4-11 the RDFI's routing number without the last digit. - ed.RDFIIdentification = ed.parseNumField(record[3:11]) + ed.RDFIIdentification = ed.parseStringField(record[3:11]) // 12-12 The last digit of the RDFI's routing number - ed.CheckDigit = ed.parseNumField(record[11:12]) + ed.CheckDigit = ed.parseStringField(record[11:12]) // 13-29 The receiver's bank account number you are crediting/debiting ed.DFIAccountNumber = record[12:29] // 30-39 Number of cents you are debiting/crediting this account @@ -137,30 +215,42 @@ func (ed *EntryDetail) Parse(record string) { ed.IdentificationNumber = record[39:54] // 55-76 The name of the receiver, usually the name on the bank account ed.IndividualName = record[54:76] - // 77-78 allows ODFIs to include codes of significance only to them - // normally blank + // 77-78 allows ODFIs to include codes of significance only to them, normally blank + // + // For WEB and TEL batches this field is the PaymentType which is either R(reoccurring) or S(single) ed.DiscretionaryData = record[76:78] // 79-79 1 if addenda exists 0 if it does not ed.AddendaRecordIndicator = ed.parseNumField(record[78:79]) - // 80-84 An internal identification (alphanumeric) that you use to uniquely identify - // this Entry Detail Recor This number should be unique to the transaction and will help identify the transaction in case of an inquiry - ed.TraceNumber = ed.parseNumField(record[79:94]) + // 80-94 An internal identification (alphanumeric) that you use to uniquely identify + // this Entry Detail Record This number should be unique to the transaction and will help identify the transaction in case of an inquiry + ed.TraceNumber = strings.TrimSpace(record[79:94]) } // String writes the EntryDetail struct to a 94 character string. func (ed *EntryDetail) String() string { - return fmt.Sprintf("%v%v%v%v%v%v%v%v%v%v%v", - ed.recordType, - ed.TransactionCode, - ed.RDFIIdentificationField(), - ed.CheckDigit, - ed.DFIAccountNumberField(), - ed.AmountField(), - ed.IdentificationNumberField(), - ed.IndividualNameField(), - ed.DiscretionaryDataField(), - ed.AddendaRecordIndicator, - ed.TraceNumberField()) + var buf strings.Builder + buf.Grow(94) + buf.WriteString(entryDetailPos) + buf.WriteString(fmt.Sprintf("%v", ed.TransactionCode)) + buf.WriteString(ed.RDFIIdentificationField()) + buf.WriteString(ed.CheckDigit) + buf.WriteString(ed.DFIAccountNumberField()) + buf.WriteString(ed.AmountField()) + buf.WriteString(ed.IdentificationNumberField()) + buf.WriteString(ed.IndividualNameField()) + buf.WriteString(ed.DiscretionaryDataField()) + buf.WriteString(fmt.Sprintf("%v", ed.AddendaRecordIndicator)) + buf.WriteString(ed.TraceNumberField()) + return buf.String() +} + +// SetValidation stores ValidateOpts on the EntryDetail which are to be used to override +// the default NACHA validation rules. +func (ed *EntryDetail) SetValidation(opts *ValidateOpts) { + if ed == nil { + return + } + ed.validateOpts = opts } // Validate performs NACHA format rule checks on the record and returns an error if not Validated @@ -169,102 +259,108 @@ func (ed *EntryDetail) Validate() error { if err := ed.fieldInclusion(); err != nil { return err } - if ed.recordType != "6" { - msg := fmt.Sprintf(msgRecordType, 6) - return &FieldError{FieldName: "recordType", Value: ed.recordType, Msg: msg} - } - if err := ed.isTransactionCode(ed.TransactionCode); err != nil { - return &FieldError{FieldName: "TransactionCode", Value: strconv.Itoa(ed.TransactionCode), Msg: err.Error()} + if ed.validateOpts != nil && ed.validateOpts.CheckTransactionCode != nil { + if err := ed.validateOpts.CheckTransactionCode(ed.TransactionCode); err != nil { + return fieldError("TransactionCode", err, strconv.Itoa(ed.TransactionCode)) + } + } else { + if err := ed.isTransactionCode(ed.TransactionCode); err != nil { + return fieldError("TransactionCode", err, strconv.Itoa(ed.TransactionCode)) + } } if err := ed.isAlphanumeric(ed.DFIAccountNumber); err != nil { - return &FieldError{FieldName: "DFIAccountNumber", Value: ed.DFIAccountNumber, Msg: err.Error()} + return fieldError("DFIAccountNumber", err, ed.DFIAccountNumber) + } + if ed.Amount < 0 { + return fieldError("Amount", ErrNegativeAmount, ed.Amount) + } + if err := ed.amountOverflowsField(); err != nil { + return fieldError("Amount", err, ed.Amount) } if err := ed.isAlphanumeric(ed.IdentificationNumber); err != nil { - return &FieldError{FieldName: "IdentificationNumber", Value: ed.IdentificationNumber, Msg: err.Error()} + return fieldError("IdentificationNumber", err, ed.IdentificationNumber) } if err := ed.isAlphanumeric(ed.IndividualName); err != nil { - return &FieldError{FieldName: "IndividualName", Value: ed.IndividualName, Msg: err.Error()} + return fieldError("IndividualName", err, ed.IndividualName) } if err := ed.isAlphanumeric(ed.DiscretionaryData); err != nil { - return &FieldError{FieldName: "DiscretionaryData", Value: ed.DiscretionaryData, Msg: err.Error()} + return fieldError("DiscretionaryData", err, ed.DiscretionaryData) } + calculated := ed.CalculateCheckDigit(ed.RDFIIdentificationField()) - if calculated != ed.CheckDigit { - msg := fmt.Sprintf(msgValidCheckDigit, calculated) - return &FieldError{FieldName: "RDFIIdentification", Value: strconv.Itoa(ed.CheckDigit), Msg: msg} + + edCheckDigit, err := strconv.Atoi(ed.CheckDigit) + if err != nil { + return fieldError("CheckDigit", err, ed.CheckDigit) } + if calculated != edCheckDigit { + return fieldError("RDFIIdentification", NewErrValidCheckDigit(calculated), ed.CheckDigit) + } return nil } // fieldInclusion validate mandatory fields are not default values. If fields are // invalid the ACH transfer will be returned. func (ed *EntryDetail) fieldInclusion() error { - if ed.recordType == "" { - return &FieldError{FieldName: "recordType", Value: ed.recordType, Msg: msgFieldInclusion} - } if ed.TransactionCode == 0 { - return &FieldError{FieldName: "TransactionCode", Value: strconv.Itoa(ed.TransactionCode), Msg: msgFieldInclusion} + return fieldError("TransactionCode", ErrConstructor, strconv.Itoa(ed.TransactionCode)) } - if ed.RDFIIdentification == 0 { - return &FieldError{FieldName: "RDFIIdentification", Value: ed.RDFIIdentificationField(), Msg: msgFieldInclusion} + if ed.RDFIIdentification == "" { + return fieldError("RDFIIdentification", ErrConstructor, ed.RDFIIdentificationField()) } if ed.DFIAccountNumber == "" { - return &FieldError{FieldName: "DFIAccountNumber", Value: ed.DFIAccountNumber, Msg: msgFieldInclusion} + return fieldError("DFIAccountNumber", ErrConstructor, ed.DFIAccountNumber) } - // amount can be 0 if it's COR, should probably be more specific... - /* - if ed.Amount == 0 { - return &FieldError{FieldName: "Amount", Value: ed.AmountField(), Msg: msgFieldInclusion} - }*/ if ed.IndividualName == "" { - return &FieldError{FieldName: "IndividualName", Value: ed.IndividualName, Msg: msgFieldInclusion} + return fieldError("IndividualName", ErrConstructor, ed.IndividualName) } - if ed.TraceNumber == 0 { - return &FieldError{FieldName: "TraceNumber", Value: ed.TraceNumberField(), Msg: msgFieldInclusion} + if ed.TraceNumber == "" { + return fieldError("TraceNumber", ErrConstructor, ed.TraceNumberField()) } return nil } -// AddAddenda appends an EntryDetail to the Addendum -func (ed *EntryDetail) AddAddenda(addenda Addenda) []Addenda { - ed.AddendaRecordIndicator = 1 - // checks to make sure that we only have either or, not both - if ed.ReturnAddendum != nil { - return nil +func (ed *EntryDetail) amountOverflowsField() error { + intstr := strconv.Itoa(ed.Amount) + strstr := ed.AmountField() + if intstr == "0" && strstr == "0000000000" { + return nil // both are empty values } - ed.Addendum = append(ed.Addendum, addenda) - return ed.Addendum -} - -// AddReturnAddenda appends an ReturnAddendum to the entry -func (ed *EntryDetail) AddReturnAddenda(returnAddendum ReturnAddenda) []ReturnAddenda { - ed.AddendaRecordIndicator = 1 - // checks to make sure that we only have either or, not both - if ed.Addendum != nil { - return nil + if len(intstr) > len(strstr) { + return fmt.Errorf("does not match formatted value %s", strstr) } - ed.ReturnAddendum = append(ed.ReturnAddendum, returnAddendum) - return ed.ReturnAddendum + return nil } // SetRDFI takes the 9 digit RDFI account number and separates it for RDFIIdentification and CheckDigit -func (ed *EntryDetail) SetRDFI(rdfi int) *EntryDetail { - s := ed.numericField(rdfi, 9) - ed.RDFIIdentification = ed.parseNumField(s[:8]) - ed.CheckDigit = ed.parseNumField(s[8:9]) +func (ed *EntryDetail) SetRDFI(rdfi string) *EntryDetail { + s := ed.stringField(rdfi, 9) + ed.RDFIIdentification = ed.parseStringField(s[:8]) + ed.CheckDigit = ed.parseStringField(s[8:9]) return ed } -// setTraceNumber takes first 8 digits of RDFI and concatenates a sequence number onto the TraceNumber -func (ed *EntryDetail) setTraceNumber(RDFIIdentification int, seq int) { - trace := ed.numericField(RDFIIdentification, 8) + ed.numericField(seq, 7) - ed.TraceNumber = ed.parseNumField(trace) +// SetTraceNumber takes first 8 digits of ODFI and concatenates a sequence number onto the TraceNumber +func (ed *EntryDetail) SetTraceNumber(ODFIIdentification string, seq int) { + traceNumber := ed.stringField(ODFIIdentification, 8) + ed.numericField(seq, 7) + ed.TraceNumber = traceNumber + + // Populate TraceNumber of addenda records that should match the Entry's trace number + if ed.Addenda02 != nil { + ed.Addenda02.TraceNumber = traceNumber + } + if ed.Addenda98 != nil { + ed.Addenda98.TraceNumber = traceNumber + } + if ed.Addenda99 != nil { + ed.Addenda99.TraceNumber = traceNumber + } } // RDFIIdentificationField get the rdfiIdentification with zero padding func (ed *EntryDetail) RDFIIdentificationField() string { - return ed.numericField(ed.RDFIIdentification, 8) + return ed.stringField(ed.RDFIIdentification, 8) } // DFIAccountNumberField gets the DFIAccountNumber with space padding @@ -282,6 +378,90 @@ func (ed *EntryDetail) IdentificationNumberField() string { return ed.alphaField(ed.IdentificationNumber, 15) } +// CheckSerialNumberField is used in RCK, ARC, BOC files but returns +// a space padded string of the underlying IdentificationNumber field +func (ed *EntryDetail) CheckSerialNumberField() string { + return ed.alphaField(ed.IdentificationNumber, 15) +} + +// SetCheckSerialNumber setter for RCK, ARC, BOC CheckSerialNumber +// which is underlying IdentificationNumber +func (ed *EntryDetail) SetCheckSerialNumber(s string) { + ed.IdentificationNumber = s +} + +// SetPOPCheckSerialNumber setter for POP CheckSerialNumber +// which is characters 1-9 of underlying CheckSerialNumber \ IdentificationNumber +func (ed *EntryDetail) SetPOPCheckSerialNumber(s string) { + ed.IdentificationNumber = ed.alphaField(s, 9) +} + +// SetPOPTerminalCity setter for POP Terminal City +// which is characters 10-13 of underlying CheckSerialNumber \ IdentificationNumber +func (ed *EntryDetail) SetPOPTerminalCity(s string) { + ed.IdentificationNumber = ed.IdentificationNumber + ed.alphaField(s, 4) +} + +// SetPOPTerminalState setter for POP Terminal State +// which is characters 14-15 of underlying CheckSerialNumber \ IdentificationNumber +func (ed *EntryDetail) SetPOPTerminalState(s string) { + ed.IdentificationNumber = ed.IdentificationNumber + ed.alphaField(s, 2) +} + +// POPCheckSerialNumberField is used in POP, characters 1-9 of underlying BatchPOP +// CheckSerialNumber / IdentificationNumber +func (ed *EntryDetail) POPCheckSerialNumberField() string { + return ed.parseStringField(ed.IdentificationNumber[0:9]) +} + +// POPTerminalCityField is used in POP, characters 10-13 of underlying BatchPOP +// CheckSerialNumber / IdentificationNumber +func (ed *EntryDetail) POPTerminalCityField() string { + return ed.parseStringField(ed.IdentificationNumber[9:13]) +} + +// POPTerminalStateField is used in POP, characters 14-15 of underlying BatchPOP +// CheckSerialNumber / IdentificationNumber +func (ed *EntryDetail) POPTerminalStateField() string { + return ed.parseStringField(ed.IdentificationNumber[13:15]) +} + +// SetSHRCardExpirationDate format MMYY is used in SHR, characters 1-4 of underlying +// IdentificationNumber +func (ed *EntryDetail) SetSHRCardExpirationDate(s string) { + ed.IdentificationNumber = ed.alphaField(s, 4) +} + +// SetSHRDocumentReferenceNumber format int is used in SHR, characters 5-15 of underlying +// IdentificationNumber +func (ed *EntryDetail) SetSHRDocumentReferenceNumber(s string) { + ed.IdentificationNumber = ed.IdentificationNumber + ed.stringField(s, 11) +} + +// SetSHRIndividualCardAccountNumber format int is used in SHR, underlying +// IndividualName +func (ed *EntryDetail) SetSHRIndividualCardAccountNumber(s string) { + ed.IndividualName = ed.stringField(s, 22) +} + +// SHRCardExpirationDateField format MMYY is used in SHR, characters 1-4 of underlying +// IdentificationNumber +func (ed *EntryDetail) SHRCardExpirationDateField() string { + return ed.alphaField(ed.parseStringField(ed.IdentificationNumber[0:4]), 4) +} + +// SHRDocumentReferenceNumberField format int is used in SHR, characters 5-15 of underlying +// IdentificationNumber +func (ed *EntryDetail) SHRDocumentReferenceNumberField() string { + return ed.stringField(ed.IdentificationNumber[4:15], 11) +} + +// SHRIndividualCardAccountNumberField format int is used in SHR, underlying +// IndividualName +func (ed *EntryDetail) SHRIndividualCardAccountNumberField() string { + return ed.stringField(ed.IndividualName, 22) +} + // IndividualNameField returns a space padded string of IndividualName func (ed *EntryDetail) IndividualNameField() string { return ed.alphaField(ed.IndividualName, 22) @@ -292,25 +472,156 @@ func (ed *EntryDetail) ReceivingCompanyField() string { return ed.IndividualNameField() } +// SetReceivingCompany setter for CCD ReceivingCompany which is underlying IndividualName +func (ed *EntryDetail) SetReceivingCompany(s string) { + ed.IndividualName = s +} + +// OriginalTraceNumberField is used in ACK and ATX files but returns the underlying IdentificationNumber field +func (ed *EntryDetail) OriginalTraceNumberField() string { + return ed.IdentificationNumberField() +} + +// SetOriginalTraceNumber setter for ACK and ATX OriginalTraceNumber which is underlying IdentificationNumber +func (ed *EntryDetail) SetOriginalTraceNumber(s string) { + ed.IdentificationNumber = s +} + +// SetCATXAddendaRecords setter for CTX and ATX AddendaRecords characters 1-4 of underlying IndividualName +func (ed *EntryDetail) SetCATXAddendaRecords(i int) { + ed.IndividualName = ed.numericField(i, 4) +} + +// SetCATXReceivingCompany setter for CTX and ATX ReceivingCompany characters 5-20 underlying IndividualName +// Position 21-22 of underlying Individual Name are reserved blank space for CTX " " +func (ed *EntryDetail) SetCATXReceivingCompany(s string) { + ed.IndividualName = ed.IndividualName + ed.alphaField(s, 16) + " " +} + +// CATXAddendaRecordsField is used in CTX and ATX files, characters 1-4 of underlying IndividualName field +func (ed *EntryDetail) CATXAddendaRecordsField() string { + return ed.parseStringField(ed.IndividualName[0:4]) +} + +// CATXReceivingCompanyField is used in CTX and ATX files, characters 5-20 of underlying IndividualName field +func (ed *EntryDetail) CATXReceivingCompanyField() string { + return ed.parseStringField(ed.IndividualName[4:20]) +} + +// CATXReservedField is used in CTX and ATX files, characters 21-22 of underlying IndividualName field +func (ed *EntryDetail) CATXReservedField() string { + return ed.IndividualName[20:22] +} + // DiscretionaryDataField returns a space padded string of DiscretionaryData func (ed *EntryDetail) DiscretionaryDataField() string { return ed.alphaField(ed.DiscretionaryData, 2) } -// PaymentType returns the discretionary data field used in web batch files -func (ed *EntryDetail) PaymentType() string { - if ed.DiscretionaryData == "" { +// PaymentTypeField returns the DiscretionaryData field used in WEB and TEL batch files +func (ed *EntryDetail) PaymentTypeField() string { + // because DiscretionaryData can be changed outside of PaymentType we reset the value for safety + ed.SetPaymentType(ed.DiscretionaryData) + return ed.DiscretionaryData +} + +// SetPaymentType as R (Recurring) all other values will result in S (single). +// This is used for WEB and TEL batch files in-place of DiscretionaryData. +func (ed *EntryDetail) SetPaymentType(t string) { + t = strings.ToUpper(strings.TrimSpace(t)) + if t == "R" { + ed.DiscretionaryData = "R" + } else { ed.DiscretionaryData = "S" } - return ed.DiscretionaryDataField() } -// TraceNumberField returns a zero padded traceNumber string +// SetProcessControlField setter for TRC Process Control Field characters 1-6 of underlying IndividualName +func (ed *EntryDetail) SetProcessControlField(s string) { + ed.IndividualName = ed.alphaField(s, 6) +} + +// SetItemResearchNumber setter for TRC Item Research Number characters 7-22 of underlying IndividualName +func (ed *EntryDetail) SetItemResearchNumber(s string) { + ed.IndividualName = ed.IndividualName + ed.alphaField(s, 16) +} + +// SetItemTypeIndicator setter for TRC Item Type Indicator which is underlying Discretionary Data +func (ed *EntryDetail) SetItemTypeIndicator(s string) { + ed.DiscretionaryData = ed.alphaField(s, 2) +} + +// ProcessControlField getter for TRC Process Control Field characters 1-6 of underlying IndividualName +func (ed *EntryDetail) ProcessControlField() string { + return ed.parseStringField(ed.IndividualName[0:6]) +} + +// ItemResearchNumber getter for TRC Item Research Number characters 7-22 of underlying IndividualName +func (ed *EntryDetail) ItemResearchNumber() string { + return ed.parseStringField(ed.IndividualName[6:22]) +} + +// ItemTypeIndicator getter for TRC Item Type Indicator which is underlying Discretionary Data +func (ed *EntryDetail) ItemTypeIndicator() string { + return ed.DiscretionaryData +} + +// TraceNumberField returns a zero padded TraceNumber string func (ed *EntryDetail) TraceNumberField() string { - return ed.numericField(ed.TraceNumber, 15) + return ed.stringField(ed.TraceNumber, 15) +} + +// CreditOrDebit returns a "C" for credit or "D" for debit based on the entry TransactionCode +func (ed *EntryDetail) CreditOrDebit() string { + if ed.TransactionCode < 10 || ed.TransactionCode > 99 { + return "" + } + tc := strconv.Itoa(ed.TransactionCode) + + // take the second number in the TransactionCode + switch tc[1:2] { + case "1", "2", "3", "4": + return "C" + case "5", "6", "7", "8", "9": + return "D" + default: + } + return "" +} + +// AddAddenda05 appends an Addenda05 to the EntryDetail +func (ed *EntryDetail) AddAddenda05(addenda05 *Addenda05) { + ed.Addenda05 = append(ed.Addenda05, addenda05) +} + +// addendaCount returns the count of Addenda records added onto this EntryDetail +func (ed *EntryDetail) addendaCount() (n int) { + if ed.Addenda02 != nil { + n += 1 + } + for i := range ed.Addenda05 { + if ed.Addenda05[i] != nil { + n += 1 + } + } + if ed.Addenda98 != nil { + n += 1 + } + if ed.Addenda99 != nil { + n += 1 + } + if ed.Addenda99Dishonored != nil { + n += 1 + } + if ed.Addenda99Contested != nil { + n += 1 + } + return n } -// HasReturnAddenda returns true if entry has return addenda -func (ed *EntryDetail) HasReturnAddenda() bool { - return ed.ReturnAddendum != nil +func sortEntriesByTraceNumber(entries []*EntryDetail) []*EntryDetail { + sort.Slice(entries[:], func(i, j int) bool { + return entries[i].TraceNumber < entries[j].TraceNumber + }) + return entries } diff --git a/entryDetail_internal_test.go b/entryDetail_internal_test.go deleted file mode 100644 index 8789efafc..000000000 --- a/entryDetail_internal_test.go +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright 2017 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. - -package ach - -import ( - "strings" - "testing" -) - -func mockEntryDetail() *EntryDetail { - entry := NewEntryDetail() - entry.TransactionCode = 22 - entry.SetRDFI(9101298) - entry.DFIAccountNumber = "123456789" - entry.Amount = 100000000 - entry.IndividualName = "Wade Arnold" - entry.TraceNumber = 123456789 - return entry -} - -func TestMockEntryDetail(t *testing.T) { - entry := mockEntryDetail() - if err := entry.Validate(); err != nil { - t.Error("mockEntryDetail does not validate and will break other tests") - } - if entry.TransactionCode != 22 { - t.Error("TransactionCode dependent default value has changed") - } - if entry.DFIAccountNumber != "123456789" { - t.Error("DFIAccountNumber dependent default value has changed") - } - if entry.Amount != 100000000 { - t.Error("Amount dependent default value has changed") - } - if entry.IndividualName != "Wade Arnold" { - t.Error("IndividualName dependent default value has changed") - } - if entry.TraceNumber != 123456789 { - t.Error("TraceNumber dependent default value has changed") - } -} - -// TestParseEntryDetail Header parses a known Entry Detail Record string. -func TestParseEntryDetail(t *testing.T) { - var line = "62705320001912345 0000010500c-1 Arnold Wade DD0076401255655291" - r := NewReader(strings.NewReader(line)) - r.addCurrentBatch(NewBatchPPD()) - r.currentBatch.SetHeader(mockBatchHeader()) - r.line = line - if err := r.parseEntryDetail(); err != nil { - t.Errorf("%T: %s", err, err) - } - record := r.currentBatch.GetEntries()[0] - - if record.recordType != "6" { - t.Errorf("RecordType Expected '6' got: %v", record.recordType) - } - if record.TransactionCode != 27 { - t.Errorf("TransactionCode Expected '27' got: %v", record.TransactionCode) - } - if record.RDFIIdentificationField() != "05320001" { - t.Errorf("RDFIIdentification Expected '05320001' got: '%v'", record.RDFIIdentificationField()) - } - if record.CheckDigit != 9 { - t.Errorf("CheckDigit Expected '9' got: %v", record.CheckDigit) - } - if record.DFIAccountNumberField() != "12345 " { - t.Errorf("DfiAccountNumber Expected '12345 ' got: %v", record.DFIAccountNumberField()) - } - if record.AmountField() != "0000010500" { - t.Errorf("Amount Expected '0000010500' got: %v", record.AmountField()) - } - - if record.IdentificationNumber != "c-1 " { - t.Errorf("IdentificationNumber Expected 'c-1 ' got: %v", record.IdentificationNumber) - } - if record.IndividualName != "Arnold Wade " { - t.Errorf("IndividualName Expected 'Arnold Wade ' got: %v", record.IndividualName) - } - if record.DiscretionaryData != "DD" { - t.Errorf("DiscretionaryData Expected 'DD' got: %v", record.DiscretionaryData) - } - if record.AddendaRecordIndicator != 0 { - t.Errorf("AddendaRecordIndicator Expected '0' got: %v", record.AddendaRecordIndicator) - } - if record.TraceNumberField() != "076401255655291" { - t.Errorf("TraceNumber Expected '076401255655291' got: %v", record.TraceNumberField()) - } -} - -// TestEDString validats that a known parsed file can be return to a string of the same value -func TestEDString(t *testing.T) { - var line = "62705320001912345 0000010500c-1 Arnold Wade DD0076401255655291" - r := NewReader(strings.NewReader(line)) - r.addCurrentBatch(NewBatchPPD()) - r.currentBatch.SetHeader(mockBatchHeader()) - r.line = line - if err := r.parseEntryDetail(); err != nil { - t.Errorf("%T: %s", err, err) - } - record := r.currentBatch.GetEntries()[0] - - if record.String() != line { - t.Errorf("Strings do not match") - } -} - -// TestValidateEDRecordType ensure error if recordType is not 6 -func TestValidateEDRecordType(t *testing.T) { - ed := mockEntryDetail() - ed.recordType = "2" - if err := ed.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "recordType" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -// TestValidateEDTransactionCode ensure error if TransactionCode is not valid -func TestValidateEDTransactionCode(t *testing.T) { - ed := mockEntryDetail() - ed.TransactionCode = 63 - if err := ed.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "TransactionCode" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestEDFieldInclusion(t *testing.T) { - ed := mockEntryDetail() - ed.Amount = 0 - if err := ed.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestEDdfiAccountNumberAlphaNumeric(t *testing.T) { - ed := mockEntryDetail() - ed.DFIAccountNumber = "®" - if err := ed.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "DFIAccountNumber" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestEDIdentificationNumberAlphaNumeric(t *testing.T) { - ed := mockEntryDetail() - ed.IdentificationNumber = "®" - if err := ed.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "IdentificationNumber" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestEDIndividualNameAlphaNumeric(t *testing.T) { - ed := mockEntryDetail() - ed.IndividualName = "W®DE" - if err := ed.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "IndividualName" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestEDDiscretionaryDataAlphaNumeric(t *testing.T) { - ed := mockEntryDetail() - ed.DiscretionaryData = "®!" - if err := ed.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "DiscretionaryData" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestEDisCheckDigit(t *testing.T) { - ed := mockEntryDetail() - ed.CheckDigit = 1 - if err := ed.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "RDFIIdentification" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestEDSetRDFI(t *testing.T) { - ed := NewEntryDetail() - ed.SetRDFI(81086674) - if ed.RDFIIdentification != 8108667 { - t.Error("RDFI identification") - } - if ed.CheckDigit != 4 { - t.Error("Unexpected check digit") - } -} - -func TestEDFieldInclusionRecordType(t *testing.T) { - entry := mockEntryDetail() - entry.recordType = "" - if err := entry.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestEDFieldInclusionTransactionCode(t *testing.T) { - entry := mockEntryDetail() - entry.TransactionCode = 0 - if err := entry.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestEDFieldInclusionRDFIIdentification(t *testing.T) { - entry := mockEntryDetail() - entry.RDFIIdentification = 0 - if err := entry.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestEDFieldInclusionDFIAccountNumber(t *testing.T) { - entry := mockEntryDetail() - entry.DFIAccountNumber = "" - if err := entry.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestEDFieldInclusionIndividualName(t *testing.T) { - entry := mockEntryDetail() - entry.IndividualName = "" - if err := entry.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestEDFieldInclusionTraceNumber(t *testing.T) { - entry := mockEntryDetail() - entry.TraceNumber = 0 - if err := entry.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} diff --git a/entryDetail_test.go b/entryDetail_test.go new file mode 100644 index 000000000..6e51f882a --- /dev/null +++ b/entryDetail_test.go @@ -0,0 +1,725 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "bytes" + "encoding/json" + "fmt" + "math" + "path/filepath" + "strconv" + "strings" + "testing" + + "github.com/moov-io/base" +) + +// mockEntryDetail creates an entry detail +func mockEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("121042882") + entry.DFIAccountNumber = "123456789" + entry.Amount = 100000000 + entry.IndividualName = "Wade Arnold" + entry.SetTraceNumber(mockBatchHeader().ODFIIdentification, 1) + entry.IdentificationNumber = "ABC##jvkdjfuiwn" + entry.Category = CategoryForward + return entry +} + +// testMockEntryDetail validates an entry detail record +func testMockEntryDetail(t testing.TB) { + entry := mockEntryDetail() + if err := entry.Validate(); err != nil { + t.Error("mockEntryDetail does not validate and will break other tests") + } + if entry.TransactionCode != CheckingCredit { + t.Error("TransactionCode dependent default value has changed") + } + if entry.DFIAccountNumber != "123456789" { + t.Error("DFIAccountNumber dependent default value has changed") + } + if entry.Amount != 100000000 { + t.Error("Amount dependent default value has changed") + } + if entry.IndividualName != "Wade Arnold" { + t.Error("IndividualName dependent default value has changed") + } + if entry.TraceNumber != "121042880000001" { + t.Errorf("TraceNumber dependent default value has changed %v", entry.TraceNumber) + } +} + +// TestMockEntryDetail tests validating an entry detail record +func TestMockEntryDetail(t *testing.T) { + testMockEntryDetail(t) +} + +// BenchmarkMockEntryDetail benchmarks validating an entry detail record +func BenchmarkMockEntryDetail(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testMockEntryDetail(b) + } +} + +// testParseEntryDetail parses a known entry detail record string. +func testParseEntryDetail(t testing.TB) { + var line = "62705320001912345 0000010500c-1 Arnold Wade DD0076401255655291" + r := NewReader(strings.NewReader(line)) + r.addCurrentBatch(NewBatchPPD(mockBatchPPDHeader())) + r.currentBatch.SetHeader(mockBatchHeader()) + r.line = line + if err := r.parseEntryDetail(); err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.currentBatch.GetEntries()[0] + + if record.TransactionCode != CheckingDebit { + t.Errorf("TransactionCode Expected '27' got: %v", record.TransactionCode) + } + if record.RDFIIdentificationField() != "05320001" { + t.Errorf("RDFIIdentification Expected '05320001' got: '%v'", record.RDFIIdentificationField()) + } + if record.CheckDigit != "9" { + t.Errorf("CheckDigit Expected '9' got: %v", record.CheckDigit) + } + if record.DFIAccountNumberField() != "12345 " { + t.Errorf("DfiAccountNumber Expected '12345 ' got: %v", record.DFIAccountNumberField()) + } + if record.AmountField() != "0000010500" { + t.Errorf("Amount Expected '0000010500' got: %v", record.AmountField()) + } + + if record.IdentificationNumber != "c-1 " { + t.Errorf("IdentificationNumber Expected 'c-1 ' got: %v", record.IdentificationNumber) + } + if record.IndividualName != "Arnold Wade " { + t.Errorf("IndividualName Expected 'Arnold Wade ' got: %v", record.IndividualName) + } + if record.DiscretionaryData != "DD" { + t.Errorf("DiscretionaryData Expected 'DD' got: %v", record.DiscretionaryData) + } + if record.AddendaRecordIndicator != 0 { + t.Errorf("AddendaRecordIndicator Expected '0' got: %v", record.AddendaRecordIndicator) + } + if record.TraceNumberField() != "076401255655291" { + t.Errorf("TraceNumber Expected '076401255655291' got: %v", record.TraceNumberField()) + } +} + +// TestParseEntryDetail tests parsing a known entry detail record string. +func TestParseEntryDetail(t *testing.T) { + testParseEntryDetail(t) +} + +// BenchmarkParseEntryDetail benchmarks parsing a known entry detail record string. +func BenchmarkParseEntryDetail(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testParseEntryDetail(b) + } +} + +// testEDString validates that a known parsed entry +// detail can be returned to a string of the same value +func testEDString(t testing.TB) { + var line = "62705320001912345 0000010500c-1 Arnold Wade DD0076401255655291" + r := NewReader(strings.NewReader(line)) + r.addCurrentBatch(NewBatchPPD(mockBatchPPDHeader())) + r.currentBatch.SetHeader(mockBatchHeader()) + r.line = line + if err := r.parseEntryDetail(); err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.currentBatch.GetEntries()[0] + + if record.String() != line { + t.Errorf("Strings do not match") + } +} + +// TestEDString tests validating that a known parsed entry +// detail can be returned to a string of the same value +func TestEDString(t *testing.T) { + testEDString(t) +} + +// BenchmarkEDString benchmarks validating that a known parsed entry +// detail can be returned to a string of the same value +func BenchmarkEDString(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDString(b) + } +} + +// testValidateEDTransactionCode validates error if transaction code is not valid +func testValidateEDTransactionCode(t testing.TB) { + ed := mockEntryDetail() + ed.TransactionCode = 63 + err := ed.Validate() + if !base.Match(err, ErrTransactionCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestValidateEDTransactionCode tests validating error if transaction code is not valid +func TestValidateEDTransactionCode(t *testing.T) { + testValidateEDTransactionCode(t) +} + +// BenchmarkValidateEDTransactionCode benchmarks validating error if transaction code is not valid +func BenchmarkValidateEDTransactionCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testValidateEDTransactionCode(b) + } +} + +func TestEntryDetailAmountNegative(t *testing.T) { + ed := mockEntryDetail() + ed.Amount = -100 + if err := ed.Validate(); !base.Match(err, ErrNegativeAmount) { + t.Errorf("%T: %s", err, err) + } +} + +// testEDFieldInclusion validates entry detail field inclusion +func testEDFieldInclusion(t testing.TB) { + ed := mockEntryDetail() + ed.Amount = 0 + err := ed.Validate() + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestEDFieldInclusion tests validating entry detail field inclusion +func TestEDFieldInclusion(t *testing.T) { + testEDFieldInclusion(t) +} + +// BenchmarkEDFieldInclusion benchmarks validating entry detail field inclusion +func BenchmarkEDFieldInclusion(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDFieldInclusion(b) + } +} + +// testEDdfiAccountNumberAlphaNumeric validates DFI account number is alpha numeric +func testEDdfiAccountNumberAlphaNumeric(t testing.TB) { + ed := mockEntryDetail() + ed.DFIAccountNumber = "®" + err := ed.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestEDdfiAccountNumberAlphaNumeric tests validating DFI account number is alpha numeric +func TestEDdfiAccountNumberAlphaNumeric(t *testing.T) { + testEDdfiAccountNumberAlphaNumeric(t) +} + +// BenchmarkEDdfiAccountNumberAlphaNumeric benchmarks validating DFI account number is alpha numeric +func BenchmarkEDdfiAccountNumberAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDdfiAccountNumberAlphaNumeric(b) + } +} + +// testEDIdentificationNumberAlphaNumeric validates identification number is alpha numeric +func testEDIdentificationNumberAlphaNumeric(t testing.TB) { + ed := mockEntryDetail() + ed.IdentificationNumber = "®" + err := ed.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestEDIdentificationNumberAlphaNumeric tests validating identification number is alpha numeric +func TestEDIdentificationNumberAlphaNumeric(t *testing.T) { + testEDIdentificationNumberAlphaNumeric(t) +} + +// BenchmarkEDIdentificationNumberAlphaNumeric benchmarks validating identification number is alpha numeric +func BenchmarkEDIdentificationNumberAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDIdentificationNumberAlphaNumeric(b) + } +} + +// testEDIndividualNameAlphaNumeric validates individual name is alpha numeric +func testEDIndividualNameAlphaNumeric(t testing.TB) { + ed := mockEntryDetail() + ed.IndividualName = "W®DE" + err := ed.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestEDIndividualNameAlphaNumeric tests validating individual name is alpha numeric +func TestEDIndividualNameAlphaNumeric(t *testing.T) { + testEDIndividualNameAlphaNumeric(t) +} + +// BenchmarkEDIndividualNameAlphaNumeric benchmarks validating individual name is alpha numeric +func BenchmarkEDIndividualNameAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDIndividualNameAlphaNumeric(b) + } +} + +// testEDDiscretionaryDataAlphaNumeric validates discretionary data is alpha numeric +func testEDDiscretionaryDataAlphaNumeric(t testing.TB) { + ed := mockEntryDetail() + ed.DiscretionaryData = "®!" + err := ed.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestEDDiscretionaryDataAlphaNumeric tests validating discretionary data is alpha numeric +func TestEDDiscretionaryDataAlphaNumeric(t *testing.T) { + testEDDiscretionaryDataAlphaNumeric(t) +} + +// BenchmarkEDDiscretionaryDataAlphaNumeric benchmarks validating discretionary data is alpha numeric +func BenchmarkEDDiscretionaryDataAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDDiscretionaryDataAlphaNumeric(b) + } +} + +// testEDisCheckDigit validates check digit +func testEDisCheckDigit(t testing.TB) { + ed := mockEntryDetail() + ed.CheckDigit = "1" + err := ed.Validate() + if !base.Match(err, NewErrValidCheckDigit(7)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestEDisCheckDigit tests validating check digit +func TestEDisCheckDigit(t *testing.T) { + testEDisCheckDigit(t) +} + +// BenchmarkEDSetRDFI benchmarks validating check digit +func BenchmarkEDisCheckDigit(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDisCheckDigit(b) + } +} + +// testEDSetRDFI validates setting RDFI +func testEDSetRDFI(t testing.TB) { + ed := NewEntryDetail() + ed.SetRDFI("810866774") + if ed.RDFIIdentification != "81086677" { + t.Error("RDFI identification") + } + if ed.CheckDigit != "4" { + t.Error("Unexpected check digit") + } +} + +// TestEDSetRDFI tests validating setting RDFI +func TestEDSetRDFI(t *testing.T) { + testEDSetRDFI(t) +} + +// BenchmarkEDSetRDFI benchmarks validating setting RDFI +func BenchmarkEDSetRDFI(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDSetRDFI(b) + } +} + +// testEDFieldInclusionTransactionCode validates transaction code field inclusion +func testEDFieldInclusionTransactionCode(t testing.TB) { + entry := mockEntryDetail() + entry.TransactionCode = 0 + err := entry.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestEDFieldInclusionTransactionCode tests validating transaction code field inclusion +func TestEDFieldInclusionTransactionCode(t *testing.T) { + testEDFieldInclusionTransactionCode(t) +} + +// BenchmarkEDFieldInclusionTransactionCode benchmarks validating transaction code field inclusion +func BenchmarkEDFieldInclusionTransactionCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDFieldInclusionTransactionCode(b) + } +} + +// testEDFieldInclusionRDFIIdentification validates RDFI identification field inclusion +func testEDFieldInclusionRDFIIdentification(t testing.TB) { + entry := mockEntryDetail() + entry.RDFIIdentification = "" + err := entry.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestEDFieldInclusionRDFIIdentification tests validating RDFI identification field inclusion +func TestEDFieldInclusionRDFIIdentification(t *testing.T) { + testEDFieldInclusionRDFIIdentification(t) +} + +// BenchmarkEDFieldInclusionRDFIIdentification benchmarks validating RDFI identification field inclusion +func BenchmarkEDFieldInclusionRDFIIdentification(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDFieldInclusionRDFIIdentification(b) + } +} + +// testEDFieldInclusionDFIAccountNumber validates DFI account number field inclusion +func testEDFieldInclusionDFIAccountNumber(t testing.TB) { + entry := mockEntryDetail() + entry.DFIAccountNumber = "" + err := entry.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestEDFieldInclusionDFIAccountNumber tests validating DFI account number field inclusion +func TestEDFieldInclusionDFIAccountNumber(t *testing.T) { + testEDFieldInclusionDFIAccountNumber(t) +} + +// BenchmarkEDFieldInclusionDFIAccountNumber benchmarks validating DFI account number field inclusion +func BenchmarkEDFieldInclusionDFIAccountNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDFieldInclusionDFIAccountNumber(b) + } +} + +// testEDFieldInclusionIndividualName validates individual name field inclusion +func testEDFieldInclusionIndividualName(t testing.TB) { + entry := mockEntryDetail() + entry.IndividualName = "" + err := entry.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestEDFieldInclusionIndividualName tests validating individual name field inclusion +func TestEDFieldInclusionIndividualName(t *testing.T) { + testEDFieldInclusionIndividualName(t) +} + +// BenchmarkEDFieldInclusionIndividualName benchmarks validating individual name field inclusion +func BenchmarkEDFieldInclusionIndividualName(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDFieldInclusionIndividualName(b) + } +} + +// testEDFieldInclusionTraceNumber validates trace number field inclusion +func testEDFieldInclusionTraceNumber(t testing.TB) { + entry := mockEntryDetail() + entry.TraceNumber = "0" + err := entry.Validate() + // TODO: are we expecting to see no error here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestEDFieldInclusionTraceNumber tests validating trace number field inclusion +func TestEDFieldInclusionTraceNumber(t *testing.T) { + testEDFieldInclusionTraceNumber(t) +} + +// BenchmarkEDFieldInclusionTraceNumber benchmarks validating trace number field inclusion +func BenchmarkEDFieldInclusionTraceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDFieldInclusionTraceNumber(b) + } +} + +// testEDAddAddenda99 validates adding Addenda99 to an entry detail +func testEDAddAddenda99(t testing.TB) { + entry := mockEntryDetail() + entry.Addenda99 = mockAddenda99() + entry.Category = CategoryReturn + entry.AddendaRecordIndicator = 1 + if entry.Category != CategoryReturn { + t.Error("Addenda99 added and isReturn is false") + } + if entry.AddendaRecordIndicator != 1 { + t.Error("Addenda99 added and record indicator is not 1") + } + +} + +// TestEDAddAddenda99 tests validating adding Addenda99 to an entry detail +func TestEDAddAddenda99(t *testing.T) { + testEDAddAddenda99(t) +} + +// BenchmarkEDAddAddenda99 benchmarks validating adding Addenda99 to an entry detail +func BenchmarkEDAddAddenda99(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDAddAddenda99(b) + } +} + +// testEDAddAddenda99Twice validates only one Addenda99 is added to an entry detail +func testEDAddAddenda99Twice(t testing.TB) { + entry := mockEntryDetail() + entry.Addenda99 = mockAddenda99() + entry.Addenda99 = mockAddenda99() + entry.Category = CategoryReturn + if entry.Category != CategoryReturn { + t.Error("Addenda99 added and Category is not CategoryReturn") + } +} + +// TestEDAddAddenda99Twice tests validating only one Addenda99 is added to an entry detail +func TestEDAddAddenda99Twice(t *testing.T) { + testEDAddAddenda99Twice(t) +} + +// BenchmarkEDAddAddenda99Twice benchmarks validating only one Addenda99 is added to an entry detail +func BenchmarkEDAddAddenda99Twice(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDAddAddenda99Twice(b) + } +} + +// testEDCreditOrDebit validates debit and credit transaction code +func testEDCreditOrDebit(t testing.TB) { + entry := mockEntryDetail() + if entry.CreditOrDebit() != "C" { // our mock's default + t.Errorf("TransactionCode %v expected a Credit(C) got %v", entry.TransactionCode, entry.CreditOrDebit()) + } + + // TransactionCode -> C or D + var cases = map[int]string{ + // invalid + -1: "", + 00: "", // invalid + 1: "", + 108: "", + // valid + 22: "C", + 23: "C", + 27: "D", + 28: "D", + 32: "C", + 33: "C", + 37: "D", + 38: "D", + } + for code, expected := range cases { + entry.TransactionCode = code + if v := entry.CreditOrDebit(); v != expected { + t.Errorf("TransactionCode %d expected %s, got %s", code, expected, v) + } + } +} + +// TestEDCreditOrDebit tests validating debit and credit transaction code +func TestEDCreditOrDebit(t *testing.T) { + testEDCreditOrDebit(t) +} + +// BenchmarkEDCreditOrDebit benchmarks validating debit and credit transaction code +func BenchmarkEDCreditOrDebit(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDCreditOrDebit(b) + } +} + +// testValidateEDCheckDigit validates CheckDigit error +func testValidateEDCheckDigit(t testing.TB) { + ed := mockEntryDetail() + ed.CheckDigit = "XYZ" + err := ed.Validate() + if !base.Match(err, &strconv.NumError{}) { + t.Errorf("%T: %s", err, err) + } +} + +// TestValidateEDCheckDigit tests validating validates CheckDigit error +func TestValidateEDCheckDigit(t *testing.T) { + testValidateEDCheckDigit(t) +} + +// BenchmarkValidateEDCheckDigit benchmarks validating CheckDigit error +func BenchmarkValidateEDCheckDigit(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testValidateEDCheckDigit(b) + } +} + +func TestEntryDetail__CategoryJSON(t *testing.T) { + ed := mockEntryDetail() + + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(ed); err != nil { + t.Fatal(err) + } + if !strings.Contains(buf.String(), `"category":"Forward"`) { + t.Error(buf.String()) + } + buf.Reset() + + // read our return file and marshal + file, err := readACHFilepath(filepath.Join("test", "testdata", "return-WEB.ach")) + if err != nil { + t.Fatal(err) + } + if n := len(file.ReturnEntries); n != 2 { + t.Errorf("got %d ReturnEntries", n) + } + if err := json.NewEncoder(&buf).Encode(file); err != nil { + t.Fatal(err) + } + // There are two ReturnEntries and two Batches + if n := strings.Count(buf.String(), `"category":"Return"`); n != 4 { + // return-WEB.ach has two EntryDetail records + t.Errorf("got %d category:Return\n%s", n, buf.String()) + } + buf.Reset() + + // COR / Notification of Change + file, err = readACHFilepath(filepath.Join("test", "testdata", "cor-example.ach")) + if err != nil { + t.Fatal(err) + } + if err := json.NewEncoder(&buf).Encode(file); err != nil { + t.Fatal(err) + } + if n := len(file.ReturnEntries); n != 0 { + t.Errorf("got %d ReturnEntries", n) + } + if n := len(file.NotificationOfChange); n != 1 { + t.Errorf("got %d NotificationOfChange", n) + } + // one NOC entry in NotificationOfChange and one in Batches + if n := strings.Count(buf.String(), `"category":"NOC"`); n != 2 { + // return-WEB.ach has two EntryDetail records + t.Errorf("got %d category:NOC\n%s", n, buf.String()) + } +} + +func TestEntryDetail__ParseReturn(t *testing.T) { + file, err := readACHFilepath(filepath.Join("test", "testdata", "return-WEB.ach")) + if err != nil { + t.Fatal(err) + } + if n := len(file.Batches); n != 2 { + t.Errorf("got %d batches: %#v", n, file.Batches) + } + for i := range file.Batches { + entries := file.Batches[i].GetEntries() + if n := len(entries); n != 1 { + t.Errorf("got %d EntryDetail records: %#v", n, entries) + } + for j := range entries { + if entries[j].Category != CategoryReturn { + t.Errorf("EntryDetail.Category=%s\n %#v", entries[j].Category, entries[j]) + } + } + } +} + +func TestEntryDetail__ParseNOC(t *testing.T) { + file, err := readACHFilepath(filepath.Join("test", "testdata", "cor-example.ach")) + if err != nil { + t.Fatal(err) + } + if n := len(file.Batches); n != 1 { + t.Errorf("got %d batches: %#v", n, file.Batches) + } + + entries := file.Batches[0].GetEntries() + if n := len(entries); n != 1 { + t.Errorf("got %d EntryDetail records: %#v", n, entries) + } + if entries[0].Category != CategoryNOC { + t.Errorf("EntryDetail.Category=%s\n %#v", entries[0].Category, entries[0]) + } +} + +func TestEntryDetail__SetValidation(t *testing.T) { + ed := mockEntryDetail() + ed.SetValidation(&ValidateOpts{ + CheckTransactionCode: func(code int) error { + if code == 999 { + return nil + } + return fmt.Errorf("unexpected TransactionCode: %d", code) + }, + }) + ed.TransactionCode = 999 + if err := ed.Validate(); err != nil { + t.Fatal(err) + } + + // nil out + ed = nil + ed.SetValidation(&ValidateOpts{}) +} + +func TestEntryDetail__LargeAmountStrings(t *testing.T) { + ed := mockEntryDetail() + ed.Amount = math.MaxInt64 + + if err := ed.Validate(); err == nil { + t.Error("expected error") + } else { + if !strings.Contains(err.Error(), "Amount 9223372036854775807 does not match formatted value 6854775807") { + t.Errorf("unexpected error: %v", err) + } + } +} diff --git a/example/ach-ppd-read/main.go b/example/ach-ppd-read/main.go deleted file mode 100644 index 72a70df9c..000000000 --- a/example/ach-ppd-read/main.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - - "github.com/moov-io/ach" -) - -func main() { - // open a file for reading. Any io.Reader Can be used - f, err := os.Open("ppd-debit.ach") - if err != nil { - log.Panicf("Can not open file: %s: \n", err) - } - r := ach.NewReader(f) - _, err = r.Read() - if err != nil { - fmt.Printf("Issue reading file: %+v \n", err) - } - // ensure we have a validated file structure - if r.File.Validate(); err != nil { - fmt.Printf("Could not validate entire read file: %v", err) - } - // If you trust the file but it's formating is off building will probably resolve the malformed file. - if r.File.Create(); err != nil { - fmt.Printf("Could not build file with read properties: %v", err) - } - - fmt.Printf("total amount debit: %v \n", r.File.Control.TotalDebitEntryDollarAmountInFile) -} diff --git a/example/ach-ppd-read/main_test.go b/example/ach-ppd-read/main_test.go deleted file mode 100644 index 45fcc779c..000000000 --- a/example/ach-ppd-read/main_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "testing" - -func Test(t *testing.T) { - main() -} diff --git a/example/ach-ppd-read/ppd-debit.ach b/example/ach-ppd-read/ppd-debit.ach deleted file mode 100644 index aae128d4e..000000000 --- a/example/ach-ppd-read/ppd-debit.ach +++ /dev/null @@ -1,5 +0,0 @@ -101 076401251 0764012510807291511A094101achdestname companyname -5225companyname origid PPDCHECKPAYMT000002080730 1076401250000001 -62705320001912345 0000010500c-1 Bachman Eric DD0076401255655291 -82250000010005320001000000010500000000000000origid 076401250000001 -9000001000001000000010005320001000000010500000000000000 \ No newline at end of file diff --git a/example/ach-ppd-write/main.go b/example/ach-ppd-write/main.go deleted file mode 100644 index 2fd6c3f66..000000000 --- a/example/ach-ppd-write/main.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - "log" - "os" - "strconv" - "time" - - "github.com/moov-io/ach" -) - -func main() { - // Example transfer to write an ACH PPD file to send/credit a external institutions account - // Important: All financial instituions are different and will require registration and exact field values. - - // Set originator bank ODFI and destination Operator for the financial institution - // this is the funding/recieving source of the transfer - fh := ach.NewFileHeader() - fh.ImmediateDestination = 9876543210 // A blank space followed by your ODFI's transit/routing numbe - fh.ImmediateOrigin = 1234567890 // Organization or Company FED ID usually 1 and FEIN/SSN. Assigned by your ODFI - fh.FileCreationDate = time.Now() // Todays Date - fh.ImmediateDestinationName = "Federal Reserve Bank" - fh.ImmediateOriginName = "My Bank Name" - - // BatchHeader identifies the originating entity and the type of transactions contained in the batch - bh := ach.NewBatchHeader() - bh.ServiceClassCode = 220 // ACH credit pushes money out, 225 debits/pulls money in. - bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with reciever - bh.CompanyIdentification = strconv.Itoa(fh.ImmediateOrigin) - bh.StandardEntryClassCode = "PPD" // Consumer destination vs Company CCD - bh.CompanyEntryDescription = "REG.SALARY" // will be on recieving accounts statement - bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1) - bh.ODFIIdentification = 12345678 // first 8 digits of your bank account - - // Identifies the recievers account information - // can be multiple entrys per batch - entry := ach.NewEntryDetail() - // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan - entry.TransactionCode = 22 // Code 22: Credit (deposit) to checking account - entry.SetRDFI(9101298) // Recievers bank transit rounting number - entry.DFIAccountNumber = "12345678" // Recievers bank account number - entry.Amount = 100000000 // Amount of transaction with no decimil. One dollar and eleven cents = 111 - entry.IndividualName = "Reciever Account Name" // Identifies the reciever of the transaction - - // build the batch - batch := ach.NewBatchPPD() - batch.SetHeader(bh) - batch.AddEntry(entry) - if err := batch.Create(); err != nil { - log.Fatalf("Unexpected error building batch: %s\n", err) - } - - // build the file - file := ach.NewFile() - file.SetHeader(fh) - file.AddBatch(batch) - if err := file.Create(); err != nil { - log.Fatalf("Unexpected error building file: %s\n", err) - } - - // write the file to std out. Anything io.Writer - w := ach.NewWriter(os.Stdout) - if err := w.WriteAll([]*ach.File{file}); err != nil { - log.Fatalf("Unexpected error: %s\n", err) - } - w.Flush() -} diff --git a/example/ach-ppd-write/main_test.go b/example/ach-ppd-write/main_test.go deleted file mode 100644 index 45fcc779c..000000000 --- a/example/ach-ppd-write/main_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "testing" - -func Test(t *testing.T) { - main() -} diff --git a/example/simple-file-creation/main.go b/example/simple-file-creation/main.go deleted file mode 100644 index 7ec754f24..000000000 --- a/example/simple-file-creation/main.go +++ /dev/null @@ -1,108 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/moov-io/ach" -) - -func main() { - // To create a file - file := ach.NewFile(ach.FileParam{ - ImmediateDestination: "0210000890", - ImmediateOrigin: "123456789", - ImmediateDestinationName: "Your Bank", - ImmediateOriginName: "Your Company", - ReferenceCode: "#00000A1"}) - - // To create a batch. - // Errors only if payment type is not supported. - batch, _ := ach.NewBatch(ach.BatchParam{ - ServiceClassCode: "200", - CompanyName: "Your Company", - StandardEntryClass: "PPD", - CompanyIdentification: "123456789", - CompanyEntryDescription: "Trans. Description", - CompanyDescriptiveDate: "Oct 23", - ODFIIdentification: "123456789"}) - - // To create an entry - entry := ach.NewEntryDetail(ach.EntryParam{ - ReceivingDFI: "102001017", - RDFIAccount: "5343121", - Amount: "17500", - TransactionCode: "27", - IDNumber: "#456789", - IndividualName: "Bob Smith", - DiscretionaryData: "B1"}) - - // To add one or more optional addenda records for an entry - addenda := ach.NewAddenda(ach.AddendaParam{ - PaymentRelatedInfo: "bonus pay for amazing work on #OSS"}) - entry.AddAddenda(addenda) - - // Entries are added to batches like so: - - batch.AddEntry(entry) - - // When all of the Entries are added to the batch we must create it. - - if err := batch.Create(); err != nil { - fmt.Printf("%T: %s", err, err) - } - - // And batches are added to files much the same way: - - file.AddBatch(batch) - - // Now add a new batch for accepting payments on the web - - batch2, _ := ach.NewBatch(ach.BatchParam{ - ServiceClassCode: "220", - CompanyName: "Your Company", - StandardEntryClass: "WEB", - CompanyIdentification: "123456789", - CompanyEntryDescription: "subscr", - CompanyDescriptiveDate: "Oct 23", - ODFIIdentification: "123456789"}) - - // Add an entry and define if it is a single or reoccuring payment - // The following is a reoccuring payment for $7.99 - - entry2 := ach.NewEntryDetail(ach.EntryParam{ - ReceivingDFI: "102001017", - RDFIAccount: "5343121", - Amount: "799", - TransactionCode: "22", - IDNumber: "#123456", - IndividualName: "Wade Arnold", - PaymentType: "R"}) - - addenda2 := ach.NewAddenda(ach.AddendaParam{ - PaymentRelatedInfo: "Monthly Membership Subscription"}) - - // add the entry to the batch - entry2.AddAddenda(addenda2) - - // Create and add the second batch - - batch2.AddEntry(entry2) - if err := batch2.Create(); err != nil { - fmt.Printf("%T: %s", err, err) - } - file.AddBatch(batch2) - - // Once we added all our batches we must create the file - - if err := file.Create(); err != nil { - fmt.Printf("%T: %s", err, err) - } - - // Finally we wnt to write the file to an io.Writer - w := ach.NewWriter(os.Stdout) - if err := w.Write(file); err != nil { - fmt.Printf("%T: %s", err, err) - } - w.Flush() -} diff --git a/example/simple-file-creation/main_test.go b/example/simple-file-creation/main_test.go deleted file mode 100644 index 45fcc779c..000000000 --- a/example/simple-file-creation/main_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "testing" - -func Test(t *testing.T) { - main() -} diff --git a/examples/example_ackRead_test.go b/examples/example_ackRead_test.go new file mode 100644 index 000000000..85047f397 --- /dev/null +++ b/examples/example_ackRead_test.go @@ -0,0 +1,59 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_ackRead() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "ack-read.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Credit Total Amount: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("Batch Credit Total Amount: %d\n", achFile.Batches[0].GetEntries()[0].Amount) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Original Trace Number: %s\n", achFile.Batches[0].GetEntries()[0].OriginalTraceNumberField()) + + // Output: + // Credit Total Amount: 0 + // Batch Credit Total Amount: 0 + // SEC Code: ACK + // Original Trace Number: 031300010000001 +} diff --git a/examples/example_ackWrite_test.go b/examples/example_ackWrite_test.go new file mode 100644 index 000000000..ac85d7162 --- /dev/null +++ b/examples/example_ackWrite_test.go @@ -0,0 +1,87 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +func Example_ackWrite() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.CreditsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.ACK + bh.CompanyEntryDescription = "Vndr Pay" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "23138010" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingZeroDollarRemittanceCredit + entry.SetRDFI("031300012") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.SetOriginalTraceNumber("031300010000001") + entry.SetReceivingCompany("Best. #1") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + + entryOne := ach.NewEntryDetail() // Fee Entry + entryOne.TransactionCode = ach.CheckingZeroDollarRemittanceCredit + entryOne.SetRDFI("031300012") + entryOne.DFIAccountNumber = "744-5678-99" + entryOne.Amount = 0 + entryOne.SetOriginalTraceNumber("031300010000002") + entryOne.SetReceivingCompany("Best. #1") + entryOne.SetTraceNumber(bh.ODFIIdentification, 2) + + // build the batch + batch := ach.NewBatchACK(bh) + batch.AddEntry(entry) + batch.AddEntry(entryOne) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetEntries()[1].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5220Name on Account 231380104 ACKVndr Pay 190816 1231380100000001 + // 624031300012744-5678-99 0000000000031300010000001Best. #1 0231380100000001 + // 624031300012744-5678-99 0000000000031300010000002Best. #1 0231380100000002 + // 82200000020006260002000000000000000000000000231380104 231380100000001 + // 9000001000001000000020006260002000000000000000000000000 +} diff --git a/examples/example_advRead_test.go b/examples/example_advRead_test.go new file mode 100644 index 000000000..bc3e2c560 --- /dev/null +++ b/examples/example_advRead_test.go @@ -0,0 +1,71 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_advRead() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "adv-read.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Credit Total Amount: %d\n", achFile.ADVControl.TotalCreditEntryDollarAmountInFile) + fmt.Printf("Debit Total Amount: %d\n", achFile.ADVControl.TotalDebitEntryDollarAmountInFile) + fmt.Printf("OriginatorStatusCode: %d\n", achFile.Batches[0].GetHeader().OriginatorStatusCode) + fmt.Printf("Batch Credit Total Amount: %d\n", achFile.Batches[0].GetADVControl().TotalCreditEntryDollarAmount) + fmt.Printf("Batch Debit Total Amount: %d\n", achFile.Batches[0].GetADVControl().TotalDebitEntryDollarAmount) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Entry Amount: %d\n", achFile.Batches[0].GetADVEntries()[0].Amount) + fmt.Printf("Sequence Number: %d\n", achFile.Batches[0].GetADVEntries()[0].SequenceNumber) + fmt.Printf("EntryOne Amount: %d\n", achFile.Batches[0].GetADVEntries()[1].Amount) + fmt.Printf("EntryOne Sequence Number: %d\n", achFile.Batches[0].GetADVEntries()[1].SequenceNumber) + + // Output: + // Credit Total Amount: 50000 + // Debit Total Amount: 250000 + // OriginatorStatusCode: 0 + // Batch Credit Total Amount: 50000 + // Batch Debit Total Amount: 250000 + // SEC Code: ADV + // Entry Amount: 50000 + // Sequence Number: 1 + // EntryOne Amount: 250000 + // EntryOne Sequence Number: 2 +} diff --git a/examples/example_advWrite_test.go b/examples/example_advWrite_test.go new file mode 100644 index 000000000..b229f3505 --- /dev/null +++ b/examples/example_advWrite_test.go @@ -0,0 +1,100 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +func Example_advWrite() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.AutomatedAccountingAdvices + bh.CompanyName = "Company Name, Inc" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.ADV + bh.CompanyEntryDescription = "Accounting" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "121042882" + bh.OriginatorStatusCode = 0 + + entry := ach.NewADVEntryDetail() + entry.TransactionCode = ach.CreditForDebitsOriginated + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 50000 + entry.AdviceRoutingNumber = "121042882" + entry.FileIdentification = "11131" + entry.ACHOperatorData = "" + entry.IndividualName = "Name" + entry.DiscretionaryData = "" + entry.AddendaRecordIndicator = 0 + entry.ACHOperatorRoutingNumber = "01100001" + entry.JulianDay = 50 + entry.SequenceNumber = 1 + + entryOne := ach.NewADVEntryDetail() + entryOne.TransactionCode = ach.DebitForCreditsOriginated + entryOne.SetRDFI("231380104") + entryOne.DFIAccountNumber = "744-5678-99" + entryOne.Amount = 250000 + entryOne.AdviceRoutingNumber = "121042882" + entryOne.FileIdentification = "11139" + entryOne.ACHOperatorData = "" + entryOne.IndividualName = "Name" + entryOne.DiscretionaryData = "" + entryOne.AddendaRecordIndicator = 0 + entryOne.ACHOperatorRoutingNumber = "01100001" + entryOne.JulianDay = 50 + entryOne.SequenceNumber = 2 + + // build the batch + batch := ach.NewBatchADV(bh) + batch.AddADVEntry(entry) + batch.AddADVEntry(entryOne) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetADVEntries()[0].String()) + fmt.Println(file.Batches[0].GetADVEntries()[1].String()) + fmt.Println(file.Batches[0].GetADVControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5280Company Name, In 231380104 ADVAccounting 190816 0121042880000001 + // 681231380104744-5678-99 00000005000012104288211131 Name 0011000010500001 + // 682231380104744-5678-99 00000025000012104288211139 Name 0011000010500002 + // 828000000200462760200000000000000025000000000000000000050000Company Name, Inc 121042880000001 + // 9000000000000000000000000000000000000000000000000000000 +} diff --git a/examples/example_arcRead_debit_test.go b/examples/example_arcRead_debit_test.go new file mode 100644 index 000000000..69f316907 --- /dev/null +++ b/examples/example_arcRead_debit_test.go @@ -0,0 +1,57 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_arcReadDebit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "arc-debit.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Check Serial Number: %s\n", achFile.Batches[0].GetEntries()[0].IdentificationNumber) + + // Output: + // Total Amount Debit: 250000 + // SEC Code: ARC + // Check Serial Number: 123879654 +} diff --git a/examples/example_arcWrite_debit_test.go b/examples/example_arcWrite_debit_test.go new file mode 100644 index 000000000..d7811678a --- /dev/null +++ b/examples/example_arcWrite_debit_test.go @@ -0,0 +1,75 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +func Example_arcWriteDebit() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.ARC + bh.CompanyEntryDescription = "ACH ARC" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "121042882" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "12345678" + entry.Amount = 250000 + entry.SetCheckSerialNumber("123879654") + entry.SetReceivingCompany("ABC Company") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + + // build the batch + batch := ach.NewBatchARC(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5225Payee Name 231380104 ARCACH ARC 190816 1121042880000001 + // 62723138010412345678 0000250000123879654 ABC Company 0121042880000001 + // 82250000010023138010000000250000000000000000231380104 121042880000001 + // 9000001000001000000010023138010000000250000000000000000 +} diff --git a/examples/example_atxRead_test.go b/examples/example_atxRead_test.go new file mode 100644 index 000000000..ef352773e --- /dev/null +++ b/examples/example_atxRead_test.go @@ -0,0 +1,73 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_atxRead() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "atx-read.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("Total Amount Credit: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Total Amount: %d\n", achFile.Batches[0].GetEntries()[0].Amount) + fmt.Printf("Original Trace Number: %s\n", achFile.Batches[0].GetEntries()[0].OriginalTraceNumberField()) + fmt.Printf("Addenda1: %s\n", achFile.Batches[0].GetEntries()[0].Addenda05[0].String()) + fmt.Printf("Addenda2: %s\n", achFile.Batches[0].GetEntries()[0].Addenda05[1].String()) + fmt.Printf("Total Amount: %d\n", achFile.Batches[0].GetEntries()[1].Amount) + fmt.Printf("Original Trace Number: %s\n", achFile.Batches[0].GetEntries()[1].OriginalTraceNumberField()) + fmt.Printf("Addenda1: %s\n", achFile.Batches[0].GetEntries()[1].Addenda05[0].String()) + fmt.Printf("Addenda2: %s\n", achFile.Batches[0].GetEntries()[1].Addenda05[1].String()) + + // Output: + // Total Amount Debit: 0 + // Total Amount Credit: 0 + // SEC Code: ATX + // Total Amount: 0 + // Original Trace Number: 031300010000001 + // Addenda1: 705Credit account 1 for service 00010000001 + // Addenda2: 705Credit account 2 for service 00020000001 + // Total Amount: 0 + // Original Trace Number: 031300010000002 + // Addenda1: 705Credit account 1 for leadership 00010000002 + // Addenda2: 705Credit account 2 for leadership 00020000002 +} diff --git a/examples/example_atxWrite_test.go b/examples/example_atxWrite_test.go new file mode 100644 index 000000000..270f0195b --- /dev/null +++ b/examples/example_atxWrite_test.go @@ -0,0 +1,121 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +func Example_atxWrite() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.CreditsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.ATX + bh.CompanyEntryDescription = "Vndr Pay" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "23138010" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingZeroDollarRemittanceCredit + entry.SetRDFI("031300012") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.SetOriginalTraceNumber("031300010000001") + entry.SetCATXAddendaRecords(2) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.AddendaRecordIndicator = 1 + + entryOne := ach.NewEntryDetail() // Fee Entry + entryOne.TransactionCode = ach.CheckingZeroDollarRemittanceCredit + entryOne.SetRDFI("031300012") + entryOne.DFIAccountNumber = "744-5678-99" + entryOne.Amount = 0 + entryOne.SetOriginalTraceNumber("031300010000002") + entryOne.SetCATXAddendaRecords(2) + entryOne.SetCATXReceivingCompany("Receiver Company") + entryOne.SetTraceNumber(bh.ODFIIdentification, 2) + entryOne.DiscretionaryData = "01" + entryOne.AddendaRecordIndicator = 1 + + entryAd1 := ach.NewAddenda05() + entryAd1.PaymentRelatedInformation = "Credit account 1 for service" + entryAd1.SequenceNumber = 1 + entryAd1.EntryDetailSequenceNumber = 0000001 + + entryAd2 := ach.NewAddenda05() + entryAd2.PaymentRelatedInformation = "Credit account 2 for service" + entryAd2.SequenceNumber = 2 + entryAd2.EntryDetailSequenceNumber = 0000001 + + entryOneAd1 := ach.NewAddenda05() + entryOneAd1.PaymentRelatedInformation = "Credit account 1 for leadership" + entryOneAd1.SequenceNumber = 1 + entryOneAd1.EntryDetailSequenceNumber = 0000002 + + entryOneAd2 := ach.NewAddenda05() + entryOneAd2.PaymentRelatedInformation = "Credit account 2 for leadership" + entryOneAd2.SequenceNumber = 2 + entryOneAd2.EntryDetailSequenceNumber = 0000002 + + // build the batch + batch := ach.NewBatchATX(bh) + batch.AddEntry(entry) + batch.GetEntries()[0].AddAddenda05(entryAd1) + batch.GetEntries()[0].AddAddenda05(entryAd2) + batch.AddEntry(entryOne) + batch.GetEntries()[1].AddAddenda05(entryOneAd1) + batch.GetEntries()[1].AddAddenda05(entryOneAd2) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetEntries()[0].Addenda05[0].String()) + fmt.Println(file.Batches[0].GetEntries()[1].String()) + fmt.Println(file.Batches[0].GetEntries()[1].Addenda05[0].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5220Name on Account 231380104 ATXVndr Pay 190816 1231380100000001 + // 624031300012744-5678-99 00000000000313000100000010002Receiver Company 011231380100000001 + // 705Credit account 1 for service 00010000001 + // 624031300012744-5678-99 00000000000313000100000020002Receiver Company 011231380100000002 + // 705Credit account 1 for leadership 00010000002 + // 82200000060006260002000000000000000000000000231380104 231380100000001 + // 9000001000001000000060006260002000000000000000000000000 +} diff --git a/examples/example_bocRead_debit_test.go b/examples/example_bocRead_debit_test.go new file mode 100644 index 000000000..adeb201da --- /dev/null +++ b/examples/example_bocRead_debit_test.go @@ -0,0 +1,56 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_bocReadDebit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "boc-debit.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Check Serial Number: %s\n", achFile.Batches[0].GetEntries()[0].IdentificationNumber) + + // Output: Total Amount Debit: 250000 + // SEC Code: BOC + // Check Serial Number: 123879654 +} diff --git a/examples/example_bocWrite_debit_test.go b/examples/example_bocWrite_debit_test.go new file mode 100644 index 000000000..52110714e --- /dev/null +++ b/examples/example_bocWrite_debit_test.go @@ -0,0 +1,75 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +func Example_bocWriteDebit() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.BOC + bh.CompanyEntryDescription = "ACH BOC" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "121042882" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "12345678" + entry.Amount = 250000 + entry.SetCheckSerialNumber("123879654") + entry.SetReceivingCompany("ABC Company") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + + // build the batch + batch := ach.NewBatchBOC(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5225Payee Name 231380104 BOCACH BOC 190816 1121042880000001 + // 62723138010412345678 0000250000123879654 ABC Company 0121042880000001 + // 82250000010023138010000000250000000000000000231380104 121042880000001 + // 9000001000001000000010023138010000000250000000000000000 +} diff --git a/examples/example_ccdRead_debit_test.go b/examples/example_ccdRead_debit_test.go new file mode 100644 index 000000000..9b95a4c2d --- /dev/null +++ b/examples/example_ccdRead_debit_test.go @@ -0,0 +1,69 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_ccdReadDebit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "ccd-debit.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("Total Amount Credit: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("CCD Entry Identification Number: %s\n", achFile.Batches[0].GetEntries()[0].IdentificationNumber) + fmt.Printf("CCD Entry Receiving Company: %s\n", achFile.Batches[0].GetEntries()[0].IndividualName) + fmt.Printf("CCD Entry Trace Number: %s\n", achFile.Batches[0].GetEntries()[0].TraceNumberField()) + fmt.Printf("CCD Fee Identification Number: %s\n", achFile.Batches[0].GetEntries()[1].IdentificationNumber) + fmt.Printf("CCD Fee Receiving Company: %s\n", achFile.Batches[0].GetEntries()[1].IndividualName) + fmt.Printf("CCD Fee Trace Number: %s\n", achFile.Batches[0].GetEntries()[1].TraceNumberField()) + + // Output: + // Total Amount Debit: 500125 + // Total Amount Credit: 0 + // SEC Code: CCD + // CCD Entry Identification Number: location1234567 + // CCD Entry Receiving Company: Best Co. #123456789012 + // CCD Entry Trace Number: 031300010000001 + // CCD Fee Identification Number: Fee123456789012 + // CCD Fee Receiving Company: Best Co. #123456789012 + // CCD Fee Trace Number: 031300010000002 +} diff --git a/examples/example_ccdWrite_debit_test.go b/examples/example_ccdWrite_debit_test.go new file mode 100644 index 000000000..a5d9b2c09 --- /dev/null +++ b/examples/example_ccdWrite_debit_test.go @@ -0,0 +1,89 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +func Example_ccdWriteDebit() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.CCD + bh.CompanyEntryDescription = "Vndr Pay" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "031300012" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 500000 + entry.IdentificationNumber = "location #1234" + entry.SetReceivingCompany("Best Co. #1") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.DiscretionaryData = "S" + + entryOne := ach.NewEntryDetail() + entryOne.TransactionCode = ach.CheckingDebit + entryOne.SetRDFI("231380104") + entryOne.DFIAccountNumber = "744-5678-99" + entryOne.Amount = 125 + entryOne.IdentificationNumber = "Fee #1" + entryOne.SetReceivingCompany("Best Co. #1") + entryOne.SetTraceNumber(bh.ODFIIdentification, 2) + entryOne.DiscretionaryData = "S" + + // build the batch + batch := ach.NewBatchCCD(bh) + batch.AddEntry(entry) + batch.AddEntry(entryOne) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetEntries()[1].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5225Name on Account 231380104 CCDVndr Pay 190816 1031300010000001 + // 627231380104744-5678-99 0000500000location #1234 Best Co. #1 S 0031300010000001 + // 627231380104744-5678-99 0000000125Fee #1 Best Co. #1 S 0031300010000002 + // 82250000020046276020000000500125000000000000231380104 031300010000001 + // 9000001000001000000020046276020000000500125000000000000 +} diff --git a/examples/example_cieRead_credit_test.go b/examples/example_cieRead_credit_test.go new file mode 100644 index 000000000..09b9b8222 --- /dev/null +++ b/examples/example_cieRead_credit_test.go @@ -0,0 +1,59 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_cieReadCredit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "cie-credit.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("Total Amount Credit: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Addenda05: %s\n", achFile.Batches[0].GetEntries()[0].Addenda05[0].String()) + + // Output: + // Total Amount Debit: 0 + // Total Amount Credit: 100000000 + // SEC Code: CIE + // Addenda05: 705Credit Store Account 00010000001 +} diff --git a/examples/example_cieWrite_credit_test.go b/examples/example_cieWrite_credit_test.go new file mode 100644 index 000000000..3dd72c91b --- /dev/null +++ b/examples/example_cieWrite_credit_test.go @@ -0,0 +1,84 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +func Example_cieWriteCredit() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.CreditsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.CIE + bh.CompanyEntryDescription = "Payment" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "121042882" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "12345678" + entry.Amount = 100000000 + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.IndividualName = "Receiver Account Name" + entry.DiscretionaryData = "01" + entry.AddendaRecordIndicator = 1 + + addenda05 := ach.NewAddenda05() + addenda05.PaymentRelatedInformation = "Credit Store Account" + addenda05.SequenceNumber = 1 + addenda05.EntryDetailSequenceNumber = 0000001 + + // build the batch + batch := ach.NewBatchCIE(bh) + batch.AddEntry(entry) + batch.GetEntries()[0].AddAddenda05(addenda05) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetEntries()[0].Addenda05[0].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5220Name on Account 231380104 CIEPayment 190816 1121042880000001 + // 62223138010412345678 0100000000 Receiver Account Name 011121042880000001 + // 705Credit Store Account 00010000001 + // 82200000020023138010000000000000000100000000231380104 121042880000001 + // 9000001000001000000020023138010000000000000000100000000 +} diff --git a/examples/example_corRead_credit_test.go b/examples/example_corRead_credit_test.go new file mode 100644 index 000000000..8f42052b5 --- /dev/null +++ b/examples/example_corRead_credit_test.go @@ -0,0 +1,61 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_corReadCredit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "cor-read.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("Total Amount Credit: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Entry Detail: %s\n", achFile.Batches[0].GetEntries()[0].String()) + fmt.Printf("Addenda98: %s\n", achFile.Batches[0].GetEntries()[0].Addenda98.String()) + + // Output: + // Total Amount Debit: 0 + // Total Amount Credit: 0 + // SEC Code: COR + // Entry Detail: 621231380104744-5678-99 0000000000location #23 Best Co. #23 1121042880000001 + // Addenda98: 798C01121042880000001 121042881918171614 091012980000088 +} diff --git a/examples/example_corWrite_credit_test.go b/examples/example_corWrite_credit_test.go new file mode 100644 index 000000000..c8989b335 --- /dev/null +++ b/examples/example_corWrite_credit_test.go @@ -0,0 +1,87 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +func Example_corWriteCredit() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.CreditsOnly + bh.StandardEntryClassCode = ach.COR + bh.CompanyName = "Your Company, inc" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "Vendor Pay" + bh.ODFIIdentification = "121042882" // Originating Routing Number + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingReturnNOCCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.IdentificationNumber = "location #23" + entry.SetReceivingCompany("Best Co. #23") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.AddendaRecordIndicator = 1 + + addenda98 := ach.NewAddenda98() + addenda98.ChangeCode = "C01" + addenda98.OriginalTrace = "121042880000001" + addenda98.OriginalDFI = "121042882" + addenda98.CorrectedData = "1918171614" + addenda98.TraceNumber = "91012980000088" + + entry.Addenda98 = addenda98 + entry.Category = ach.CategoryNOC + + // build the batch + batch := ach.NewBatchCOR(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetEntries()[0].Addenda98.String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5220Your Company, in 121042882 CORVendor Pay 000000 1121042880000001 + // 621231380104744-5678-99 0000000000location #23 Best Co. #23 1121042880000001 + // 798C01121042880000001 121042881918171614 091012980000088 + // 82200000020023138010000000000000000000000000121042882 121042880000001 + // 9000001000001000000020023138010000000000000000000000000 +} diff --git a/examples/example_ctxRead_debit_test.go b/examples/example_ctxRead_debit_test.go new file mode 100644 index 000000000..bd8fbeae3 --- /dev/null +++ b/examples/example_ctxRead_debit_test.go @@ -0,0 +1,61 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_ctxReadDebit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "ctx-debit.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("Total Amount Credit: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Addenda1: %s\n", achFile.Batches[0].GetEntries()[0].Addenda05[0].String()) + fmt.Printf("Addenda2: %s\n", achFile.Batches[0].GetEntries()[0].Addenda05[1].String()) + + // Output: + // Total Amount Debit: 100000000 + // Total Amount Credit: 0 + // SEC Code: CTX + // Addenda1: 705Debit First Account 00010000001 + // Addenda2: 705Debit Second Account 00020000001 +} diff --git a/examples/example_ctxWrite_debit_test.go b/examples/example_ctxWrite_debit_test.go new file mode 100644 index 000000000..ee71fe610 --- /dev/null +++ b/examples/example_ctxWrite_debit_test.go @@ -0,0 +1,95 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +// Example_ctxWriteDebit writes a CTX debit file +func Example_ctxWriteDebit() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.CTX + bh.CompanyEntryDescription = "ACH CTX" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "121042882" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "12345678" + entry.Amount = 100000000 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(2) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.DiscretionaryData = "01" + + addenda1 := ach.NewAddenda05() + addenda1.PaymentRelatedInformation = "Debit First Account" + addenda1.SequenceNumber = 1 + addenda1.EntryDetailSequenceNumber = 0000001 + + addenda2 := ach.NewAddenda05() + addenda2.PaymentRelatedInformation = "Debit Second Account" + addenda2.SequenceNumber = 2 + addenda2.EntryDetailSequenceNumber = 0000001 + + // build the batch + batch := ach.NewBatchCTX(bh) + batch.AddEntry(entry) + batch.Entries[0].AddendaRecordIndicator = 1 + batch.GetEntries()[0].AddAddenda05(addenda1) + batch.GetEntries()[0].AddAddenda05(addenda2) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetEntries()[0].Addenda05[0].String()) + fmt.Println(file.Batches[0].GetEntries()[0].Addenda05[1].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5225Name on Account 231380104 CTXACH CTX 190816 1121042880000001 + // 62723138010412345678 010000000045689033 0002Receiver Company 011121042880000001 + // 705Debit First Account 00010000001 + // 705Debit Second Account 00020000001 + // 82250000030023138010000100000000000000000000231380104 121042880000001 + // 9000001000001000000030023138010000100000000000000000000 +} diff --git a/examples/example_customReturnCode_test.go b/examples/example_customReturnCode_test.go new file mode 100644 index 000000000..c9ce01184 --- /dev/null +++ b/examples/example_customReturnCode_test.go @@ -0,0 +1,80 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "strings" + + "github.com/moov-io/ach" +) + +// Example_customReturnCode writes an ACH file with a non-standard Return Code +func Example_customReturnCode() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.CreditsOnly + bh.CompanyName = "My Company" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.PPD + bh.CompanyEntryDescription = "Cash Back" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "987654320" + + entry := ach.NewEntryDetail() + entry.TransactionCode = 22 // example of a custom code + entry.SetRDFI("123456780") + entry.DFIAccountNumber = "12567" + entry.Amount = 100000000 + entry.SetTraceNumber(bh.ODFIIdentification, 2) + entry.IndividualName = "Jane Smith" + addenda99 := ach.NewAddenda99() + addenda99.ReturnCode = "abc" + entry.Addenda99 = addenda99 + entry.Category = ach.CategoryReturn + entry.AddendaRecordIndicator = 1 + entry.Addenda99.SetValidation(&ach.ValidateOpts{ + CustomReturnCodes: true, + }) + + // build the batch + batch := ach.NewBatchPPD(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Printf("TransactionCode=%d\n", file.Batches[0].GetEntries()[0].TransactionCode) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Printf("ReturnCode=%s\n", strings.TrimSpace(file.Batches[0].GetEntries()[0].Addenda99.ReturnCode)) + + // Output: + // TransactionCode=22 + // 62212345678012567 0100000000 Jane Smith 1987654320000002 + // ReturnCode=abc +} diff --git a/examples/example_custom_trace_number_test.go b/examples/example_custom_trace_number_test.go new file mode 100644 index 000000000..c942b20b3 --- /dev/null +++ b/examples/example_custom_trace_number_test.go @@ -0,0 +1,81 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +// Example_customTraceNumber writes an ACH file with a non-standard NACHA TraceNumber +func Example_customTraceNumber() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.CreditsOnly + bh.CompanyName = "My Company" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.PPD + bh.CompanyEntryDescription = "Cash Back" + // fix EffectiveEntryDate for consistent output + bh.EffectiveEntryDate = "190816" + bh.ODFIIdentification = "987654320" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingCredit + entry.SetRDFI("123456780") + entry.DFIAccountNumber = "12567" + entry.Amount = 100000000 + entry.TraceNumber = "321888" + entry.IndividualName = "Jane Smith" + + // build the batch + batch := ach.NewBatchPPD(bh) + batch.SetValidation(&ach.ValidateOpts{ + CustomTraceNumbers: true, + }) + batch.AddEntry(entry) + + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + if err := file.Validate(); err != nil { + log.Fatalf("Unexpected validation error: %v", err) + } + + if trace := file.Batches[0].GetEntries()[0].TraceNumber; trace != "321888" { + log.Fatalf("Unexpected trace number: %s", trace) + } + + fmt.Printf("TransactionCode=%d\n", file.Batches[0].GetEntries()[0].TransactionCode) + fmt.Printf("%s\n", file.Batches[0].GetEntries()[0].String()) + + // Output: + // TransactionCode=22 + // 62212345678012567 0100000000 Jane Smith 0000000000321888 +} diff --git a/examples/example_custom_transaction_code_test.go b/examples/example_custom_transaction_code_test.go new file mode 100644 index 000000000..9bf2be507 --- /dev/null +++ b/examples/example_custom_transaction_code_test.go @@ -0,0 +1,84 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +// Example_customTransactionCode writes an ACH file with a non-standard NACHA TransactionCodes +func Example_customTransactionCode() { + validationOpts := &ach.ValidateOpts{ + // CheckTransactionCode lets us override the standard set of NACHA + // codes for applications which use custom TransactionCode values. + CheckTransactionCode: func(code int) error { + // Is it a custom TransactionCode of ours? + if code == 78 { + return nil + } + // Is it a NACHA standard code? + return ach.StandardTransactionCode(code) + }, + } + + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.CreditsOnly + bh.CompanyName = "My Company" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.PPD + bh.CompanyEntryDescription = "Cash Back" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "987654320" + bh.SetValidation(validationOpts) + + entry := ach.NewEntryDetail() + entry.TransactionCode = 78 // example of a custom code + entry.SetRDFI("123456780") + entry.DFIAccountNumber = "12567" + entry.Amount = 100000000 + entry.SetTraceNumber(bh.ODFIIdentification, 2) + entry.IndividualName = "Jane Smith" + entry.SetValidation(validationOpts) + + // build the batch + batch := ach.NewBatchPPD(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Printf("TransactionCode=%d\n", file.Batches[0].GetEntries()[0].TransactionCode) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + + // Output: + // TransactionCode=78 + // 67812345678012567 0100000000 Jane Smith 0987654320000002 +} diff --git a/examples/example_dneRead_test.go b/examples/example_dneRead_test.go new file mode 100644 index 000000000..1162ee3a4 --- /dev/null +++ b/examples/example_dneRead_test.go @@ -0,0 +1,57 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_dneRead() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "dne-read.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount: %d\n", achFile.Batches[0].GetEntries()[0].Amount) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Payment Related Information: %s\n", achFile.Batches[0].GetEntries()[0].Addenda05[0].String()) + + // Output: + // Total Amount: 0 + // SEC Code: DNE + // Payment Related Information: 705DATE OF DEATH*010218*CUSTOMERSSN*#########*AMOUNT*$$$$.cc\ 00010000001 +} diff --git a/examples/example_dneWrite_test.go b/examples/example_dneWrite_test.go new file mode 100644 index 000000000..1b1fd2e2a --- /dev/null +++ b/examples/example_dneWrite_test.go @@ -0,0 +1,84 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +// Example_dneWrite writes a DNR file +func Example_dneWrite() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.CreditsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.DNE + bh.CompanyEntryDescription = "Death" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "23138010" + bh.OriginatorStatusCode = 2 + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingReturnNOCCredit + entry.SetRDFI("031300012") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.SetOriginalTraceNumber("031300010000001") + entry.SetReceivingCompany("Best. #1") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + + addenda := ach.NewAddenda05() + addenda.PaymentRelatedInformation = ` DATE OF DEATH*010218*CUSTOMERSSN*#########*AMOUNT*$$$$.cc\` + entry.AddAddenda05(addenda) + entry.AddendaRecordIndicator = 1 + + // build the batch + batch := ach.NewBatchDNE(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetEntries()[0].Addenda05[0].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5220Name on Account 231380104 DNEDeath 190816 2231380100000001 + // 621031300012744-5678-99 0000000000031300010000001Best. #1 1231380100000001 + // 705 DATE OF DEATH*010218*CUSTOMERSSN*#########*AMOUNT*$$$$.cc\ 00010000001 + // 82200000020003130001000000000000000000000000231380104 231380100000001 + // 9000001000001000000020003130001000000000000000000000000 +} diff --git a/examples/example_enrRead_test.go b/examples/example_enrRead_test.go new file mode 100644 index 000000000..2407bbd28 --- /dev/null +++ b/examples/example_enrRead_test.go @@ -0,0 +1,57 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_enrRead() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "enr-read.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount: %d\n", achFile.Batches[0].GetEntries()[0].Amount) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Payment Related Information: %s\n", achFile.Batches[0].GetEntries()[0].Addenda05[0].String()) + + // Output: + // Total Amount: 0 + // SEC Code: ENR + // Payment Related Information: 70522*12200004*3*123987654321*777777777*DOE*JOHN*1\ 00010000001 +} diff --git a/examples/example_enrWrite_test.go b/examples/example_enrWrite_test.go new file mode 100644 index 000000000..6974c925d --- /dev/null +++ b/examples/example_enrWrite_test.go @@ -0,0 +1,82 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +// Example_enrWrite writes and ENR file +func Example_enrWrite() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.ENR + bh.CompanyEntryDescription = "AUTOENROLL" + bh.ODFIIdentification = "23138010" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("031300012") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.SetOriginalTraceNumber("031300010000001") + entry.SetReceivingCompany("Best. #1") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + + addenda05 := ach.NewAddenda05() + addenda05.PaymentRelatedInformation = `22*12200004*3*123987654321*777777777*DOE*JOHN*1\` // From NACHA 2013 Official Rules + entry.AddAddenda05(addenda05) + entry.AddendaRecordIndicator = 1 + + // build the batch + batch := ach.NewBatchENR(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetEntries()[0].Addenda05[0].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5225Name on Account 231380104 ENRAUTOENROLL 1231380100000001 + // 627031300012744-5678-99 0000000000031300010000001Best. #1 1231380100000001 + // 70522*12200004*3*123987654321*777777777*DOE*JOHN*1\ 00010000001 + // 82250000020003130001000000000000000000000000231380104 231380100000001 + // 9000001000001000000020003130001000000000000000000000000 +} diff --git a/examples/example_iatRead_mixedCreditDebit_test.go b/examples/example_iatRead_mixedCreditDebit_test.go new file mode 100644 index 000000000..2cabafc3f --- /dev/null +++ b/examples/example_iatRead_mixedCreditDebit_test.go @@ -0,0 +1,97 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_iatReadMixedCreditDebit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "iat-mixedCreditDebit.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("SEC Code: %s\n", achFile.IATBatches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Debit Entry: %s\n", achFile.IATBatches[0].Entries[0].String()) + fmt.Printf("Addenda10: %s\n", achFile.IATBatches[0].Entries[0].Addenda10.String()) + fmt.Printf("Addenda11: %s\n", achFile.IATBatches[0].Entries[0].Addenda11.String()) + fmt.Printf("Addenda12: %s\n", achFile.IATBatches[0].Entries[0].Addenda12.String()) + fmt.Printf("Addenda13: %s\n", achFile.IATBatches[0].Entries[0].Addenda13.String()) + fmt.Printf("Addenda14: %s\n", achFile.IATBatches[0].Entries[0].Addenda14.String()) + fmt.Printf("Addenda15: %s\n", achFile.IATBatches[0].Entries[0].Addenda15.String()) + fmt.Printf("Addenda16: %s\n", achFile.IATBatches[0].Entries[0].Addenda16.String()) + fmt.Printf("Addenda17: %s\n", achFile.IATBatches[0].Entries[0].Addenda17[0].String()) + fmt.Printf("Addenda18: %s\n", achFile.IATBatches[0].Entries[0].Addenda18[0].String()) + fmt.Printf("Total File Debit Amount: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("Credit Entry: %s\n", achFile.IATBatches[0].Entries[1].String()) + fmt.Printf("Addenda10: %s\n", achFile.IATBatches[0].Entries[1].Addenda10.String()) + fmt.Printf("Addenda11: %s\n", achFile.IATBatches[0].Entries[1].Addenda11.String()) + fmt.Printf("Addenda12: %s\n", achFile.IATBatches[0].Entries[1].Addenda12.String()) + fmt.Printf("Addenda13: %s\n", achFile.IATBatches[0].Entries[1].Addenda13.String()) + fmt.Printf("Addenda14: %s\n", achFile.IATBatches[0].Entries[1].Addenda14.String()) + fmt.Printf("Addenda15: %s\n", achFile.IATBatches[0].Entries[1].Addenda15.String()) + fmt.Printf("Addenda16: %s\n", achFile.IATBatches[0].Entries[1].Addenda16.String()) + fmt.Printf("Addenda17: %s\n", achFile.IATBatches[0].Entries[1].Addenda17[0].String()) + fmt.Printf("Addenda18: %s\n", achFile.IATBatches[0].Entries[1].Addenda18[0].String()) + fmt.Printf("Total File Credit Amount: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + + // Output: + // SEC Code: IAT + // Debit Entry: 6271210428820007 0000100000123456789 1231380100000001 + // Addenda10: 710ANN000000000000100000928383-23938 BEK Enterprises 0000001 + // Addenda11: 711BEK Solutions 15 West Place Street 0000001 + // Addenda12: 712JacobsTown*PA\ US*19305\ 0000001 + // Addenda13: 713Wells Fargo 01231380104 US 0000001 + // Addenda14: 714Citadel Bank 01121042882 CA 0000001 + // Addenda15: 7159874654932139872121 Front Street 0000001 + // Addenda16: 716LetterTown*AB\ CA*80014\ 0000001 + // Addenda17: 717This is an international payment 00010000001 + // Addenda18: 718Bank of France 01456456456987987 FR 00010000001 + // Total File Debit Amount: 100000 + // Credit Entry: 6221210428820007 0000100000123456789 1231380100000002 + // Addenda10: 710ANN000000000000100000928383-23938 ADCAF Enterprises 0000002 + // Addenda11: 711ADCAF Solutions 15 West Place Street 0000002 + // Addenda12: 712JacobsTown*PA\ US*19305\ 0000002 + // Addenda13: 713Wells Fargo 01231380104 US 0000002 + // Addenda14: 714Citadel Bank 01121042882 CA 0000002 + // Addenda15: 71598746549321398718 Fifth Street 0000002 + // Addenda16: 716LetterTown*AB\ CA*80014\ 0000002 + // Addenda17: 717This is an international payment 00010000002 + // Addenda18: 718Bank of France 01456456456987987 FR 00010000002 + // Total File Credit Amount: 100000 +} diff --git a/examples/example_iatWrite_mixedCreditDebit_test.go b/examples/example_iatWrite_mixedCreditDebit_test.go new file mode 100644 index 000000000..bc8e81400 --- /dev/null +++ b/examples/example_iatWrite_mixedCreditDebit_test.go @@ -0,0 +1,253 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "strconv" + + "github.com/moov-io/ach" +) + +// Example_iatWriteMixedCreditDebit writes a IAT mixed debit and credit file +func Example_iatWriteMixedCreditDebit() { + fh := mockFileHeader() + + bh := ach.NewIATBatchHeader() + bh.ServiceClassCode = ach.MixedDebitsAndCredits + bh.ForeignExchangeIndicator = "FF" + bh.ForeignExchangeReferenceIndicator = 3 + bh.ISODestinationCountryCode = "US" + bh.OriginatorIdentification = "123456789" + bh.StandardEntryClassCode = ach.IAT + bh.CompanyEntryDescription = "TRADEPAYMT" + bh.ISOOriginatingCurrencyCode = "CAD" + bh.ISODestinationCurrencyCode = "USD" + bh.ODFIIdentification = "23138010" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + + entry := ach.NewIATEntryDetail() + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("121042882") + entry.AddendaRecords = 007 + entry.DFIAccountNumber = "123456789" + entry.Amount = 100000 // 1000.00 + entry.SetTraceNumber("23138010", 1) + entry.Category = ach.CategoryForward + + addenda10 := ach.NewAddenda10() + addenda10.TransactionTypeCode = "ANN" + addenda10.ForeignPaymentAmount = 100000 + addenda10.ForeignTraceNumber = "928383-23938" + addenda10.Name = "BEK Enterprises" + addenda10.EntryDetailSequenceNumber = 00000001 + entry.Addenda10 = addenda10 + + addenda11 := ach.NewAddenda11() + addenda11.OriginatorName = "BEK Solutions" + addenda11.OriginatorStreetAddress = "15 West Place Street" + addenda11.EntryDetailSequenceNumber = 00000001 + entry.Addenda11 = addenda11 + + addenda12 := ach.NewAddenda12() + addenda12.OriginatorCityStateProvince = "JacobsTown*PA\\" + addenda12.OriginatorCountryPostalCode = "US*19305\\" + addenda12.EntryDetailSequenceNumber = 00000001 + entry.Addenda12 = addenda12 + + addenda13 := ach.NewAddenda13() + addenda13.ODFIName = "Wells Fargo" + addenda13.ODFIIDNumberQualifier = "01" + addenda13.ODFIIdentification = "231380104" + addenda13.ODFIBranchCountryCode = "US" + addenda13.EntryDetailSequenceNumber = 00000001 + entry.Addenda13 = addenda13 + + addenda14 := ach.NewAddenda14() + addenda14.RDFIName = "Citadel Bank" + addenda14.RDFIIDNumberQualifier = "01" + addenda14.RDFIIdentification = "121042882" + addenda14.RDFIBranchCountryCode = "CA" + addenda14.EntryDetailSequenceNumber = 00000001 + entry.Addenda14 = addenda14 + + addenda15 := ach.NewAddenda15() + addenda15.ReceiverIDNumber = "987465493213987" + addenda15.ReceiverStreetAddress = "2121 Front Street" + addenda15.EntryDetailSequenceNumber = 00000001 + entry.Addenda15 = addenda15 + + addenda16 := ach.NewAddenda16() + addenda16.ReceiverCityStateProvince = "LetterTown*AB\\" + addenda16.ReceiverCountryPostalCode = "CA*80014\\" + addenda16.EntryDetailSequenceNumber = 00000001 + entry.Addenda16 = addenda16 + + addenda17 := ach.NewAddenda17() + addenda17.PaymentRelatedInformation = "This is an international payment" + addenda17.SequenceNumber = 1 + addenda17.EntryDetailSequenceNumber = 0000001 + entry.AddAddenda17(addenda17) + + addenda18 := ach.NewAddenda18() + addenda18.ForeignCorrespondentBankName = "Bank of France" + addenda18.ForeignCorrespondentBankIDNumberQualifier = "01" + addenda18.ForeignCorrespondentBankIDNumber = "456456456987987" + addenda18.ForeignCorrespondentBankBranchCountryCode = "FR" + addenda18.SequenceNumber = 3 + addenda18.EntryDetailSequenceNumber = 0000001 + entry.AddAddenda18(addenda18) + + entryTwo := ach.NewIATEntryDetail() + entryTwo.TransactionCode = ach.CheckingCredit + entryTwo.SetRDFI("121042882") + entryTwo.AddendaRecords = 007 + entryTwo.DFIAccountNumber = "123456789" + entryTwo.Amount = 100000 // 1000.00 + entryTwo.SetTraceNumber("23138010", 2) + entryTwo.Category = ach.CategoryForward + + addenda10Two := ach.NewAddenda10() + addenda10Two.TransactionTypeCode = "ANN" + addenda10Two.ForeignPaymentAmount = 100000 + addenda10Two.ForeignTraceNumber = "928383-23938" + addenda10Two.Name = "ADCAF Enterprises" + addenda10Two.EntryDetailSequenceNumber = 00000002 + entryTwo.Addenda10 = addenda10Two + + addenda11Two := ach.NewAddenda11() + addenda11Two.OriginatorName = "ADCAF Solutions" + addenda11Two.OriginatorStreetAddress = "15 West Place Street" + addenda11Two.EntryDetailSequenceNumber = 00000002 + entryTwo.Addenda11 = addenda11Two + + addenda12Two := ach.NewAddenda12() + addenda12Two.OriginatorCityStateProvince = "JacobsTown*PA\\" + addenda12Two.OriginatorCountryPostalCode = "US*19305\\" + addenda12Two.EntryDetailSequenceNumber = 00000002 + entryTwo.Addenda12 = addenda12Two + + addenda13Two := ach.NewAddenda13() + addenda13Two.ODFIName = "Wells Fargo" + addenda13Two.ODFIIDNumberQualifier = "01" + addenda13Two.ODFIIdentification = "231380104" + addenda13Two.ODFIBranchCountryCode = "US" + addenda13Two.EntryDetailSequenceNumber = 00000002 + entryTwo.Addenda13 = addenda13Two + + addenda14Two := ach.NewAddenda14() + addenda14Two.RDFIName = "Citadel Bank" + addenda14Two.RDFIIDNumberQualifier = "01" + addenda14Two.RDFIIdentification = "121042882" + addenda14Two.RDFIBranchCountryCode = "CA" + addenda14Two.EntryDetailSequenceNumber = 00000002 + entryTwo.Addenda14 = addenda14Two + + addenda15Two := ach.NewAddenda15() + addenda15Two.ReceiverIDNumber = "987465493213987" + addenda15Two.ReceiverStreetAddress = "18 Fifth Street" + addenda15Two.EntryDetailSequenceNumber = 00000002 + entryTwo.Addenda15 = addenda15Two + + addenda16Two := ach.NewAddenda16() + addenda16Two.ReceiverCityStateProvince = "LetterTown*AB\\" + addenda16Two.ReceiverCountryPostalCode = "CA*80014\\" + addenda16Two.EntryDetailSequenceNumber = 00000002 + entryTwo.Addenda16 = addenda16Two + + addenda17Two := ach.NewAddenda17() + addenda17Two.PaymentRelatedInformation = "This is an international payment" + addenda17Two.SequenceNumber = 1 + addenda17Two.EntryDetailSequenceNumber = 0000002 + entryTwo.AddAddenda17(addenda17Two) + + addenda18Two := ach.NewAddenda18() + addenda18Two.ForeignCorrespondentBankName = "Bank of France" + addenda18Two.ForeignCorrespondentBankIDNumberQualifier = "01" + addenda18Two.ForeignCorrespondentBankIDNumber = "456456456987987" + addenda18Two.ForeignCorrespondentBankBranchCountryCode = "FR" + addenda18Two.SequenceNumber = 3 + addenda18Two.EntryDetailSequenceNumber = 0000002 + entryTwo.AddAddenda18(addenda18Two) + + // build the batch + batch := ach.NewIATBatch(bh) + batch.AddEntry(entry) + batch.AddEntry(entryTwo) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + achFile := ach.NewFile() + achFile.SetHeader(fh) + achFile.AddIATBatch(batch) + if err := achFile.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Printf("SEC Code: %s\n", achFile.IATBatches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Debit Entry: %s\n", achFile.IATBatches[0].Entries[0].String()) + fmt.Printf("Addenda10: %s\n", achFile.IATBatches[0].Entries[0].Addenda10.String()) + fmt.Printf("Addenda11: %s\n", achFile.IATBatches[0].Entries[0].Addenda11.String()) + fmt.Printf("Addenda12: %s\n", achFile.IATBatches[0].Entries[0].Addenda12.String()) + fmt.Printf("Addenda13: %s\n", achFile.IATBatches[0].Entries[0].Addenda13.String()) + fmt.Printf("Addenda14: %s\n", achFile.IATBatches[0].Entries[0].Addenda14.String()) + fmt.Printf("Addenda15: %s\n", achFile.IATBatches[0].Entries[0].Addenda15.String()) + fmt.Printf("Addenda16: %s\n", achFile.IATBatches[0].Entries[0].Addenda16.String()) + fmt.Printf("Addenda17: %s\n", achFile.IATBatches[0].Entries[0].Addenda17[0].String()) + fmt.Printf("Addenda18: %s\n", achFile.IATBatches[0].Entries[0].Addenda18[0].String()) + fmt.Printf("Total File Debit Amount: %s\n", strconv.Itoa(achFile.Control.TotalDebitEntryDollarAmountInFile)) + fmt.Printf("Credit Entry: %s\n", achFile.IATBatches[0].Entries[1].String()) + fmt.Printf("Addenda10: %s\n", achFile.IATBatches[0].Entries[1].Addenda10.String()) + fmt.Printf("Addenda11: %s\n", achFile.IATBatches[0].Entries[1].Addenda11.String()) + fmt.Printf("Addenda12: %s\n", achFile.IATBatches[0].Entries[1].Addenda12.String()) + fmt.Printf("Addenda13: %s\n", achFile.IATBatches[0].Entries[1].Addenda13.String()) + fmt.Printf("Addenda14: %s\n", achFile.IATBatches[0].Entries[1].Addenda14.String()) + fmt.Printf("Addenda15: %s\n", achFile.IATBatches[0].Entries[1].Addenda15.String()) + fmt.Printf("Addenda16: %s\n", achFile.IATBatches[0].Entries[1].Addenda16.String()) + fmt.Printf("Addenda17: %s\n", achFile.IATBatches[0].Entries[1].Addenda17[0].String()) + fmt.Printf("Addenda18: %s\n", achFile.IATBatches[0].Entries[1].Addenda18[0].String()) + fmt.Printf("Total File Credit Amount: %s\n", strconv.Itoa(achFile.Control.TotalCreditEntryDollarAmountInFile)) + + // Output: + // SEC Code: IAT + // Debit Entry: 6271210428820007 0000100000123456789 1231380100000001 + // Addenda10: 710ANN000000000000100000928383-23938 BEK Enterprises 0000001 + // Addenda11: 711BEK Solutions 15 West Place Street 0000001 + // Addenda12: 712JacobsTown*PA\ US*19305\ 0000001 + // Addenda13: 713Wells Fargo 01231380104 US 0000001 + // Addenda14: 714Citadel Bank 01121042882 CA 0000001 + // Addenda15: 7159874654932139872121 Front Street 0000001 + // Addenda16: 716LetterTown*AB\ CA*80014\ 0000001 + // Addenda17: 717This is an international payment 00010000001 + // Addenda18: 718Bank of France 01456456456987987 FR 00010000001 + // Total File Debit Amount: 100000 + // Credit Entry: 6221210428820007 0000100000123456789 1231380100000002 + // Addenda10: 710ANN000000000000100000928383-23938 ADCAF Enterprises 0000002 + // Addenda11: 711ADCAF Solutions 15 West Place Street 0000002 + // Addenda12: 712JacobsTown*PA\ US*19305\ 0000002 + // Addenda13: 713Wells Fargo 01231380104 US 0000002 + // Addenda14: 714Citadel Bank 01121042882 CA 0000002 + // Addenda15: 71598746549321398718 Fifth Street 0000002 + // Addenda16: 716LetterTown*AB\ CA*80014\ 0000002 + // Addenda17: 717This is an international payment 00010000002 + // Addenda18: 718Bank of France 01456456456987987 FR 00010000002 + // Total File Credit Amount: 100000 +} diff --git a/examples/example_lambda_json_to_nacha.go b/examples/example_lambda_json_to_nacha.go new file mode 100644 index 000000000..1672a5772 --- /dev/null +++ b/examples/example_lambda_json_to_nacha.go @@ -0,0 +1,68 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//go:build examples +// +build examples + +package main + +import ( + "bytes" + "context" + + "github.com/aws/aws-lambda-go/lambda" + "github.com/moov-io/ach" + "github.com/moov-io/base" +) + +type JsonParseEvent struct { + Json ach.File `json:"data"` +} + +func main() { + lambda.Start(HandleRequest) +} + +// logic to be executed when lambda starts goes here +func HandleRequest(ctx context.Context, event JsonParseEvent) (string, error) { + + // get file from lambda event, it has already been marshaled from json to ach.File by Go + file := event.Json + + // set file ID + file.ID = base.ID() + + // validate parsed file + err := file.Validate() + if err != nil { + return "", err + } + + // create buffer to contain NACHA text + buf := new(bytes.Buffer) + + // write ach.File to buffer + err = ach.NewWriter(buf).Write(&file) + if err != nil { + return buf.String(), err + } + + // get NACHA text from buffer + parseRes := buf.String() + + return parseRes, err +} diff --git a/examples/example_lambda_nacha_to_json.go b/examples/example_lambda_nacha_to_json.go new file mode 100644 index 000000000..3bc3a3f8a --- /dev/null +++ b/examples/example_lambda_nacha_to_json.go @@ -0,0 +1,68 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//go:build examples +// +build examples + +package main + +import ( + "context" + "strings" + + "github.com/aws/aws-lambda-go/lambda" + "github.com/moov-io/ach" + "github.com/moov-io/base" +) + +type NachaParseEvent struct { + Nacha string `json:"data"` +} + +type NachaParseResponse struct { + File ach.File `json:"file"` +} + +func main() { + lambda.Start(HandleRequest) +} + +// logic to be executed when lambda starts goes here +func HandleRequest(ctx context.Context, event NachaParseEvent) (NachaParseResponse, error) { + + // get NACHA file text from lambda event + rd := strings.NewReader(event.Nacha) + + // create file from NACHA text + file, err := ach.NewReader(rd).Read() + if err != nil { + return NachaParseResponse{File: file}, err + } + + // set file ID + file.ID = base.ID() + + // validate parsed file + if err := file.Validate(); err != nil { + return NachaParseResponse{File: file}, err + } + + //create response object + parseRes := NachaParseResponse{File: file} + + return parseRes, err +} diff --git a/examples/example_mteRead_debit_test.go b/examples/example_mteRead_debit_test.go new file mode 100644 index 000000000..94e560a21 --- /dev/null +++ b/examples/example_mteRead_debit_test.go @@ -0,0 +1,57 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_mteReadDebit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "mte-read.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount: %d\n", achFile.Batches[0].GetEntries()[0].Amount) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Addenda02: %s\n", achFile.Batches[0].GetEntries()[0].Addenda02.String()) + + // Output: + // Total Amount: 10000 + // SEC Code: MTE + // Addenda02: 702 2005091234561224 321 East Market Street ANYTOWN VA231380100000001 +} diff --git a/examples/example_mteWrite_debit_test.go b/examples/example_mteWrite_debit_test.go new file mode 100644 index 000000000..72831a3e8 --- /dev/null +++ b/examples/example_mteWrite_debit_test.go @@ -0,0 +1,87 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +func Example_mteWriteDebit() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Merchant with ATM" // Merchant with the ATM + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.MTE + bh.CompanyEntryDescription = "CASH WITHDRAW" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "23138010" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("031300012") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 10000 + entry.SetOriginalTraceNumber("031300010000001") + entry.SetReceivingCompany("JANE DOE") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + + addenda02 := ach.NewAddenda02() + addenda02.TerminalIdentificationCode = "200509" + addenda02.TerminalLocation = "321 East Market Street" + addenda02.TerminalCity = "ANYTOWN" + addenda02.TerminalState = "VA" + + addenda02.TransactionSerialNumber = "123456" + addenda02.TransactionDate = "1224" + addenda02.TraceNumber = entry.TraceNumber + entry.Addenda02 = addenda02 + entry.AddendaRecordIndicator = 1 + + // build the batch + batch := ach.NewBatchMTE(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5225Merchant with AT 231380104 MTECASH WITHD 190816 1231380100000001 + // 627031300012744-5678-99 0000010000031300010000001JANE DOE 1231380100000001 + // 82250000020003130001000000010000000000000000231380104 231380100000001 + // 9000001000001000000020003130001000000010000000000000000 +} diff --git a/examples/example_popRead_debit_test.go b/examples/example_popRead_debit_test.go new file mode 100644 index 000000000..b97ab971c --- /dev/null +++ b/examples/example_popRead_debit_test.go @@ -0,0 +1,61 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_popReadDebit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "pop-debit.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("POP Check Serial Number: %s\n", achFile.Batches[0].GetEntries()[0].IdentificationNumber[0:9]) + fmt.Printf("POP Terminal City: %s\n", achFile.Batches[0].GetEntries()[0].IdentificationNumber[9:13]) + fmt.Printf("POP Terminal State: %s\n", achFile.Batches[0].GetEntries()[0].IdentificationNumber[13:15]) + + // Output: + // Total Amount Debit: 250500 + // SEC Code: POP + // POP Check Serial Number: 123456789 + // POP Terminal City: PHIL + // POP Terminal State: PA +} diff --git a/examples/example_popWrite_debit_test.go b/examples/example_popWrite_debit_test.go new file mode 100644 index 000000000..a73dbc300 --- /dev/null +++ b/examples/example_popWrite_debit_test.go @@ -0,0 +1,77 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +func Example_popWriteDebit() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Originator" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.POP + bh.CompanyEntryDescription = "ACH POP" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "121042882" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "12345678" + entry.Amount = 250500 + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.IndividualName = "Wade Arnold" + entry.SetPOPCheckSerialNumber("123456") + entry.SetPOPTerminalCity("PHIL") + entry.SetPOPTerminalState("PA") + + // build the batch + batch := ach.NewBatchPOP(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5225Originator 231380104 POPACH POP 190816 1121042880000001 + // 62723138010412345678 0000250500123456 PHILPAWade Arnold 0121042880000001 + // 82250000010023138010000000250500000000000000231380104 121042880000001 + // 9000001000001000000010023138010000000250500000000000000 +} diff --git a/examples/example_posRead_debit_test.go b/examples/example_posRead_debit_test.go new file mode 100644 index 000000000..0e240fece --- /dev/null +++ b/examples/example_posRead_debit_test.go @@ -0,0 +1,59 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_posReadDebit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "pos-debit.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("POS Card Transaction Type: %s\n", achFile.Batches[0].GetEntries()[0].DiscretionaryData) + fmt.Printf("POS Trace Number: %s\n", achFile.Batches[0].GetEntries()[0].TraceNumber) + + // Output: + // Total Amount Debit: 100000000 + // SEC Code: POS + // POS Card Transaction Type: 01 + // POS Trace Number: 121042880000001 +} diff --git a/examples/example_posWrite_debit_test.go b/examples/example_posWrite_debit_test.go new file mode 100644 index 000000000..4301e0999 --- /dev/null +++ b/examples/example_posWrite_debit_test.go @@ -0,0 +1,91 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +func Example_posWriteDebit() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.POS + bh.CompanyEntryDescription = "Sale" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "121042882" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "12345678" + entry.Amount = 100000000 + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.IndividualName = "Receiver Account Name" + entry.DiscretionaryData = "01" + entry.AddendaRecordIndicator = 1 + + addenda02 := ach.NewAddenda02() + addenda02.ReferenceInformationOne = "REFONEA" + addenda02.ReferenceInformationTwo = "REF" + addenda02.TerminalIdentificationCode = "TERM02" + addenda02.TransactionSerialNumber = "100049" + addenda02.TransactionDate = "0614" + addenda02.AuthorizationCodeOrExpireDate = "123456" + addenda02.TerminalLocation = "Target Store 0049" + addenda02.TerminalCity = "PHILADELPHIA" + addenda02.TerminalState = "PA" + addenda02.TraceNumber = "121042880000001" + + // build the batch + batch := ach.NewBatchPOS(bh) + batch.AddEntry(entry) + batch.GetEntries()[0].Addenda02 = addenda02 + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetEntries()[0].Addenda02.String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5225Name on Account 231380104 POSSale 190816 1121042880000001 + // 62723138010412345678 0100000000 Receiver Account Name 011121042880000001 + // 702REFONEAREFTERM021000490614123456Target Store 0049 PHILADELPHIA PA121042880000001 + // 82250000020023138010000100000000000000000000231380104 121042880000001 + // 9000001000001000000020023138010000100000000000000000000 +} diff --git a/examples/example_ppdRead_credit_test.go b/examples/example_ppdRead_credit_test.go new file mode 100644 index 000000000..46d455d50 --- /dev/null +++ b/examples/example_ppdRead_credit_test.go @@ -0,0 +1,57 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_ppdReadCredit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "ppd-credit.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Total File Debit Amount: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("Total File Credit Amount: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + + // Output: + // SEC Code: PPD + // Total File Debit Amount: 0 + // Total File Credit Amount: 100000000 +} diff --git a/examples/example_ppdRead_debit_test.go b/examples/example_ppdRead_debit_test.go new file mode 100644 index 000000000..61b2b6c9e --- /dev/null +++ b/examples/example_ppdRead_debit_test.go @@ -0,0 +1,57 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_ppdReadDebit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "ppd-debit.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Total File Debit Amount: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("Total File Credit Amount: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + + // Output: + // SEC Code: PPD + // Total File Debit Amount: 200000000 + // Total File Credit Amount: 0 +} diff --git a/examples/example_ppdRead_segmentFile_test.go b/examples/example_ppdRead_segmentFile_test.go new file mode 100644 index 000000000..2733332b0 --- /dev/null +++ b/examples/example_ppdRead_segmentFile_test.go @@ -0,0 +1,78 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_ppdReadSegmentFile() { + // Open a file for reading, any io.Reader can be used + fCredit, err := os.Open(filepath.Join("testdata", "segmentFile-ppd-credit.ach")) + if err != nil { + log.Fatalln(err) + } + rCredit := ach.NewReader(fCredit) + achFileCredit, err := rCredit.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFileCredit.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFileCredit.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + // Open a file for reading, any io.Reader can be used + fDebit, err := os.Open(filepath.Join("testdata", "segmentFile-ppd-debit.ach")) + if err != nil { + log.Fatalln(err) + } + rDebit := ach.NewReader(fDebit) + achFileDebit, err := rDebit.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFileDebit.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFileDebit.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Credit Amount: %d\n", achFileCredit.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFileCredit.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Total Debit Amount: %d\n", achFileDebit.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFileDebit.Batches[0].GetHeader().StandardEntryClassCode) + + // Output: + // Total Credit Amount: 200000000 + // SEC Code: PPD + // Total Debit Amount: 200000000 + // SEC Code: PPD +} diff --git a/examples/example_ppdWrite_credit_test.go b/examples/example_ppdWrite_credit_test.go new file mode 100644 index 000000000..ffea7c506 --- /dev/null +++ b/examples/example_ppdWrite_credit_test.go @@ -0,0 +1,76 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +// Example_ppdWriteCredit writes a PPD credit file +func Example_ppdWriteCredit() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.CreditsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.PPD + bh.CompanyEntryDescription = "REG.SALARY" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "121042882" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "987654321" + entry.Amount = 100000000 + entry.SetTraceNumber(bh.ODFIIdentification, 2) + entry.IndividualName = "Credit Account 1" + + // build the batch + batch := ach.NewBatchPPD(bh) + batch.AddEntry(entry) + + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5220Name on Account 231380104 PPDREG.SALARY 190816 1121042880000001 + // 622231380104987654321 0100000000 Credit Account 1 0121042880000002 + // 82200000010023138010000000000000000100000000231380104 121042880000001 + // 9000001000001000000010023138010000000000000000100000000 +} diff --git a/examples/example_ppdWrite_debit_test.go b/examples/example_ppdWrite_debit_test.go new file mode 100644 index 000000000..2f4bb3211 --- /dev/null +++ b/examples/example_ppdWrite_debit_test.go @@ -0,0 +1,76 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +// // Example_ppdWriteDebit writes a PPD debit file +func Example_ppdWriteDebit() { + + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.PPD + bh.CompanyEntryDescription = "REG.SALARY" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "121042882" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("231380104") // Receivers bank transit routing number + entry.DFIAccountNumber = "123456789" // Receivers bank account number + entry.Amount = 200000000 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.IndividualName = "Debit Account" // Identifies the receiver of the transaction + + // build the batch + batch := ach.NewBatchPPD(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5225Name on Account 231380104 PPDREG.SALARY 190816 1121042880000001 + // 627231380104123456789 0200000000 Debit Account 0121042880000001 + // 82250000010023138010000200000000000000000000231380104 121042880000001 + // 9000001000001000000010023138010000200000000000000000000 +} diff --git a/examples/example_ppdWrite_segmentFile_test.go b/examples/example_ppdWrite_segmentFile_test.go new file mode 100644 index 000000000..82d26f117 --- /dev/null +++ b/examples/example_ppdWrite_segmentFile_test.go @@ -0,0 +1,84 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_ppdWriteSegmentFile() { + // open a file for reading. Any io.Reader Can be used + f, err := os.Open(filepath.Join("testdata", "ppd-mixedDebitCredit.ach")) + if err != nil { + log.Fatal(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + fmt.Printf("Issue reading file: %+v \n", err) + } + + // verify a TraceNumber is set + for i := range achFile.Batches { + ed := achFile.Batches[i].GetEntries() + for j := range ed { + if ed[j].TraceNumber == "" { + log.Fatalf("Batch[%d].Entries[%d] is missing a TraceNumber", i, j) + } + } + } + + // ensure we have a validated file structure + if achFile.Validate(); err != nil { + fmt.Printf("Could not validate entire read file: %v", err) + } + + sfc := ach.NewSegmentFileConfiguration() + creditFile, debitFile, err := achFile.SegmentFile(sfc) + + if err != nil { + fmt.Printf("Could not segment the file: %v", err) + } + + if err := creditFile.Validate(); err != nil { + log.Fatal(err) + } + if err := debitFile.Validate(); err != nil { + log.Fatal(err) + } + + ed := creditFile.Batches[0].GetEntries() + for i := range ed { + fmt.Printf("credit %d: %s\n", i, ed[i].String()) + } + + ed = debitFile.Batches[0].GetEntries() + for i := range ed { + fmt.Printf("debit %d: %s\n", i, ed[i].String()) + } + + // Output: + // credit 0: 622231380104987654321 0100000000 Credit Account 1 0121042880000002 + // credit 1: 622231380104837098765 0100000000 Credit Account 2 0121042880000003 + // debit 0: 627231380104123456789 0200000000 Debit Account 0121042880000001 +} diff --git a/examples/example_rckRead_debit_test.go b/examples/example_rckRead_debit_test.go new file mode 100644 index 000000000..4296f5b19 --- /dev/null +++ b/examples/example_rckRead_debit_test.go @@ -0,0 +1,57 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_rckReadDebit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "rck-debit.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Check Serial Number: %s\n", achFile.Batches[0].GetEntries()[0].IdentificationNumber) + + // Output: + // Total Amount Debit: 2400 + // SEC Code: RCK + // Check Serial Number: 123123123 +} diff --git a/examples/example_rckWrite_debit_test.go b/examples/example_rckWrite_debit_test.go new file mode 100644 index 000000000..e94dc24e2 --- /dev/null +++ b/examples/example_rckWrite_debit_test.go @@ -0,0 +1,77 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +func Example_rckWriteDebit() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.RCK + bh.CompanyEntryDescription = "REDEPCHECK" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "121042882" + + // Identifies the receivers account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "12345678" + entry.Amount = 2400 + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.IndividualName = "Wade Arnold" + entry.SetCheckSerialNumber("123123123") + + // build the batch + batch := ach.NewBatchRCK(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5225Name on Account 231380104 RCKREDEPCHECK 190816 1121042880000001 + // 62723138010412345678 0000002400123123123 Wade Arnold 0121042880000001 + // 82250000010023138010000000002400000000000000231380104 121042880000001 + // 9000001000001000000010023138010000000002400000000000000 +} diff --git a/examples/example_server_fileCreateJSON_test.go b/examples/example_server_fileCreateJSON_test.go new file mode 100644 index 000000000..5e84a20b1 --- /dev/null +++ b/examples/example_server_fileCreateJSON_test.go @@ -0,0 +1,70 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + lg "log" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strconv" + "time" + + "github.com/moov-io/ach/server" + + "github.com/go-kit/log" +) + +func Example_serverFileCreateJSON() { + repo := server.NewRepositoryInMemory(24*time.Hour, nil) + service := server.NewService(repo) + logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) + handler := server.MakeHTTPHandler(service, repo, logger) + + // Spin up a local HTTP server + server := httptest.NewServer(handler) + defer server.Close() + + // Read an Example ach.File in JSON format + file, err := os.Open(filepath.Join("testdata", "ppd-valid.json")) + if err != nil { + lg.Fatal(err) + } + + // Make our request + req, err := http.NewRequest("POST", server.URL+"/files/create", file) + if err != nil { + lg.Fatal(err) + } + req.Header.Set("Content-Type", "application/json") + resp, err := server.Client().Do(req) + if err != nil { + lg.Fatal(err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + lg.Fatalf("got %d HTTP status code", resp.StatusCode) + } + + fmt.Printf("%s", strconv.Itoa(resp.StatusCode)+"\n") + + // Output: 200 +} diff --git a/examples/example_server_fileCreateTextPlain_test.go b/examples/example_server_fileCreateTextPlain_test.go new file mode 100644 index 000000000..74b0ed00a --- /dev/null +++ b/examples/example_server_fileCreateTextPlain_test.go @@ -0,0 +1,70 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + lg "log" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strconv" + "time" + + "github.com/moov-io/ach/server" + + "github.com/go-kit/log" +) + +func Example_serverFileCreate() { + repo := server.NewRepositoryInMemory(24*time.Hour, nil) + service := server.NewService(repo) + logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) + handler := server.MakeHTTPHandler(service, repo, logger) + + // Spin up a local HTTP server + server := httptest.NewServer(handler) + defer server.Close() + + // Read an Example ach.File in text/plain format + file, err := os.Open(filepath.Join("testdata", "ppd-credit.ach")) + if err != nil { + lg.Fatal(err) + } + + // Make our request + req, err := http.NewRequest("POST", server.URL+"/files/create", file) + if err != nil { + lg.Fatal(err) + } + req.Header.Set("Content-Type", "text/plain") + resp, err := server.Client().Do(req) + if err != nil { + lg.Fatal(err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + lg.Fatalf("got %d HTTP status code", resp.StatusCode) + } + + fmt.Printf("%s", strconv.Itoa(resp.StatusCode)+"\n") + + // Output: 200 +} diff --git a/examples/example_shrRead_debit_test.go b/examples/example_shrRead_debit_test.go new file mode 100644 index 000000000..438a57c93 --- /dev/null +++ b/examples/example_shrRead_debit_test.go @@ -0,0 +1,61 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_shrReadDebit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "shr-debit.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("SHR Card Expiration Date: %s\n", achFile.Batches[0].GetEntries()[0].IdentificationNumber[0:4]) + fmt.Printf("SHR Document Reference Number: %s\n", achFile.Batches[0].GetEntries()[0].IdentificationNumber[4:15]) + fmt.Printf("SHR Individual Card Account Number: %s\n", achFile.Batches[0].GetEntries()[0].IndividualName) + + // Output: + // Total Amount Debit: 100000000 + // SEC Code: SHR + // SHR Card Expiration Date: 0722 + // SHR Document Reference Number: 12345678910 + // SHR Individual Card Account Number: 0001234567891123456789 +} diff --git a/examples/example_shrWrite_debit_test.go b/examples/example_shrWrite_debit_test.go new file mode 100644 index 000000000..993a04c1a --- /dev/null +++ b/examples/example_shrWrite_debit_test.go @@ -0,0 +1,93 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +func Example_shrWrite() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.SHR + bh.CompanyEntryDescription = "Payment" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "121042882" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "12345678" + entry.Amount = 100000000 + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.SetSHRCardExpirationDate("0722") + entry.SetSHRDocumentReferenceNumber("12345678910") + entry.SetSHRIndividualCardAccountNumber("1234567891123456789") + entry.DiscretionaryData = "01" + entry.AddendaRecordIndicator = 1 + + addenda02 := ach.NewAddenda02() + addenda02.ReferenceInformationOne = "REFONEA" + addenda02.ReferenceInformationTwo = "REF" + addenda02.TerminalIdentificationCode = "TERM02" + addenda02.TransactionSerialNumber = "100049" + addenda02.TransactionDate = "0614" + addenda02.AuthorizationCodeOrExpireDate = "123456" + addenda02.TerminalLocation = "Target Store 0049" + addenda02.TerminalCity = "PHILADELPHIA" + addenda02.TerminalState = "PA" + addenda02.TraceNumber = "121042880000001" + + // build the batch + batch := ach.NewBatchSHR(bh) + batch.AddEntry(entry) + batch.GetEntries()[0].Addenda02 = addenda02 + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetEntries()[0].Addenda02.String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5225Name on Account 231380104 SHRPayment 190816 1121042880000001 + // 62723138010412345678 01000000000722123456789100001234567891123456789011121042880000001 + // 702REFONEAREFTERM021000490614123456Target Store 0049 PHILADELPHIA PA121042880000001 + // 82250000020023138010000100000000000000000000231380104 121042880000001 + // 9000001000001000000020023138010000100000000000000000000 +} diff --git a/examples/example_telRead_debit_test.go b/examples/example_telRead_debit_test.go new file mode 100644 index 000000000..273113c2a --- /dev/null +++ b/examples/example_telRead_debit_test.go @@ -0,0 +1,55 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_telReadDebit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "tel-debit.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + + // Output: + // Total Amount Debit: 50000 + // SEC Code: TEL +} diff --git a/examples/example_telWrite_debit_test.go b/examples/example_telWrite_debit_test.go new file mode 100644 index 000000000..13891081a --- /dev/null +++ b/examples/example_telWrite_debit_test.go @@ -0,0 +1,74 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +func Example_telWriteDebit() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.TEL + bh.CompanyEntryDescription = "Payment" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "121042882" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "12345678" + entry.Amount = 50000 + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.IndividualName = "Receiver Account Name" + + // build the batch + batch := ach.NewBatchTEL(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5225Name on Account 231380104 TELPayment 190816 1121042880000001 + // 62723138010412345678 0000050000 Receiver Account Name 0121042880000001 + // 82250000010023138010000000050000000000000000231380104 121042880000001 + // 9000001000001000000010023138010000000050000000000000000 +} diff --git a/examples/example_trcRead_debit_test.go b/examples/example_trcRead_debit_test.go new file mode 100644 index 000000000..35d370fd8 --- /dev/null +++ b/examples/example_trcRead_debit_test.go @@ -0,0 +1,63 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_trcReadDebit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "trc-debit.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Check Serial Number: %s\n", achFile.Batches[0].GetEntries()[0].IdentificationNumber) + fmt.Printf("Process Control Field: %s\n", achFile.Batches[0].GetEntries()[0].IndividualName[0:6]) + fmt.Printf("Item Research Number: %s\n", achFile.Batches[0].GetEntries()[0].IndividualName[6:22]) + fmt.Printf("Item Type Indicator: %s\n", achFile.Batches[0].GetEntries()[0].DiscretionaryData) + + // Output: + // Total Amount Debit: 250000 + // SEC Code: TRC + // Check Serial Number: 123456789012345 + // Process Control Field: CHECK1 + // Item Research Number: 1234567890123456 + // Item Type Indicator: 01 +} diff --git a/examples/example_trcWrite_debit_test.go b/examples/example_trcWrite_debit_test.go new file mode 100644 index 000000000..b5352ca2d --- /dev/null +++ b/examples/example_trcWrite_debit_test.go @@ -0,0 +1,77 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +func Example_trcWriteDebit() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.TRC + bh.CompanyEntryDescription = "ACH TRC" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "121042882" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "12345678" + entry.Amount = 250000 + entry.SetCheckSerialNumber("123456789012345") + entry.SetProcessControlField("CHECK1") + entry.SetItemResearchNumber("1234567890123456") + entry.SetItemTypeIndicator("01") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + + // build the batch + batch := ach.NewBatchTRC(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5225Payee Name 231380104 TRCACH TRC 190816 1121042880000001 + // 62723138010412345678 0000250000123456789012345CHECK11234567890123456010121042880000001 + // 82250000010023138010000000250000000000000000231380104 121042880000001 + // 9000001000001000000010023138010000000250000000000000000 +} diff --git a/examples/example_trxRead_debit_test.go b/examples/example_trxRead_debit_test.go new file mode 100644 index 000000000..677060e19 --- /dev/null +++ b/examples/example_trxRead_debit_test.go @@ -0,0 +1,63 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_trxReadDebit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "trx-debit.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("Total Amount Credit: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Item Type Indicator: %s\n", achFile.Batches[0].GetEntries()[0].ItemTypeIndicator()) + fmt.Printf("Addenda1: %s\n", achFile.Batches[0].GetEntries()[0].Addenda05[0].String()) + fmt.Printf("Addenda2: %s\n", achFile.Batches[0].GetEntries()[0].Addenda05[1].String()) + + // Output: + // Total Amount Debit: 250000 + // Total Amount Credit: 0 + // SEC Code: TRX + // Item Type Indicator: 01 + // Addenda1: 705Debit First Account 00010000001 + // Addenda2: 705Debit Second Account 00020000001 +} diff --git a/examples/example_trxWrite_debit_test.go b/examples/example_trxWrite_debit_test.go new file mode 100644 index 000000000..51568aa20 --- /dev/null +++ b/examples/example_trxWrite_debit_test.go @@ -0,0 +1,94 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +func Example_trxWriteDebit() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.TRX + bh.CompanyEntryDescription = "ACH TRX" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "121042882" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "12345678" + entry.Amount = 250000 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(2) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.SetItemTypeIndicator("01") + + addenda1 := ach.NewAddenda05() + addenda1.PaymentRelatedInformation = "Debit First Account" + addenda1.SequenceNumber = 1 + addenda1.EntryDetailSequenceNumber = 0000001 + + addenda2 := ach.NewAddenda05() + addenda2.PaymentRelatedInformation = "Debit Second Account" + addenda2.SequenceNumber = 2 + addenda2.EntryDetailSequenceNumber = 0000001 + + // build the batch + batch := ach.NewBatchTRX(bh) + batch.AddEntry(entry) + batch.Entries[0].AddendaRecordIndicator = 1 + batch.GetEntries()[0].AddAddenda05(addenda1) + batch.GetEntries()[0].AddAddenda05(addenda2) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetEntries()[0].Addenda05[0].String()) + fmt.Println(file.Batches[0].GetEntries()[0].Addenda05[1].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5225Name on Account 231380104 TRXACH TRX 190816 1121042880000001 + // 62723138010412345678 000025000045689033 0002Receiver Company 011121042880000001 + // 705Debit First Account 00010000001 + // 705Debit Second Account 00020000001 + // 82250000030023138010000000250000000000000000231380104 121042880000001 + // 9000001000001000000030023138010000000250000000000000000 +} diff --git a/examples/example_webRead_credit_test.go b/examples/example_webRead_credit_test.go new file mode 100644 index 000000000..397f81c86 --- /dev/null +++ b/examples/example_webRead_credit_test.go @@ -0,0 +1,57 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_webReadCredit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "web-credit.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Total File Debit Amount: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("Total File Credit Amount: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + + // Output: + // SEC Code: WEB + // Total File Debit Amount: 0 + // Total File Credit Amount: 10000 +} diff --git a/examples/example_webWrite_credit_test.go b/examples/example_webWrite_credit_test.go new file mode 100644 index 000000000..4cc9c762d --- /dev/null +++ b/examples/example_webWrite_credit_test.go @@ -0,0 +1,85 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +// Example_webWriteCredit writes a WEB credit file +func Example_webWriteCredit() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.CreditsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.WEB + bh.CompanyEntryDescription = "Subscribe" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "121042882" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "12345678" + entry.Amount = 10000 + entry.IndividualName = "John Doe" + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.IdentificationNumber = "#789654" + entry.DiscretionaryData = "S" + entry.Category = ach.CategoryForward + entry.AddendaRecordIndicator = 1 + + addenda1 := ach.NewAddenda05() + addenda1.PaymentRelatedInformation = "PAY-GATE payment\\" + entry.AddAddenda05(addenda1) + + // build the batch + batch := ach.NewBatchWEB(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetEntries()[0].Addenda05[0].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5220Name on Account 231380104 WEBSubscribe 190816 1121042880000001 + // 62223138010412345678 0000010000#789654 John Doe S 1121042880000001 + // 705PAY-GATE payment\ 00010000001 + // 82200000020023138010000000000000000000010000231380104 121042880000001 + // 9000001000001000000020023138010000000000000000000010000 +} diff --git a/examples/example_xckRead_debit_test.go b/examples/example_xckRead_debit_test.go new file mode 100644 index 000000000..1faa1766a --- /dev/null +++ b/examples/example_xckRead_debit_test.go @@ -0,0 +1,61 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/moov-io/ach" +) + +func Example_xckReadDebit() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open(filepath.Join("testdata", "xck-debit.ach")) + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Check Serial Number: %s\n", achFile.Batches[0].GetEntries()[0].IdentificationNumber) + fmt.Printf("Process Control Field: %s\n", achFile.Batches[0].GetEntries()[0].IndividualName[0:6]) + fmt.Printf("Item Research Number: %s\n", achFile.Batches[0].GetEntries()[0].IndividualName[6:22]) + + // Output: + // Total Amount Debit: 250000 + // SEC Code: XCK + // Check Serial Number: 123456789012345 + // Process Control Field: CHECK1 + // Item Research Number: 1234567890123456 +} diff --git a/examples/example_xckWrite_debit_test.go b/examples/example_xckWrite_debit_test.go new file mode 100644 index 000000000..7e355610e --- /dev/null +++ b/examples/example_xckWrite_debit_test.go @@ -0,0 +1,76 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "fmt" + "log" + + "github.com/moov-io/ach" +) + +func Example_xckWriteDebit() { + fh := mockFileHeader() + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.XCK + bh.CompanyEntryDescription = "ACH XCK" + bh.EffectiveEntryDate = "190816" // need EffectiveEntryDate to be fixed so it can match output + bh.ODFIIdentification = "121042882" + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "12345678" + entry.Amount = 250000 + entry.SetCheckSerialNumber("123456789012345") + entry.SetProcessControlField("CHECK1") + entry.SetItemResearchNumber("1234567890123456") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + + // build the batch + batch := ach.NewBatchXCK(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fmt.Println(file.Header.String()) + fmt.Println(file.Batches[0].GetHeader().String()) + fmt.Println(file.Batches[0].GetEntries()[0].String()) + fmt.Println(file.Batches[0].GetControl().String()) + fmt.Println(file.Control.String()) + + // Output: + // 101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 + // 5225Payee Name 231380104 XCKACH XCK 190816 1121042880000001 + // 62723138010412345678 0000250000123456789012345CHECK11234567890123456 0121042880000001 + // 82250000010023138010000000250000000000000000231380104 121042880000001 + // 9000001000001000000010023138010000000250000000000000000 +} diff --git a/examples/http/main.go b/examples/http/main.go new file mode 100644 index 000000000..d4c8f1e4b --- /dev/null +++ b/examples/http/main.go @@ -0,0 +1,118 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Package main is an example for creating an Automated Clearing House (ACH) file with Moov's HTTP service. +// To run this example first start the ach service locally: +// +// $ go run ./cmd/server // from this project's root directory +// +// Then, in a second terminal you can run this example: +// +// $ go run ./examples/http // from project root +package main + +import ( + "bytes" + "encoding/json" + "io" + "log" + "net/http" + "time" + + "github.com/moov-io/ach" +) + +var ( + // achAddress refers to the local host and port for the ACH service running locally + achAddress = "http://localhost:8080" +) + +func main() { + // Example transfer to write an ACH PPD file to send/credit a external institutions account + // Important: All financial institutions are different and will require registration and exact field values. + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "121042882" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") + fh.ImmediateDestinationName = "Receiver Bank Name" + fh.ImmediateOriginName = "Origin Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.CreditsOnly + bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.PPD + bh.CompanyEntryDescription = "REG.SALARY" // will be on receiving accounts statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" // Originating Routing Number + + // Identifies the receivers account information + // can be multiple entry's per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = 22 // Code 22: Credit (deposit) to checking account + entry.SetRDFI("231380104") // Receivers bank transit routing number + entry.DFIAccountNumber = "12345678" // Receivers bank account number + entry.Amount = 100000000 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.IndividualName = "Receiver Account Name" // Identifies the receiver of the transaction + + // build the batch + batch := ach.NewBatchPPD(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Encode our ACH File as JSON for the upload... + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(&file); err != nil { + log.Fatal(err) + } + + // Make our HTTP request to the ACH service + req, err := http.NewRequest("POST", achAddress+"/files/create", &buf) + if err != nil { + log.Fatal(err) + } + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + + if resp.StatusCode == 200 { + log.Printf("File created!") + } else { + bs, _ := io.ReadAll(resp.Body) + log.Fatalf("error creating file: %v", string(bs)) + } +} diff --git a/examples/mock.go b/examples/mock.go new file mode 100644 index 000000000..39bbcfa3d --- /dev/null +++ b/examples/mock.go @@ -0,0 +1,35 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package examples + +import ( + "github.com/moov-io/ach" +) + +func mockFileHeader() ach.FileHeader { + fh := ach.NewFileHeader() + fh.ImmediateDestination = "031300012" + fh.ImmediateOrigin = "231380104" + // need FileCreationDate and FileCreationTime to be fixed so it can match output + fh.FileCreationDate = "190816" + fh.FileCreationTime = "1055" + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + fh.ReferenceCode = "12345678" + return fh +} diff --git a/examples/testdata/ack-read.ach b/examples/testdata/ack-read.ach new file mode 100644 index 000000000..6c8f6d7e2 --- /dev/null +++ b/examples/testdata/ack-read.ach @@ -0,0 +1,10 @@ +101 031300012 2313801041908161055A094101Federal Reserve Bank My Bank Name +5220Name on Account 231380104 ACKVndr Pay 190816 1231380100000001 +624031300012744-5678-99 0000000000031300010000001Best. #1 0231380100000001 +624031300012744-5678-99 0000000000031300010000002Best. #1 0231380100000002 +82200000020006260002000000000000000000000000231380104 231380100000001 +9000001000001000000020006260002000000000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/adv-read.ach b/examples/testdata/adv-read.ach new file mode 100644 index 000000000..a93b197a4 --- /dev/null +++ b/examples/testdata/adv-read.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821908161055A094101Federal Reserve Bank My Bank Name +5280Company Name, In 121042882 ADVAccounting 190816 0121042880000001 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500001 +682231380104744-5678-99 00000025000012104288211139 Name 0011000010500002 +828000000200462760200000000000000025000000000000000000050000Company Name, Inc 121042880000001 +90000010000010000000200462760200000000000000025000000000000000000050000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/arc-debit.ach b/examples/testdata/arc-debit.ach new file mode 100644 index 000000000..af8831316 --- /dev/null +++ b/examples/testdata/arc-debit.ach @@ -0,0 +1,10 @@ +101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 +5225Payee Name 231380104 ARCACH ARC 190816 1121042880000001 +62723138010412345678 0000250000123879654 ABC Company 0121042880000001 +82250000010023138010000000250000000000000000231380104 121042880000001 +9000001000001000000010023138010000000250000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/atx-read.ach b/examples/testdata/atx-read.ach new file mode 100644 index 000000000..5ed3027c8 --- /dev/null +++ b/examples/testdata/atx-read.ach @@ -0,0 +1,10 @@ +101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 +5220Name on Account 231380104 ATXVndr Pay 190816 1231380100000001 +624031300012744-5678-99 00000000000313000100000010002Receiver Company 011231380100000001 +705Credit account 1 for service 00010000001 +705Credit account 2 for service 00020000001 +624031300012744-5678-99 00000000000313000100000020002Receiver Company 011231380100000002 +705Credit account 1 for leadership 00010000002 +705Credit account 2 for leadership 00020000002 +82200000060006260002000000000000000000000000231380104 231380100000001 +9000001000001000000060006260002000000000000000000000000 \ No newline at end of file diff --git a/examples/testdata/boc-debit.ach b/examples/testdata/boc-debit.ach new file mode 100644 index 000000000..62b035552 --- /dev/null +++ b/examples/testdata/boc-debit.ach @@ -0,0 +1,10 @@ +101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 +5225Payee Name 231380104 BOCACH BOC 190816 1121042880000001 +62723138010412345678 0000250000123879654 ABC Company 0121042880000001 +82250000010023138010000000250000000000000000231380104 121042880000001 +9000001000001000000010023138010000000250000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/ccd-debit.ach b/examples/testdata/ccd-debit.ach new file mode 100644 index 000000000..a3111fc27 --- /dev/null +++ b/examples/testdata/ccd-debit.ach @@ -0,0 +1,10 @@ +101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 +5225Name on Account 231380104 CCDVndr Pay 190816 1031300010000001 +627231380104744-5678-99 0000500000location1234567Best Co. #123456789012S 0031300010000001 +627231380104744-5678-99 0000000125Fee123456789012Best Co. #123456789012S 0031300010000002 +82250000020046276020000000500125000000000000231380104 031300010000001 +9000001000001000000020046276020000000500125000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/cie-credit.ach b/examples/testdata/cie-credit.ach new file mode 100644 index 000000000..f994aa266 --- /dev/null +++ b/examples/testdata/cie-credit.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821806200000A094101Federal Reserve Bank My Bank Name +5220Name on Account 121042882 CIEPayment 180621 1121042880000001 +62223138010412345678 0100000000 Receiver Account Name 011121042880000001 +705Credit Store Account 00010000001 +82200000020023138010000000000000000100000000121042882 121042880000001 +9000001000001000000020023138010000000000000000100000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/contested-return.ach b/examples/testdata/contested-return.ach new file mode 100644 index 000000000..d53bd05f2 --- /dev/null +++ b/examples/testdata/contested-return.ach @@ -0,0 +1,10 @@ +101 231380104 1210428822208172357A094101Federal Reserve Bank My Bank Name +5220ACME Corporation 121042882 PPDPAYROLL 000000 1121042880000001 +622121042882123456789 0100000000ABC##jvkdjfuiwnWade Arnold 1121042880000001 +799R07099912340000015 09101298Authorization Revoked 000000000000000 +799R68059999990000301 12391871 12391871000000117901Untimely Return 059999990000001 +799R71059999990000301000167123918711647799999900003011650188999999000030116667 123918710000001 +82200000040012104288000000000000000100000000121042882 121042880000001 +9000001000001000000040012104288000000000000000100000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/examples/testdata/cor-read.ach b/examples/testdata/cor-read.ach new file mode 100644 index 000000000..3ce12a523 --- /dev/null +++ b/examples/testdata/cor-read.ach @@ -0,0 +1,10 @@ +101 23138010401210428821908161055A094101Federal Reserve Bank My Bank Name +5220Your Company, in 121042882 CORVendor Pay 190816 1121042880000001 +621231380104744-5678-99 0000000000location #23 Best Co. #23 1121042880000001 +798C01121042880000001 121042881918171614 091012980000088 +82200000020023138010000000000000000000000000121042882 121042880000001 +9000001000001000000020023138010000000000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/ctx-debit.ach b/examples/testdata/ctx-debit.ach new file mode 100644 index 000000000..984b15252 --- /dev/null +++ b/examples/testdata/ctx-debit.ach @@ -0,0 +1,10 @@ +101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 +5225Name on Account 231380104 CTXACH CTX 190816 1121042880000001 +62723138010412345678 010000000045689033 0002Receiver Company 011121042880000001 +705Debit First Account 00010000001 +705Debit Second Account 00020000001 +82250000030023138010000100000000000000000000231380104 121042880000001 +9000001000001000000030023138010000100000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/dishonored-return.ach b/examples/testdata/dishonored-return.ach new file mode 100644 index 000000000..39c2826c6 --- /dev/null +++ b/examples/testdata/dishonored-return.ach @@ -0,0 +1,10 @@ +101 231380104 1210428822208172357A094101Federal Reserve Bank My Bank Name +5225Payee Name 231380104 POSACH POS 000000 1231380100000001 +627121042882744-5678-99 000002500045689033 Wade Arnold 011231380100000001 +799R68059999990000301 12391871 12391871000000117901Untimely Return 059999990000001 +627121042882744-5678-99 000002300045689033 Adam Decaf 011231380100000002 +799R68059999990000301 12391871 12391871000000117901Untimely Return 059999990000001 +82250000040024208576000000048000000000000000231380104 231380100000001 +9000001000001000000040024208576000000048000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/examples/testdata/dne-read.ach b/examples/testdata/dne-read.ach new file mode 100644 index 000000000..0d4d48463 --- /dev/null +++ b/examples/testdata/dne-read.ach @@ -0,0 +1,10 @@ +101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 +5220Name on Account 231380104 DNEDeath 190816 2231380100000001 +621031300012744-5678-99 0000000000031300010000001Best. #1 1231380100000001 +705 DATE OF DEATH*010218*CUSTOMERSSN*#########*AMOUNT*$$$$.cc\ 00010000001 +82200000020003130001000000000000000000000000231380104 231380100000001 +9000001000001000000020003130001000000000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/enr-read.ach b/examples/testdata/enr-read.ach new file mode 100644 index 000000000..fa4402157 --- /dev/null +++ b/examples/testdata/enr-read.ach @@ -0,0 +1,10 @@ +101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 +5225Name on Account 231380104 ENRAUTOENROLL 1231380100000001 +627031300012744-5678-99 0000000000031300010000001Best. #1 1231380100000001 +70522*12200004*3*123987654321*777777777*DOE*JOHN*1\ 00010000001 +82250000020003130001000000000000000000000000231380104 231380100000001 +9000001000001000000020003130001000000000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/iat-mixedCreditDebit.ach b/examples/testdata/iat-mixedCreditDebit.ach new file mode 100644 index 000000000..3399008e0 --- /dev/null +++ b/examples/testdata/iat-mixedCreditDebit.ach @@ -0,0 +1,30 @@ +101 12104288202313801041908161055A094101Bank My Bank Name +5200 FF3 US123456789 IATTRADEPAYMTCADUSD190816 0231380100000001 +6271210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +718Bank of France 01456456456987987 FR 00010000001 +6221210428820007 0000100000123456789 1231380100000002 +710ANN000000000000100000928383-23938 ADCAF Enterprises 0000002 +711ADCAF Solutions 15 West Place Street 0000002 +712JacobsTown*PA\ US*19305\ 0000002 +713Wells Fargo 01231380104 US 0000002 +714Citadel Bank 01121042882 CA 0000002 +71598746549321398718 Fifth Street 0000002 +716LetterTown*AB\ CA*80014\ 0000002 +717This is an international payment 00010000002 +718Bank of France 01456456456987987 FR 00010000002 +82000000200024208576000000100000000000100000 231380100000001 +9000001000003000000200024208576000000100000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/mte-read.ach b/examples/testdata/mte-read.ach new file mode 100644 index 000000000..3238f701f --- /dev/null +++ b/examples/testdata/mte-read.ach @@ -0,0 +1,10 @@ +101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 +5225Merchant with AT 231380104 MTECASH WITHD 190816 1231380100000001 +627031300012744-5678-99 0000010000031300010000001JANE DOE 1231380100000001 +702 2005091234561224 321 East Market Street ANYTOWN VA231380100000001 +82250000020003130001000000010000000000000000231380104 231380100000001 +9000001000001000000020003130001000000010000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/examples/testdata/pop-debit.ach b/examples/testdata/pop-debit.ach new file mode 100644 index 000000000..69f0218a7 --- /dev/null +++ b/examples/testdata/pop-debit.ach @@ -0,0 +1,10 @@ +101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 +5225Originator 231380104 POPACH POP 190816 1121042880000001 +62723138010412345678 0000250500123456789PHILPAWade Arnold 0121042880000001 +82250000010023138010000000250500000000000000231380104 121042880000001 +9000001000001000000010023138010000000250500000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/pos-debit.ach b/examples/testdata/pos-debit.ach new file mode 100644 index 000000000..b06bf165c --- /dev/null +++ b/examples/testdata/pos-debit.ach @@ -0,0 +1,10 @@ +101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 +5225Name on Account 231380104 POSSale 190816 1121042880000001 +62723138010412345678 0100000000 Receiver Account Name 011121042880000001 +702REFONEAREFTERM021000490614123456Target Store 0049 PHILADELPHIA PA121042880000001 +82250000020023138010000100000000000000000000231380104 121042880000001 +9000001000001000000020023138010000100000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/ppd-credit.ach b/examples/testdata/ppd-credit.ach new file mode 100644 index 000000000..97f8e70eb --- /dev/null +++ b/examples/testdata/ppd-credit.ach @@ -0,0 +1,10 @@ +101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 +5220Name on Account 231380104 PPDREG.SALARY 190816 1121042880000001 +622231380104987654321 0100000000 Credit Account 1 0121042880000002 +82200000010023138010000000000000000100000000231380104 121042880000001 +9000001000001000000010023138010000000000000000100000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/ppd-debit.ach b/examples/testdata/ppd-debit.ach new file mode 100644 index 000000000..171795ff9 --- /dev/null +++ b/examples/testdata/ppd-debit.ach @@ -0,0 +1,10 @@ +101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 +5225Name on Account 231380104 PPDREG.SALARY 190816 1121042880000001 +627231380104123456789 0200000000 Debit Account 0121042880000001 +82250000010023138010000200000000000000000000231380104 121042880000001 +9000001000001000000010023138010000200000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/ppd-mixedDebitCredit.ach b/examples/testdata/ppd-mixedDebitCredit.ach new file mode 100644 index 000000000..a0425e652 --- /dev/null +++ b/examples/testdata/ppd-mixedDebitCredit.ach @@ -0,0 +1,10 @@ +101 23138010401210428821907181055A094101Federal Reserve Bank My Bank Name +5200Name on Account 121042882 PPDREG.SALARY 190719 1121042880000001 +627231380104123456789 0200000000 Debit Account 0121042880000001 +622231380104987654321 0100000000 Credit Account 1 0121042880000002 +622231380104837098765 0100000000 Credit Account 2 0121042880000003 +82000000030069414030000200000000000200000000121042882 121042880000001 +9000001000001000000030069414030000200000000000200000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/ppd-valid.json b/examples/testdata/ppd-valid.json new file mode 100644 index 000000000..9d4608f44 --- /dev/null +++ b/examples/testdata/ppd-valid.json @@ -0,0 +1,67 @@ +{ + "id": "adam-01", + "fileHeader": { + "id": "adam-01", + "immediateDestination": "231380104", + "immediateOrigin": "121042882", + "fileCreationDate": "181008", + "fileCreationTime": "", + "fileIDModifier": "A", + "immediateDestinationName": "Citadel", + "immediateOriginName": "Wells Fargo" + }, + "batches": [ + { + "batchHeader": { + "id": "adam-01", + "serviceClassCode": 200, + "companyName": "Wells Fargo", + "companyIdentification": "121042882", + "standardEntryClassCode": "PPD", + "companyEntryDescription": "Trans. Des", + "effectiveEntryDate": "2018-10-09T00:00:00Z", + "ODFIIdentification": "12104288", + "batchNumber": 1 + }, + "entryDetails": [ + { + "id": "adam-01", + "transactionCode": 22, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "81967038518 ", + "amount": 100000, + "identificationNumber": "#83738AB# ", + "individualName": "Steven Tander ", + "discretionaryData": " ", + "addendaRecordIndicator": 0, + "traceNumber": "121042880000001", + "category": "Forward" + } + ], + "batchControl": { + "id": "adam-01", + "serviceClassCode": 200, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000, + "companyIdentification": "121042882", + "ODFIIdentification": "12104288", + "batchNumber": 1 + } + } + ], + "IATBatches": null, + "fileControl": { + "id": "adam-01", + "batchCount": 1, + "blockCount": 1, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000 + }, + "NotificationOfChange": null, + "ReturnEntries": null +} diff --git a/examples/testdata/rck-debit.ach b/examples/testdata/rck-debit.ach new file mode 100644 index 000000000..79e5f6e11 --- /dev/null +++ b/examples/testdata/rck-debit.ach @@ -0,0 +1,10 @@ +101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 +5225Name on Account 231380104 RCKREDEPCHECK 190816 1121042880000001 +62723138010412345678 0000002400123123123 Wade Arnold 0121042880000001 +82250000010023138010000000002400000000000000231380104 121042880000001 +9000001000001000000010023138010000000002400000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/segmentFile-ppd-credit.ach b/examples/testdata/segmentFile-ppd-credit.ach new file mode 100644 index 000000000..c6fb00153 --- /dev/null +++ b/examples/testdata/segmentFile-ppd-credit.ach @@ -0,0 +1,10 @@ +101 23138010401210428821907191341A094101Federal Reserve Bank My Bank Name +5220Name on Account 121042882 PPDREG.SALARY 190719 1121042880000001 +622231380104987654321 0100000000 Credit Account 1 0121042880000001 +622231380104837098765 0100000000 Credit Account 2 0121042880000002 +82200000020046276020000000000000000200000000121042882 121042880000001 +9000001000001000000020046276020000000000000000200000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/segmentFile-ppd-debit.ach b/examples/testdata/segmentFile-ppd-debit.ach new file mode 100644 index 000000000..8b5e225f8 --- /dev/null +++ b/examples/testdata/segmentFile-ppd-debit.ach @@ -0,0 +1,10 @@ +101 23138010401210428821907191341A094101Federal Reserve Bank My Bank Name +5225Name on Account 121042882 PPDREG.SALARY 190719 1121042880000001 +627231380104123456789 0200000000 Debit Account 0121042880000001 +82250000010023138010000200000000000000000000121042882 121042880000001 +9000001000001000000010023138010000200000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/shr-debit.ach b/examples/testdata/shr-debit.ach new file mode 100644 index 000000000..9d2d48d54 --- /dev/null +++ b/examples/testdata/shr-debit.ach @@ -0,0 +1,10 @@ +101 23138010401210428821908161111A094101Federal Reserve Bank My Bank Name +5225Name on Account 121042882 SHRPayment 190823 1121042880000001 +62723138010412345678 01000000000722123456789100001234567891123456789011121042880000001 +702REFONEAREFTERM021000490614123456Target Store 0049 PHILADELPHIA PA121042880000001 +82250000020023138010000100000000000000000000121042882 121042880000001 +9000001000001000000020023138010000100000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/tel-debit.ach b/examples/testdata/tel-debit.ach new file mode 100644 index 000000000..cfaefb39a --- /dev/null +++ b/examples/testdata/tel-debit.ach @@ -0,0 +1,10 @@ +101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 +5225Name on Account 231380104 TELPayment 190816 1121042880000001 +62723138010412345678 0000050000 Receiver Account Name 0121042880000001 +82250000010023138010000000050000000000000000231380104 121042880000001 +9000001000001000000010023138010000000050000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/trc-debit.ach b/examples/testdata/trc-debit.ach new file mode 100644 index 000000000..300d123d3 --- /dev/null +++ b/examples/testdata/trc-debit.ach @@ -0,0 +1,10 @@ +101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 +5225Payee Name 231380104 TRCACH TRC 190816 1121042880000001 +62723138010412345678 0000250000123456789012345CHECK11234567890123456010121042880000001 +82250000010023138010000000250000000000000000231380104 121042880000001 +9000001000001000000010023138010000000250000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/trx-debit.ach b/examples/testdata/trx-debit.ach new file mode 100644 index 000000000..4ce671cc6 --- /dev/null +++ b/examples/testdata/trx-debit.ach @@ -0,0 +1,10 @@ +101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 +5225Name on Account 231380104 TRXACH TRX 190816 1121042880000001 +62723138010412345678 000025000045689033 0002Receiver Company 011121042880000001 +705Debit First Account 00010000001 +705Debit Second Account 00020000001 +82250000030023138010000000250000000000000000231380104 121042880000001 +9000001000001000000030023138010000000250000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/web-credit.ach b/examples/testdata/web-credit.ach new file mode 100644 index 000000000..7ace7e0ff --- /dev/null +++ b/examples/testdata/web-credit.ach @@ -0,0 +1,10 @@ +101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 +5220Name on Account 231380104 WEBSubscribe 190816 1121042880000001 +62223138010412345678 0000010000#789654 John Doe S 1121042880000001 +705PAY-GATE payment\ 00010000001 +82200000020023138010000000000000000000010000231380104 121042880000001 +9000001000001000000020023138010000000000000000000010000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/examples/testdata/xck-debit.ach b/examples/testdata/xck-debit.ach new file mode 100644 index 000000000..5052e8be8 --- /dev/null +++ b/examples/testdata/xck-debit.ach @@ -0,0 +1,10 @@ +101 03130001202313801041908161055A094101Federal Reserve Bank My Bank Name 12345678 +5225Payee Name 231380104 XCKACH XCK 190816 1121042880000001 +62723138010412345678 0000250000123456789012345CHECK11234567890123456 0121042880000001 +82250000010023138010000000250000000000000000231380104 121042880000001 +9000001000001000000010023138010000000250000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/fieldErrors.go b/fieldErrors.go new file mode 100644 index 000000000..b3842bcc7 --- /dev/null +++ b/fieldErrors.go @@ -0,0 +1,200 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "errors" + "fmt" +) + +var ( + // Errors specific to validation + + //ErrNonAlphanumeric is given when a field has non-alphanumeric characters + ErrNonAlphanumeric = errors.New("has non alphanumeric characters") + //ErrUpperAlpha is given when a field is not in uppercase + ErrUpperAlpha = errors.New("is not uppercase A-Z or 0-9") + //ErrFieldInclusion is given when a field is mandatory and has a default value + ErrFieldInclusion = errors.New("is a mandatory field and has a default value") + //ErrConstructor is given when there's a mandatory field is not initialized correctly, and prompts to use the constructor + ErrConstructor = errors.New("is a mandatory field and has a default value, did you use the constructor?") + //ErrFieldRequired is given when a field is required + ErrFieldRequired = errors.New("is a required field") + //ErrServiceClass is given when there's an invalid service class code + ErrServiceClass = errors.New("is an invalid Service Class Code") + //ErrSECCode is given when there's an invalid standard entry class code + ErrSECCode = errors.New("is an invalid Standard Entry Class Code") + //ErrOrigStatusCode is given when there's an invalid originator status code + ErrOrigStatusCode = errors.New("is an invalid Originator Status Code") + //ErrAddendaTypeCode is given when there's an invalid addenda type code + ErrAddendaTypeCode = errors.New("is an invalid Addenda Type Code") + //ErrTransactionCode is given when there's an invalid transaction code + ErrTransactionCode = errors.New("is an invalid Transaction Code") + //ErrIdentificationNumber is given when there's an invalid identification number + ErrIdentificationNumber = errors.New("is an invalid identification number") + //ErrCardTransactionType is given when there's an invalid card transaction type + ErrCardTransactionType = errors.New("is an invalid Card Transaction Type") + //ErrValidMonth is given when there's an invalid month + ErrValidMonth = errors.New("is an invalid month") + //ErrValidDay is given when there's an invalid day + ErrValidDay = errors.New("is an invalid day") + //ErrValidYear is given when there's an invalid year + ErrValidYear = errors.New("is an invalid year") + // ErrValidState is the error given when a field has an invalid US state or territory + ErrValidState = errors.New("is an invalid US state or territory") + // ErrValidISO3166 is the error given when a field has an invalid ISO 3166-1-alpha-2 code + ErrValidISO3166 = errors.New("is an invalid ISO 3166-1-alpha-2 code") + // ErrValidISO4217 is the error given when a field has an invalid ISO 4217 code + ErrValidISO4217 = errors.New("is an invalid ISO 4217 code") + + // EntryDetail errors + + // ErrNegativeAmount is the error given when an Amount value is negaitve, which is + // against NACHA rules and guidelines. + ErrNegativeAmount = errors.New("amounts cannot be negative") + + // Addenda errors + + // ErrAddenda98ChangeCode is given when there's an invalid addenda change code + ErrAddenda98ChangeCode = errors.New("found is not a valid addenda Change Code") + // ErrAddenda98CorrectedData is given when the corrected data does not corespond to the change code + ErrAddenda98CorrectedData = errors.New("must contain the corrected information corresponding to the Change Code") + // ErrAddenda99ReturnCode is given when there's an invalid return code + ErrAddenda99ReturnCode = errors.New("found is not a valid return code") + // ErrAddenda99DishonoredReturnCode is given when there's an invalid dishonored return code + ErrAddenda99DishonoredReturnCode = errors.New("found is not a valid dishonored return code") + // ErrAddenda99ContestedReturnCode is given when there's an invalid dishonored return code + ErrAddenda99ContestedReturnCode = errors.New("found is not a valid contested dishonored return code") + // ErrBatchCORAddenda is given when an entry in a COR batch does not have an addenda98 + ErrBatchCORAddenda = errors.New("one Addenda98 record is required for each entry in SEC Type COR") + + // FileHeader errors + + // ErrRecordSize is given when there's an invalid record size + ErrRecordSize = errors.New("is not 094") + // ErrBlockingFactor is given when there's an invalid blocking factor + ErrBlockingFactor = errors.New("is not 10") + // ErrFormatCode is given when there's an invalid format code + ErrFormatCode = errors.New("is not 1") + + // IAT + + // ErrForeignExchangeIndicator is given when there's an invalid foreign exchange indicator + ErrForeignExchangeIndicator = errors.New("is an invalid Foreign Exchange Indicator") + // ErrForeignExchangeReferenceIndicator is given when there's an invalid foreign exchange reference indicator + ErrForeignExchangeReferenceIndicator = errors.New("is an invalid Foreign Exchange Reference Indicator") + // ErrTransactionTypeCode is given when there's an invalid transaction type code + ErrTransactionTypeCode = errors.New("is an invalid Addenda10 Transaction Type Code") + // ErrIDNumberQualifier is given when there's an invalid identification number qualifier + ErrIDNumberQualifier = errors.New("is an invalid Identification Number Qualifier") + // ErrIATBatchAddendaIndicator is given when there's an invalid addenda record for an IAT batch + ErrIATBatchAddendaIndicator = errors.New("is invalid for addenda record(s) found") +) + +// FieldError is returned for errors at a field level in a record +type FieldError struct { + FieldName string // field name where error happened + Value interface{} // value that cause error + Err error // context of the error. + Msg string // deprecated +} + +// Error message is constructed +// FieldName Msg Value +// Example1: BatchCount $% has none alphanumeric characters +// Example2: BatchCount 5 is out-of-balance with file count 6 +func (e *FieldError) Error() string { + return fmt.Sprintf("%s %v %s", e.FieldName, e.Value, e.Err) +} + +// Unwrap implements the base.UnwrappableError interface for FieldError +func (e *FieldError) Unwrap() error { + return e.Err +} + +func fieldError(field string, err error, values ...interface{}) error { + if err == nil { + return nil + } + if _, ok := err.(*FieldError); ok { + return err + } + fe := FieldError{ + FieldName: field, + Err: err, + } + // only the first value counts + if len(values) > 0 { + fe.Value = values[0] + } + return &fe +} + +// ErrValidCheckDigit is the error given when the observed check digit does not match the calculated one +type ErrValidCheckDigit struct { + Message string + CalculatedCheckDigit int +} + +// NewErrValidCheckDigit creates a new error of the ErrValidCheckDigit type +func NewErrValidCheckDigit(digit int) ErrValidCheckDigit { + return ErrValidCheckDigit{ + Message: fmt.Sprintf("does not match calculated check digit %v", digit), + CalculatedCheckDigit: digit, + } +} + +func (e ErrValidCheckDigit) Error() string { + return e.Message +} + +// ErrValidFieldLength is the error given when the field does not have the correct length +type ErrValidFieldLength struct { + Message string + ExpectedLength int +} + +// NewErrValidFieldLength creates a new error of the ErrValidFieldLength type +func NewErrValidFieldLength(expectedLength int) ErrValidFieldLength { + return ErrValidFieldLength{ + Message: fmt.Sprintf("is not length %v", expectedLength), + ExpectedLength: expectedLength, + } +} + +func (e ErrValidFieldLength) Error() string { + return e.Message +} + +// ErrRecordType is the error given when the field does not have the right record type +type ErrRecordType struct { + Message string + ExpectedType int +} + +// NewErrRecordType creates a new error of the ErrRecordType type +func NewErrRecordType(expectedType int) ErrRecordType { + return ErrRecordType{ + Message: fmt.Sprintf("received expecting %v", expectedType), + ExpectedType: expectedType, + } +} + +func (e ErrRecordType) Error() string { + return e.Message +} diff --git a/file.go b/file.go index b4c1a6eb1..158815f4b 100644 --- a/file.go +++ b/file.go @@ -1,16 +1,32 @@ -// Copyright 2017 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. - -// Package ach reads and writes (ACH) Automated Clearing House files. ACH is the -// primary method of electronic money movement through the United States. +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // -// https://en.wikipedia.org/wiki/Automated_Clearing_House +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + package ach import ( + "bytes" + "encoding/json" + "errors" "fmt" - "strconv" + "strings" + "sync" + "time" + + "github.com/moov-io/base" ) // First position of all Record Types. These codes are uniquely assigned to @@ -27,28 +43,6 @@ const ( RecordLength = 94 ) -// currently supported SEC codes -const ( - ppd = "PPD" - web = "WEB" - ccd = "CCD" - cor = "COR" -) - -// Errors strings specific to parsing a Batch container -var ( - msgFileControlEquality = "header %v is not equal to control %v" - msgFileCalculatedControlEquality = "calculated %v is out-of-balance with control %v" - // specific messages - msgRecordLength = "must be 94 characters and found %d" - msgFileBatchOutside = "outside of current batch" - msgFileBatchInside = "inside of current batch" - msgFileControl = "none or more than one file control exists" - msgFileHeader = "none or more than one file headers exists" - msgUnknownRecordType = "%s is an unknown record type" - msgFileNoneSEC = "%v SEC(standard entry class) is not implemented" -) - // FileError is an error describing issues validating a file type FileError struct { FieldName string @@ -56,57 +50,818 @@ type FileError struct { Msg string } -func (e *FileError) Error() string { +func (e FileError) Error() string { return fmt.Sprintf("%s %s", e.FieldName, e.Msg) } // File contains the structures of a parsed ACH File. type File struct { - Header FileHeader - Batches []Batcher - Control FileControl + ID string `json:"id"` + Header FileHeader `json:"fileHeader"` + Batches []Batcher `json:"batches"` + IATBatches []IATBatch `json:"IATBatches"` + Control FileControl `json:"fileControl"` + ADVControl ADVFileControl `json:"fileADVControl"` - converters -} + // NotificationOfChange (Notification of change) is a slice of references to BatchCOR in file.Batches + NotificationOfChange []Batcher `json:"NotificationOfChange"` -// FileParam is the minimal fields required to make a ach file header -type FileParam struct { - // ImmediateDestination is the originating banks ABA routing number. Frequently your banks ABA routing number. - ImmediateDestination string `json:"immediate_destination"` - // ImmediateOrigin - ImmediateOrigin string `json:"immediate_origin"` - // ImmediateDestinationName is the originating banks name. - ImmediateDestinationName string `json:"immediate_destination_name"` - ImmediateOriginName string `json:"immediate_origin_name"` - ReferenceCode string `json:"reference_code,omitempty"` + // ReturnEntries is a slice of references to file.Batches that contain return entries + ReturnEntries []Batcher `json:"ReturnEntries"` + + validateOpts *ValidateOpts } // NewFile constructs a file template. -func NewFile(params ...FileParam) *File { - if len(params) > 0 { - fh := NewFileHeader(params[0]) - return &File{ - Header: fh, +func NewFile() *File { + return &File{ + Header: NewFileHeader(), + Control: NewFileControl(), + } +} + +type file struct { + ID string `json:"id"` +} + +type fileHeader struct { + Header FileHeader `json:"fileHeader"` +} + +type fileControl struct { + Control FileControl `json:"fileControl"` +} + +type advFileControl struct { + ADVControl ADVFileControl `json:"advFileControl"` +} + +// FileFromJSON attempts to return a *File object assuming the input is valid JSON. +// +// Callers should always check for a nil-error before using the returned file. +// +// The File returned may not be valid and an error may be returned from validation. +// Invalid files may be rejected by Financial Institutions or ACH tools. +// +// Date and Time fields in formats: RFC 3339 and ISO 8601 will be parsed and rewritten +// as their YYMMDD (year, month, day) or hhmm (hour, minute) formats. +func FileFromJSON(bs []byte) (*File, error) { + return FileFromJSONWith(bs, nil) +} + +// FileFromJSONWith attempts to return a *File object assuming the input is valid JSON. +// +// It allows custom validation overrides, so the file may not be Nacha compliant +// after parsing. Invalid files may be rejected by Financial Institutions or ACH tools. +// +// Callers should always check for a nil-error before using the returned file. +// +// Date and Time fields in formats: RFC 3339 and ISO 8601 will be parsed and rewritten +// as their YYMMDD (year, month, day) or hhmm (hour, minute) formats. +func FileFromJSONWith(bs []byte, opts *ValidateOpts) (*File, error) { + if len(bs) == 0 { + return nil, errors.New("no JSON data provided") + } + + // read file root level + var f file + file := NewFile() + if err := json.NewDecoder(bytes.NewReader(bs)).Decode(&f); err != nil { + return nil, fmt.Errorf("problem reading File: %v", err) + } + file.ID = f.ID + if opts != nil { + file.SetValidation(opts) + } + + // Read FileHeader + header := fileHeader{ + Header: file.Header, + } + if err := json.NewDecoder(bytes.NewReader(bs)).Decode(&header); err != nil { + return nil, fmt.Errorf("problem reading FileHeader: %v", err) + } + file.Header = header.Header + + // Build resulting file + if err := file.setBatchesFromJSON(bs); err != nil { + return nil, err + } + + // Overwrite various timestamps with their ACH formatted values + file.overwriteDateTimeFields() + + if !file.IsADV() { + // Read FileControl + control := fileControl{ Control: NewFileControl(), } + if err := json.NewDecoder(bytes.NewReader(bs)).Decode(&control); err != nil { + return nil, fmt.Errorf("problem reading FileControl: %v", err) + } + file.Control = control.Control + } else { + // Read ADVFileControl + advControl := advFileControl{ + ADVControl: NewADVFileControl(), + } + if err := json.NewDecoder(bytes.NewReader(bs)).Decode(&advControl); err != nil { + return nil, fmt.Errorf("problem reading ADVFileControl: %v", err) + } + file.ADVControl = advControl.ADVControl + } + if !file.IsADV() { + file.Control.BatchCount = len(file.Batches) + } else { + file.ADVControl.BatchCount = len(file.Batches) } - return &File{ - Header: NewFileHeader(), - Control: NewFileControl(), + + if opts != nil { + file.SetValidation(opts) + } + if err := file.Create(); err != nil { + return file, err + } + if err := file.Validate(); err != nil { + return file, err + } + return file, nil +} + +// MarshalJSON will produce a JSON blob with the ACH file's fields and validation settings. +func (f *File) MarshalJSON() ([]byte, error) { + type Aux struct { + File + ValidateOpts *ValidateOpts `json:"validateOpts"` + } + return json.Marshal(Aux{ + File: *f, + ValidateOpts: f.validateOpts, + }) +} + +// UnmarshalJSON parses a JSON blob with ach.FileFromJSON +func (f *File) UnmarshalJSON(p []byte) error { + file, err := FileFromJSONWith(p, f.validateOpts) + if err != nil { + return err + } + if file != nil { + *f = *file + } + opts, err := readValidateOpts(p) + if err != nil { + return err + } + f.SetValidation(opts) + return nil +} + +func readValidateOpts(p []byte) (*ValidateOpts, error) { + type Aux struct { + ValidateOpts *ValidateOpts `json:"validateOpts"` + } + var opts Aux + err := json.Unmarshal(p, &opts) + if err != nil { + return nil, err + } + return opts.ValidateOpts, nil +} + +type batchesJSON struct { + Batches []*Batch `json:"batches"` +} + +type iatBatchesJSON struct { + IATBatches []IATBatch `json:"iatBatches"` +} + +func setEntryRecordType(e *EntryDetail) { + if e.Addenda02 != nil { + e.Addenda02.TypeCode = "02" + } + for _, a := range e.Addenda05 { + a.TypeCode = "05" + } + if e.Addenda98 != nil { + e.Addenda98.TypeCode = "98" + } + if e.Addenda99 != nil { + e.Addenda99.TypeCode = "99" + } + if e.Addenda99Dishonored != nil { + e.Addenda99Dishonored.TypeCode = "99" + } + if e.Addenda99Contested != nil { + e.Addenda99Contested.TypeCode = "99" } } -// Create creates a valid file and requires that the FileHeader and at least one Batch +func setADVEntryRecordType(e *ADVEntryDetail) { + if e.Addenda99 == nil { + e.Category = CategoryForward + } +} + +func setIATEntryRecordType(e *IATEntryDetail) { + // these values need to be inferred from the json field names + if e.Addenda10 != nil { + e.Addenda10.TypeCode = "10" + } + if e.Addenda11 != nil { + e.Addenda11.TypeCode = "11" + } + if e.Addenda12 != nil { + e.Addenda12.TypeCode = "12" + } + if e.Addenda13 != nil { + e.Addenda13.TypeCode = "13" + } + if e.Addenda14 != nil { + e.Addenda14.TypeCode = "14" + } + if e.Addenda15 != nil { + e.Addenda15.TypeCode = "15" + } + if e.Addenda16 != nil { + e.Addenda16.TypeCode = "16" + } + for _, a := range e.Addenda17 { + a.TypeCode = "17" + } + for _, a := range e.Addenda18 { + a.TypeCode = "18" + } + if e.Addenda98 != nil { + e.Addenda98.TypeCode = "98" + } + if e.Addenda99 != nil { + e.Addenda99.TypeCode = "99" + } +} + +// setBatchesFromJson takes bs as JSON and attempts to read out all the Batches within. +// +// We have to break this out as Batcher is an interface (and can't be read by Go's +// json struct tag decoding). +func (f *File) setBatchesFromJSON(bs []byte) error { + var batches batchesJSON + var iatBatches iatBatchesJSON + + if err := json.Unmarshal(bs, &batches); err != nil { + return err + } + // Clear out any nil batches + for i := range f.Batches { + if f.Batches[i] == nil { + f.Batches = append(f.Batches[:i], f.Batches[i+1:]...) + } + } + // Add new batches to file + for i := range batches.Batches { + if batches.Batches[i] == nil { + continue + } + batch := *batches.Batches[i] + batch.SetID(batch.Header.ID) + batch.SetValidation(f.validateOpts) + + for _, e := range batch.Entries { + // these values need to be inferred from the json field names + setEntryRecordType(e) + } + for _, e := range batch.ADVEntries { + setADVEntryRecordType(e) + } + + if err := batch.build(); err != nil { + return batch.Error("Invalid Batch", err, batch.Header.ID) + } + + // Attach a batch with the correct type + f.Batches = append(f.Batches, ConvertBatchType(batch)) + } + + if err := json.Unmarshal(bs, &iatBatches); err != nil { + return err + } + + // Add new iatBatches to file + for i := range iatBatches.IATBatches { + if len(iatBatches.IATBatches) == 0 { + continue + } + + iatBatch := iatBatches.IATBatches[i] + iatBatch.ID = iatBatch.Header.ID + for _, e := range iatBatch.Entries { + setIATEntryRecordType(e) + } + + if err := iatBatch.build(); err != nil { + return iatBatch.Error("from JSON", err) + } + f.IATBatches = append(f.IATBatches, iatBatch) + } + + return nil +} + +// overwriteDateTimeFields will scan through fields in a File for Date / Time +// values which are not in their ACH format (YYMMDD, hhmm). It'll attempt to parse +// various formats and overwrite them to the expected values (YYMMDD, hhmm). +func (f *File) overwriteDateTimeFields() { + // File header + if t, err := datetimeParse(f.Header.FileCreationDate); err == nil { + f.Header.FileCreationDate = t.Format("060102") + } + if t, err := datetimeParse(f.Header.FileCreationTime); err == nil { + f.Header.FileCreationTime = t.Format("1504") + } + + // Batches + for i := range f.Batches { + // BatchHeader + header := f.Batches[i].GetHeader() + if t, err := datetimeParse(strings.TrimPrefix(header.CompanyDescriptiveDate, "SD")); err == nil { + header.CompanyDescriptiveDate = "SD" + t.Format("1504") + } + if t, err := datetimeParse(header.EffectiveEntryDate); err == nil { + header.EffectiveEntryDate = t.Format("060102") + } + f.Batches[i].SetHeader(header) + } + + // TODO(adam): Addenda99 has DateOfDeath which is hard to parse and overwrite with Batcher.GetEntries() copying structs + + // IAT Batches + for i := range f.IATBatches { + if t, err := datetimeParse(f.IATBatches[i].Header.EffectiveEntryDate); err == nil { + f.IATBatches[i].Header.EffectiveEntryDate = t.Format("060102") + } + } +} + +var datetimeformats = []string{ + "2006-01-02T15:04:05.999Z", // Default javascript (new Date).toISOString() + "2006-01-02T15:04:05Z", // ISO 8601 without milliseconds + time.RFC3339, // Go default +} + +func datetimeParse(v string) (time.Time, error) { + for i := range datetimeformats { + if t, err := time.Parse(datetimeformats[i], v); err == nil && !t.IsZero() { + return t, nil + } + } + return time.Time{}, fmt.Errorf("unknown format: %s", v) +} + +// Create will modify the File to tabulate and assemble it into a valid state. +// This includes setting any posting dates, sequence numbers, counts, and sums. +// +// Create requires a FileHeader and at least one Batch if validateOpts.AllowZeroBatches is false. +// +// Since each Batch may modify computable fields in the File, any calls to +// Batch.Create should be done before Create. +// +// To check if the File is Nacha compliant, call Validate or ValidateWith. func (f *File) Create() error { + opts := f.validateOpts + if opts == nil { + opts = &ValidateOpts{} + } + // Requires a valid FileHeader to build FileControl - if err := f.Header.Validate(); err != nil { + if !opts.AllowMissingFileHeader { + if err := f.Header.Validate(); err != nil { + return err + } + } + + // If AllowZeroBatches is false, require at least one Batch in the new file. + if (f.validateOpts == nil || !f.validateOpts.AllowZeroBatches) && + len(f.Batches) <= 0 && len(f.IATBatches) <= 0 { + return ErrFileNoBatches + } + + if !f.IsADV() { + // add 2 for FileHeader/control and reset if build was called twice do to error + totalRecordsInFile := 2 + batchSeq := 1 + fileEntryAddendaCount := 0 + fileEntryHashSum := 0 + totalDebitAmount := 0 + totalCreditAmount := 0 + + for i, batch := range f.Batches { + // create ascending batch numbers unless batch number has been provided + if f.Batches[i].GetHeader().BatchNumber <= 1 { + f.Batches[i].GetHeader().BatchNumber = batchSeq + f.Batches[i].GetControl().BatchNumber = batchSeq + } + batchSeq++ + // sum file entry and addenda records. Assume batch.Create batch properly calculated control + fileEntryAddendaCount = fileEntryAddendaCount + batch.GetControl().EntryAddendaCount + // add 2 for Batch header/control + entry added count + totalRecordsInFile = totalRecordsInFile + 2 + batch.GetControl().EntryAddendaCount + // sum hash from batch control. Assume Batch.Build properly calculated field. + fileEntryHashSum = fileEntryHashSum + batch.GetControl().EntryHash + totalDebitAmount = totalDebitAmount + batch.GetControl().TotalDebitEntryDollarAmount + totalCreditAmount = totalCreditAmount + batch.GetControl().TotalCreditEntryDollarAmount + } + for i, iatBatch := range f.IATBatches { + // create ascending batch numbers + if f.IATBatches[i].GetHeader().BatchNumber <= 1 { + f.IATBatches[i].GetHeader().BatchNumber = batchSeq + f.IATBatches[i].GetControl().BatchNumber = batchSeq + } + batchSeq++ + // sum file entry and addenda records. Assume batch.Create batch properly calculated control + fileEntryAddendaCount = fileEntryAddendaCount + iatBatch.GetControl().EntryAddendaCount + // add 2 for Batch header/control + entry added count + totalRecordsInFile = totalRecordsInFile + 2 + iatBatch.GetControl().EntryAddendaCount + // sum hash from batch control. Assume Batch.Build properly calculated field. + fileEntryHashSum = fileEntryHashSum + iatBatch.GetControl().EntryHash + totalDebitAmount = totalDebitAmount + iatBatch.GetControl().TotalDebitEntryDollarAmount + totalCreditAmount = totalCreditAmount + iatBatch.GetControl().TotalCreditEntryDollarAmount + } + + // create FileControl from calculated values + fc := NewFileControl() + fc.ID = f.ID + fc.BatchCount = batchSeq - 1 + // blocking factor of 10 is static default value in f.Header.blockingFactor. + if (totalRecordsInFile % 10) != 0 { + fc.BlockCount = totalRecordsInFile/10 + 1 + } else { + fc.BlockCount = totalRecordsInFile / 10 + } + fc.EntryAddendaCount = fileEntryAddendaCount + + // If greater than 10 digits, truncate + fc.EntryHash = fc.converters.leastSignificantDigits(fileEntryHashSum, 10) + + fc.TotalDebitEntryDollarAmountInFile = totalDebitAmount + fc.TotalCreditEntryDollarAmountInFile = totalCreditAmount + f.Control = fc + } else { + if err := f.createFileADV(); err != nil { + return err + } + } + return nil +} + +// AddBatch appends a Batch to the ach.File +func (f *File) AddBatch(batch Batcher) []Batcher { + if batch.Category() == CategoryNOC { + f.NotificationOfChange = append(f.NotificationOfChange, batch) + } + if batch.Category() == CategoryReturn { + f.ReturnEntries = append(f.ReturnEntries, batch) + } + f.Batches = append(f.Batches, batch) + return f.Batches +} + +// RemoveBatch will delete a given Batcher from an ach.File +func (f *File) RemoveBatch(batch Batcher) { + if batch.Category() == CategoryNOC { + for i := 0; i < len(f.NotificationOfChange); i++ { + if f.NotificationOfChange[i].Equal(batch) { + f.NotificationOfChange = append(f.NotificationOfChange[:i], f.NotificationOfChange[i+1:]...) + i-- + } + } + } + if batch.Category() == CategoryReturn { + for i := 0; i < len(f.ReturnEntries); i++ { + if f.ReturnEntries[i].Equal(batch) { + f.ReturnEntries = append(f.ReturnEntries[:i], f.ReturnEntries[i+1:]...) + i-- + } + } + } + for i := 0; i < len(f.Batches); i++ { + if f.Batches[i].Equal(batch) { + f.Batches = append(f.Batches[:i], f.Batches[i+1:]...) + i-- + } + } +} + +// AddIATBatch appends a IATBatch to the ach.File +func (f *File) AddIATBatch(iatBatch IATBatch) []IATBatch { + f.IATBatches = append(f.IATBatches, iatBatch) + return f.IATBatches +} + +// SetHeader allows for header to be built. +func (f *File) SetHeader(h FileHeader) *File { + f.Header = h + return f +} + +// Validate performs checks on each record according to Nacha guidelines. +// Validate will never modify the File. +// +// ValidateOpts may be set to bypass certain rules and will only be applied to the FileHeader. +// The underlying Batches and Entries on this File will use their own ValidateOpts if they are set. +// +// The first error encountered is returned. +func (f *File) Validate() error { + return f.ValidateWith(f.validateOpts) +} + +func (f *File) GetValidation() *ValidateOpts { + if f == nil { + return nil + } + return f.validateOpts +} + +// SetValidation stores ValidateOpts on the File which are to be used to override +// the default NACHA validation rules. +func (f *File) SetValidation(opts *ValidateOpts) { + if f == nil { + return + } + + f.validateOpts = opts + f.Header.SetValidation(opts) +} + +// ValidateOpts contains specific overrides from the default set of validations +// performed on a NACHA file, records and various fields within. +type ValidateOpts struct { + // RequireABAOrigin can be set to enable routing number validation + // over the ImmediateOrigin file header field. + RequireABAOrigin bool `json:"requireABAOrigin"` + + // BypassOriginValidation can be set to skip validation for the + // ImmediateOrigin file header field. + // + // This also allows for custom TraceNumbers which aren't prefixed with + // a routing number as required by the NACHA specification. + BypassOriginValidation bool `json:"bypassOriginValidation"` + + // BypassDestinationValidation can be set to skip validation for the + // ImmediateDestination file header field. + // + // This also allows for custom TraceNumbers which aren't prefixed with + // a routing number as required by the NACHA specification. + BypassDestinationValidation bool `json:"bypassDestinationValidation"` + + // CheckTransactionCode allows for custom validation of TransactionCode values + // + // Note: Functions cannot be serialized into/from JSON, so this check cannot be used from config files. + CheckTransactionCode func(code int) error `json:"-"` + + // CustomTraceNumbers disables Nacha specified checks of TraceNumbers: + // - Ascending order of trace numbers within batches + // - Trace numbers beginning with their ODFI's routing number + // - AddendaRecordIndicator is set correctly + CustomTraceNumbers bool `json:"customTraceNumbers"` + + // AllowZeroBatches allows the file to have zero batches + AllowZeroBatches bool `json:"allowZeroBatches"` + + // AllowMissingFileHeader allows a file to be read without a FileHeader record. + AllowMissingFileHeader bool `json:"allowMissingFileHeader"` + + // AllowMissingFileControl allows a file to be read without a FileControl record. + AllowMissingFileControl bool `json:"allowMissingFileControl"` + + // BypassCompanyIdentificationMatch allows batches in which the Company Identification field + // in the batch header and control do not match. + BypassCompanyIdentificationMatch bool `json:"bypassCompanyIdentificationMatch"` + + // CustomReturnCodes can be set to skip validation for the Return Code field in an Addenda99 + // This allows for non-standard/deprecated return codes (e.g. R97) + CustomReturnCodes bool `json:"customReturnCodes"` + + // UnequalServiceClassCode skips equality checks for the ServiceClassCode in each pair of BatchHeader + // and BatchControl records. + UnequalServiceClassCode bool `json:"unequalServiceClassCode"` + + // AllowUnorderedBatchNumebrs allows a file to be read with unordered batch numbers. + AllowUnorderedBatchNumbers bool `json:"allowUnorderedBatchNumbers"` +} + +// ValidateWith performs checks on each record according to Nacha guidelines. +// ValidateWith will never modify the File. +// +// ValidateOpts may be set to bypass certain rules and will only be applied to the FileHeader. +// opts passed in will override ValidateOpts set by SetValidation. +// The underlying Batches and Entries on this File will use their own ValidateOpts if they are set. +// +// The first error encountered is returned. +func (f *File) ValidateWith(opts *ValidateOpts) error { + if opts == nil { + opts = &ValidateOpts{} + } + + if !opts.AllowMissingFileHeader { + if err := f.Header.ValidateWith(opts); err != nil { + return err + } + } + + if !f.IsADV() { + // The value of the Batch Count Field is equal to the number of Company/Batch/Header Records in the file. + if f.Control.BatchCount != (len(f.Batches) + len(f.IATBatches)) { + return NewErrFileCalculatedControlEquality("BatchCount", len(f.Batches), f.Control.BatchCount) + } + + for _, b := range f.Batches { + if err := b.Validate(); err != nil { + return err + } + } + + if !opts.AllowMissingFileControl { + if err := f.Control.Validate(); err != nil { + return err + } + } + if err := f.isEntryAddendaCount(false); err != nil { + return err + } + if err := f.isFileAmount(false); err != nil { + return err + } + if !opts.AllowUnorderedBatchNumbers { + if err := f.isSequenceAscending(); err != nil { + return err + } + } + return f.isEntryHash(false) + } + + // File contains ADV batches BatchADV + + // The value of the Batch Count Field is equal to the number of Company/Batch/Header Records in the file. + if f.ADVControl.BatchCount != len(f.Batches) { + return NewErrFileCalculatedControlEquality("BatchCount", len(f.Batches), f.ADVControl.BatchCount) + } + if !opts.AllowMissingFileControl { + if err := f.ADVControl.Validate(); err != nil { + return err + } + } + if err := f.isEntryAddendaCount(true); err != nil { return err } - // Requires at least one Batch in the new file. - if len(f.Batches) <= 0 { - return &FileError{FieldName: "Batchs", Value: strconv.Itoa(len(f.Batches)), Msg: "must have []*Batches to be built"} + if err := f.isFileAmount(true); err != nil { + return err } + return f.isEntryHash(true) +} + +// isEntryAddendaCount is prepared by hashing the RDFI's 8-digit Routing Number in each entry. +// The Entry Hash provides a check against inadvertent alteration of data +func (f *File) isEntryAddendaCount(IsADV bool) error { + // IsADV + // true: the file contains ADV batches + // false: the file contains other batch types + + count := 0 + + // we assume that each batch block has already validated the addenda count is accurate in batch control. + + if !IsADV { + for _, batch := range f.Batches { + count += batch.GetControl().EntryAddendaCount + } + for _, iatBatch := range f.IATBatches { + count += iatBatch.GetControl().EntryAddendaCount + } + if f.Control.EntryAddendaCount != count { + return NewErrFileCalculatedControlEquality("EntryAddendaCount", count, f.Control.EntryAddendaCount) + } + } else { + for _, batch := range f.Batches { + count += batch.GetADVControl().EntryAddendaCount + } + if f.ADVControl.EntryAddendaCount != count { + return NewErrFileCalculatedControlEquality("EntryAddendaCount", count, f.ADVControl.EntryAddendaCount) + } + } + return nil +} + +// isFileAmount The Total Debit and Credit Entry Dollar Amounts Fields contain accumulated +// Entry Detail debit and credit totals within the file +func (f *File) isFileAmount(IsADV bool) error { + // IsADV + // true: the file contains ADV batches + // false: the file contains other batch types + + debit := 0 + credit := 0 + + if !IsADV { + for _, batch := range f.Batches { + debit += batch.GetControl().TotalDebitEntryDollarAmount + credit += batch.GetControl().TotalCreditEntryDollarAmount + } + // IAT + for _, iatBatch := range f.IATBatches { + debit += iatBatch.GetControl().TotalDebitEntryDollarAmount + credit += iatBatch.GetControl().TotalCreditEntryDollarAmount + } + + if f.Control.TotalDebitEntryDollarAmountInFile != debit { + return NewErrFileCalculatedControlEquality("TotalDebitEntryDollarAmountInFile", debit, f.Control.TotalDebitEntryDollarAmountInFile) + } + if f.Control.TotalCreditEntryDollarAmountInFile != credit { + return NewErrFileCalculatedControlEquality("TotalCreditEntryDollarAmountInFile", credit, f.Control.TotalCreditEntryDollarAmountInFile) + } + } else { + for _, batch := range f.Batches { + debit += batch.GetADVControl().TotalDebitEntryDollarAmount + credit += batch.GetADVControl().TotalCreditEntryDollarAmount + } + + if f.ADVControl.TotalDebitEntryDollarAmountInFile != debit { + return NewErrFileCalculatedControlEquality("TotalDebitEntryDollarAmountInFile", debit, f.ADVControl.TotalDebitEntryDollarAmountInFile) + } + if f.ADVControl.TotalCreditEntryDollarAmountInFile != credit { + return NewErrFileCalculatedControlEquality("TotalCreditEntryDollarAmountInFile", credit, f.ADVControl.TotalCreditEntryDollarAmountInFile) + + } + } + return nil +} + +// isEntryHash validates the hash by recalculating the result +func (f *File) isEntryHash(IsADV bool) error { + // IsADV + // true: the file contains ADV batches + // false: the file contains other batch types but not ADV + + hashField := f.calculateEntryHash(IsADV) + + if !IsADV { + if hashField != f.Control.EntryHash { + return NewErrFileCalculatedControlEquality("EntryHash", hashField, f.Control.EntryHash) + } + } else { + if hashField != f.ADVControl.EntryHash { + return NewErrFileCalculatedControlEquality("EntryHash", hashField, f.ADVControl.EntryHash) + } + } + return nil +} + +// calculateEntryHash This field is prepared by hashing the 8-digit Routing Number in each batch. +// The Entry Hash provides a check against inadvertent alteration of data +func (f *File) calculateEntryHash(IsADV bool) int { + // IsADV + // true: the file contains ADV batches + // false: the file contains other batch types but not ADV + + hash := 0 + + if !IsADV { + for _, batch := range f.Batches { + hash = hash + batch.GetControl().EntryHash + } + // IAT + for _, iatBatch := range f.IATBatches { + hash = hash + iatBatch.GetControl().EntryHash + } + } else { + for _, batch := range f.Batches { + hash = hash + batch.GetADVControl().EntryHash + } + } + + // Ensure the entry hash cannot exceed 10 digits + // If greater than 10 digits, truncate + return f.Control.leastSignificantDigits(hash, 10) +} + +// IsADV determines if the File is a File containing ADV batches +func (f *File) IsADV() bool { + for i := range f.Batches { + if v := f.Batches[i].GetHeader(); v == nil { + f.Batches[i].SetHeader(NewBatchHeader()) + } + if v := f.Batches[i].GetControl(); v == nil { + f.Batches[i].SetControl(NewBatchControl()) + } + if f.Batches[i].GetHeader().StandardEntryClassCode == ADV { + return true + } + } + return false +} + +func (f *File) createFileADV() error { // add 2 for FileHeader/control and reset if build was called twice do to error totalRecordsInFile := 2 batchSeq := 1 @@ -114,23 +869,31 @@ func (f *File) Create() error { fileEntryHashSum := 0 totalDebitAmount := 0 totalCreditAmount := 0 + for i, batch := range f.Batches { // create ascending batch numbers - f.Batches[i].GetHeader().BatchNumber = batchSeq - f.Batches[i].GetControl().BatchNumber = batchSeq + + if batch.GetHeader().StandardEntryClassCode != ADV { + return ErrFileADVOnly + } + + if f.Batches[i].GetHeader().BatchNumber <= 1 { + f.Batches[i].GetHeader().BatchNumber = batchSeq + f.Batches[i].GetADVControl().BatchNumber = batchSeq + } batchSeq++ - // sum file entry and addenda records. Assume batch.Create() batch properly calculated control - fileEntryAddendaCount = fileEntryAddendaCount + batch.GetControl().EntryAddendaCount + // sum file entry and addenda records. Assume batch.Create batch properly calculated control + fileEntryAddendaCount = fileEntryAddendaCount + batch.GetADVControl().EntryAddendaCount // add 2 for Batch header/control + entry added count - totalRecordsInFile = totalRecordsInFile + 2 + batch.GetControl().EntryAddendaCount + totalRecordsInFile = totalRecordsInFile + 2 + batch.GetADVControl().EntryAddendaCount // sum hash from batch control. Assume Batch.Build properly calculated field. - fileEntryHashSum = fileEntryHashSum + batch.GetControl().EntryHash - totalDebitAmount = totalDebitAmount + batch.GetControl().TotalDebitEntryDollarAmount - totalCreditAmount = totalCreditAmount + batch.GetControl().TotalCreditEntryDollarAmount - + fileEntryHashSum = fileEntryHashSum + batch.GetADVControl().EntryHash + totalDebitAmount = totalDebitAmount + batch.GetADVControl().TotalDebitEntryDollarAmount + totalCreditAmount = totalCreditAmount + batch.GetADVControl().TotalCreditEntryDollarAmount } - // create FileControl from calculated values - fc := NewFileControl() + + fc := NewADVFileControl() + fc.ID = f.ID fc.BatchCount = batchSeq - 1 // blocking factor of 10 is static default value in f.Header.blockingFactor. if (totalRecordsInFile % 10) != 0 { @@ -142,97 +905,361 @@ func (f *File) Create() error { fc.EntryHash = fileEntryHashSum fc.TotalDebitEntryDollarAmountInFile = totalDebitAmount fc.TotalCreditEntryDollarAmountInFile = totalCreditAmount - f.Control = fc + f.ADVControl = fc return nil } -// AddBatch appends a Batch to the ach.File -func (f *File) AddBatch(batch Batcher) []Batcher { - f.Batches = append(f.Batches, batch) - return f.Batches -} +// SegmentFile takes a valid ACH File and returns 2 segmented ACH Files, one ACH File containing credit entries and one +// ACH File containing debit entries. The return is 2 Files a Credit File and Debit File, or an error.: +// File - Credit File +// File - Debit File +// Error - Error or Nil +// Callers should always check for a nil-error before using the returned file. +// +// The File returned may not be valid and callers should confirm with Validate. Invalid files may +// be rejected by other Financial Institutions or ACH tools. +func (f *File) SegmentFile(_ *SegmentFileConfiguration) (*File, *File, error) { + if err := f.Validate(); err != nil { + return nil, nil, err + } -// SetHeader allows for header to be built. -func (f *File) SetHeader(h FileHeader) *File { - f.Header = h - return f -} + creditFile := NewFile() + debitFile := NewFile() -// Validate NACHA rules on the entire batch before being added to a File -func (f *File) Validate() error { - // The value of the Batch Count Field is equal to the number of Company/Batch/Header Records in the file. - if f.Control.BatchCount != len(f.Batches) { - msg := fmt.Sprintf(msgFileCalculatedControlEquality, len(f.Batches), f.Control.BatchCount) - return &FileError{FieldName: "BatchCount", Value: strconv.Itoa(len(f.Batches)), Msg: msg} + if f.Batches != nil { + f.segmentFileBatches(creditFile, debitFile) } - if err := f.isEntryAddendaCount(); err != nil { - return err + if f.IATBatches != nil { + f.segmentFileIATBatches(creditFile, debitFile) } - if err := f.isFileAmount(); err != nil { - return err + // Additional Sorting to be FI specific + if len(creditFile.Batches) != 0 || len(creditFile.IATBatches) != 0 { + f.addFileHeaderData(creditFile) + if err := creditFile.Create(); err != nil { + return nil, nil, err + } + if err := creditFile.Validate(); err != nil { + return nil, nil, err + } } - - if err := f.isEntryHash(); err != nil { - return err + if len(debitFile.Batches) != 0 || len(debitFile.IATBatches) != 0 { + f.addFileHeaderData(debitFile) + if err := debitFile.Create(); err != nil { + return nil, nil, err + } + if err := debitFile.Validate(); err != nil { + return nil, nil, err + } } - - return nil + return creditFile, debitFile, nil } -// isEntryAddenda is prepared by hashing the RDFI’s 8-digit Routing Number in each entry. -//The Entry Hash provides a check against inadvertent alteration of data -func (f *File) isEntryAddendaCount() error { - count := 0 - // we assume that each batch block has already validated the addenda count is accurate in batch control. +func (f *File) segmentFileBatches(creditFile, debitFile *File) { for _, batch := range f.Batches { - count += batch.GetControl().EntryAddendaCount + bh := batch.GetHeader() + + var creditBatch Batcher + var debitBatch Batcher + + switch bh.StandardEntryClassCode { + case ADV: + switch bh.ServiceClassCode { + case AutomatedAccountingAdvices: + bh := createSegmentFileBatchHeader(AutomatedAccountingAdvices, bh) + creditBatch, _ = NewBatch(bh) + debitBatch, _ = NewBatch(bh) + + entries := batch.GetADVEntries() + for _, entry := range entries { + segmentFileBatchAddADVEntry(creditBatch, debitBatch, entry) + } + // Add the Entry to its Batch + if creditBatch != nil && len(creditBatch.GetADVEntries()) > 0 { + _ = creditBatch.Create() + creditFile.AddBatch(creditBatch) + } + + if debitBatch != nil && len(debitBatch.GetADVEntries()) > 0 { + _ = debitBatch.Create() + debitFile.AddBatch(debitBatch) + } + } + default: + switch bh.ServiceClassCode { + case MixedDebitsAndCredits: + cbh := createSegmentFileBatchHeader(CreditsOnly, bh) + creditBatch, _ = NewBatch(cbh) + + dbh := createSegmentFileBatchHeader(DebitsOnly, bh) + debitBatch, _ = NewBatch(dbh) + + entries := batch.GetEntries() + for _, entry := range entries { + segmentFileBatchAddEntry(creditBatch, debitBatch, entry) + } + + if creditBatch != nil && len(creditBatch.GetEntries()) > 0 { + _ = creditBatch.Create() + creditFile.AddBatch(creditBatch) + } + if debitBatch != nil && len(debitBatch.GetEntries()) > 0 { + _ = debitBatch.Create() + debitFile.AddBatch(debitBatch) + } + case CreditsOnly: + creditFile.AddBatch(batch) + case DebitsOnly: + debitFile.AddBatch(batch) + } + } } - if f.Control.EntryAddendaCount != count { - msg := fmt.Sprintf(msgFileCalculatedControlEquality, count, f.Control.EntryAddendaCount) - return &FileError{FieldName: "EntryAddendaCount", Value: f.Control.EntryAddendaCountField(), Msg: msg} +} + +// segmentFileIATBatches segments IAT batches debits and credits into debit and credit files +func (f *File) segmentFileIATBatches(creditFile, debitFile *File) { + for _, iatb := range f.IATBatches { + IATBh := iatb.GetHeader() + + switch IATBh.ServiceClassCode { + case MixedDebitsAndCredits: + cbh := createSegmentFileIATBatchHeader(CreditsOnly, IATBh) + creditIATBatch := NewIATBatch(cbh) + + dbh := createSegmentFileIATBatchHeader(DebitsOnly, IATBh) + debitIATBatch := NewIATBatch(dbh) + + entries := iatb.GetEntries() + for _, IATEntry := range entries { + IATEntry.TraceNumber = "" // unset so Batch.build generates a TraceNumber + switch IATEntry.TransactionCode { + case CheckingCredit, CheckingReturnNOCCredit, CheckingPrenoteCredit, CheckingZeroDollarRemittanceCredit, + SavingsCredit, SavingsReturnNOCCredit, SavingsPrenoteCredit, SavingsZeroDollarRemittanceCredit, + GLCredit, GLReturnNOCCredit, GLPrenoteCredit, GLZeroDollarRemittanceCredit, + LoanCredit, LoanReturnNOCCredit, LoanPrenoteCredit, LoanZeroDollarRemittanceCredit: + creditIATBatch.AddEntry(IATEntry) + case CheckingDebit, CheckingReturnNOCDebit, CheckingPrenoteDebit, CheckingZeroDollarRemittanceDebit, + SavingsDebit, SavingsReturnNOCDebit, SavingsPrenoteDebit, SavingsZeroDollarRemittanceDebit, + GLDebit, GLReturnNOCDebit, GLPrenoteDebit, GLZeroDollarRemittanceDebit, + LoanDebit, LoanReturnNOCDebit: + debitIATBatch.AddEntry(IATEntry) + } + } + + if len(creditIATBatch.GetEntries()) > 0 { + _ = creditIATBatch.Create() + creditFile.AddIATBatch(creditIATBatch) + } + if len(debitIATBatch.GetEntries()) > 0 { + _ = debitIATBatch.Create() + debitFile.AddIATBatch(debitIATBatch) + } + case CreditsOnly: + creditFile.AddIATBatch(iatb) + case DebitsOnly: + debitFile.AddIATBatch(iatb) + } } - return nil + } -// isFileAmount tThe Total Debit and Credit Entry Dollar Amounts Fields contain accumulated -// Entry Detail debit and credit totals within the file -func (f *File) isFileAmount() error { - debit := 0 - credit := 0 - for _, batch := range f.Batches { - debit += batch.GetControl().TotalDebitEntryDollarAmount - credit += batch.GetControl().TotalCreditEntryDollarAmount +// createSegmentFileBatchHeader adds BatchHeader data for a debit/credit Segment File +func createSegmentFileBatchHeader(serviceClassCode int, bh *BatchHeader) *BatchHeader { + nbh := NewBatchHeader() + nbh.ID = base.ID() + nbh.ServiceClassCode = serviceClassCode + nbh.CompanyName = bh.CompanyName + nbh.CompanyDiscretionaryData = bh.CompanyDiscretionaryData + nbh.CompanyIdentification = bh.CompanyIdentification + nbh.StandardEntryClassCode = bh.StandardEntryClassCode + nbh.CompanyEntryDescription = bh.CompanyEntryDescription + nbh.CompanyDescriptiveDate = bh.CompanyDescriptiveDate + nbh.EffectiveEntryDate = bh.EffectiveEntryDate + nbh.SettlementDate = bh.SettlementDate + if serviceClassCode == AutomatedAccountingAdvices { + nbh.OriginatorStatusCode = 0 // ADV requires this be 0 + } else { + nbh.OriginatorStatusCode = bh.OriginatorStatusCode } - if f.Control.TotalDebitEntryDollarAmountInFile != debit { - msg := fmt.Sprintf(msgFileCalculatedControlEquality, debit, f.Control.TotalDebitEntryDollarAmountInFile) - return &FileError{FieldName: "TotalDebitEntryDollarAmountInFile", Value: f.Control.TotalDebitEntryDollarAmountInFileField(), Msg: msg} + nbh.ODFIIdentification = bh.ODFIIdentification + return nbh +} + +// createSegmentFileIATBatchHeader adds IATBatchHeader data for a debit/credit Segment File +func createSegmentFileIATBatchHeader(serviceClassCode int, IATBh *IATBatchHeader) *IATBatchHeader { + nbh := NewIATBatchHeader() + nbh.ID = base.ID() + nbh.ServiceClassCode = serviceClassCode + nbh.ForeignExchangeIndicator = IATBh.ForeignExchangeIndicator + nbh.ForeignExchangeReferenceIndicator = IATBh.ForeignExchangeReferenceIndicator + nbh.ISODestinationCountryCode = IATBh.ISODestinationCountryCode + nbh.OriginatorIdentification = IATBh.OriginatorIdentification + nbh.StandardEntryClassCode = IATBh.StandardEntryClassCode + nbh.CompanyEntryDescription = IATBh.CompanyEntryDescription + nbh.ISOOriginatingCurrencyCode = IATBh.ISOOriginatingCurrencyCode + nbh.ISODestinationCurrencyCode = IATBh.ISODestinationCurrencyCode + nbh.ODFIIdentification = IATBh.ODFIIdentification + return nbh +} + +// addFileHeaderData adds FileHeader data for a debit/credit Segment File +func (f *File) addFileHeaderData(file *File) *File { + file.ID = base.ID() + file.Header.ID = base.ID() + file.Header.ImmediateOrigin = f.Header.ImmediateOrigin + file.Header.ImmediateDestination = f.Header.ImmediateDestination + file.Header.FileCreationDate = time.Now().Format("060102") + file.Header.FileCreationTime = time.Now().AddDate(0, 0, 1).Format("1504") // HHmm + file.Header.ImmediateDestinationName = f.Header.ImmediateDestinationName + file.Header.ImmediateOriginName = f.Header.ImmediateOriginName + return file +} + +// segmentFileBatchAddEntry adds entries to batches in a segmented file +// Applies to All SEC Codes except ADV (Automated Accounting Advice) +func segmentFileBatchAddEntry(creditBatch, debitBatch Batcher, entry *EntryDetail) { + switch entry.TransactionCode { + case CheckingCredit, CheckingReturnNOCCredit, CheckingPrenoteCredit, CheckingZeroDollarRemittanceCredit, + SavingsCredit, SavingsReturnNOCCredit, SavingsPrenoteCredit, SavingsZeroDollarRemittanceCredit, + GLCredit, GLReturnNOCCredit, GLPrenoteCredit, GLZeroDollarRemittanceCredit, + LoanCredit, LoanReturnNOCCredit, LoanPrenoteCredit, LoanZeroDollarRemittanceCredit: + creditBatch.AddEntry(entry) + case CheckingDebit, CheckingReturnNOCDebit, CheckingPrenoteDebit, CheckingZeroDollarRemittanceDebit, + SavingsDebit, SavingsReturnNOCDebit, SavingsPrenoteDebit, SavingsZeroDollarRemittanceDebit, + GLDebit, GLReturnNOCDebit, GLPrenoteDebit, GLZeroDollarRemittanceDebit, + LoanDebit, LoanReturnNOCDebit: + debitBatch.AddEntry(entry) } - if f.Control.TotalCreditEntryDollarAmountInFile != credit { - msg := fmt.Sprintf(msgFileCalculatedControlEquality, credit, f.Control.TotalCreditEntryDollarAmountInFile) - return &FileError{FieldName: "TotalCreditEntryDollarAmountInFile", Value: f.Control.TotalCreditEntryDollarAmountInFileField(), Msg: msg} +} + +// segmentFileBatchAddADVEntry adds entries to batches in a segment file for SEC Code ADV (Automated Accounting Advice) +func segmentFileBatchAddADVEntry(creditBatch Batcher, debitBatch Batcher, entry *ADVEntryDetail) { + switch entry.TransactionCode { + case CreditForDebitsOriginated, CreditForCreditsReceived, CreditForCreditsRejected, CreditSummary: + creditBatch.AddADVEntry(entry) + case DebitForCreditsOriginated, DebitForDebitsReceived, DebitForDebitsRejectedBatches, DebitSummary: + debitBatch.AddADVEntry(entry) } - return nil } -// isEntryHash validates the hash by recalculating the result -func (f *File) isEntryHash() error { - hashField := f.calculateEntryHash() - if hashField != f.Control.EntryHashField() { - msg := fmt.Sprintf(msgFileCalculatedControlEquality, hashField, f.Control.EntryHashField()) - return &FileError{FieldName: "EntryHash", Value: f.Control.EntryHashField(), Msg: msg} +// FlattenBatches flattens the file's batches by consolidating batches with the same BatchHeader data into one Batch. +// Entries within each flattened batch will be sorted by their TraceNumber field. +func (f *File) FlattenBatches() (*File, error) { + out := NewFile() + + // Helper method to fetch the batch header without the trace number + getHeader := func(batchHeader fmt.Stringer) string { + return batchHeader.String()[:87] } - return nil + + batchesByHeader := make(map[string]Batcher) + if f.Batches != nil { + for _, b := range f.Batches { + bhKey := getHeader(b.GetHeader()) + _, found := batchesByHeader[bhKey] + if !found { + newBatch, err := NewBatch(b.GetHeader()) + if err != nil { + return nil, err + } + + batchesByHeader[bhKey] = newBatch + out.AddBatch(newBatch) + } + + newBatch := batchesByHeader[bhKey] + if newBatch.GetHeader().StandardEntryClassCode == "ADV" { + for _, e := range b.GetADVEntries() { + newBatch.AddADVEntry(e) + } + } else { + for _, e := range b.GetEntries() { + newBatch.AddEntry(e) + } + } + } + } + for i := range out.Batches { + bh := out.Batches[i].GetHeader() + if bh.StandardEntryClassCode != "ADV" { + batch, _ := NewBatch(bh) + entries := sortEntriesByTraceNumber(out.Batches[i].GetEntries()) + for i := range entries { + batch.AddEntry(entries[i]) + } + out.Batches[i] = batch + if err := batch.Create(); err != nil { + return nil, err + } + } + } + + IATBatchesByHeader := make(map[string]*IATBatch) + if f.IATBatches != nil { + for _, b := range f.IATBatches { + bhKey := getHeader(b.GetHeader()) + _, found := IATBatchesByHeader[bhKey] + if !found { + newBatch := NewIATBatch(b.GetHeader()) + IATBatchesByHeader[bhKey] = &newBatch + out.AddIATBatch(newBatch) + } + + newBatch := IATBatchesByHeader[bhKey] + for _, e := range b.GetEntries() { + newBatch.AddEntry(e) + } + } + } + + // Set batches to valid state + wg := sync.WaitGroup{} + + wg.Add(len(batchesByHeader)) + for _, b := range batchesByHeader { + go func(b Batcher) { + defer wg.Done() + _ = b.Create() + }(b) + } + + wg.Add(len(IATBatchesByHeader)) + for _, b := range IATBatchesByHeader { + go func(b IATBatch) { + defer wg.Done() + _ = b.Create() + }(*b) + } + + wg.Wait() + + // Add FileHeaderData. + f.addFileHeaderData(out) + + if err := out.Create(); err != nil { + return nil, err + } + if err := out.Validate(); err != nil { + return nil, err + } + return out, nil } -// calculateEntryHash This field is prepared by hashing the 8-digit Routing Number in each batch. -// The Entry Hash provides a check against inadvertent alteration of data -func (f *File) calculateEntryHash() string { - hash := 0 +// Validates that the batch numbers are ascending +func (f *File) isSequenceAscending() error { + lastSeq := 0 for _, batch := range f.Batches { - hash = hash + batch.GetControl().EntryHash + current := batch.GetHeader().BatchNumber + if f.validateOpts == nil || !f.validateOpts.CustomTraceNumbers { + if current <= lastSeq { + return NewErrFileBatchNumberAscending(lastSeq, current) + } + } + + lastSeq = current } - return f.numericField(hash, 10) + + return nil } diff --git a/fileControl.go b/fileControl.go index 4c96fc130..79818ecfc 100644 --- a/fileControl.go +++ b/fileControl.go @@ -1,37 +1,46 @@ -// Copyright 2017 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. package ach -import "fmt" +import ( + "strings" + "unicode/utf8" +) // FileControl record contains entry counts, dollar totals and hash // totals accumulated from each batch control record in the file. type FileControl struct { - // RecordType defines the type of record in the block. fileControlPos 9 - recordType string - - // BatchCount total number of batches (i.e., ‘5’ records) in the file - BatchCount int - + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // BatchCount total number of batches (i.e., '5' records) in the file + BatchCount int `json:"batchCount"` // BlockCount total number of records in the file (include all headers and trailer) divided - // by 10 (This number must be evenly divisible by 10. If not, additional records consisting of all 9’s are added to the file after the initial ‘9’ record to fill out the block 10.) - BlockCount int - - // EntryAddendaCount total detail and addenda records in the file - EntryAddendaCount int - + // by 10 (This number must be evenly divisible by 10. If not, additional records consisting of all 9's are added to the file after the initial '9' record to fill out the block 10.) + BlockCount int `json:"blockCount"` + // EntryAddendaCount is a tally of each Entry Detail Record and each Addenda + // Record processed, within either the batch or file as appropriate. + EntryAddendaCount int `json:"entryAddendaCount"` // EntryHash calculated in the same manner as the batch has total but includes total from entire file - EntryHash int - + EntryHash int `json:"entryHash"` // TotalDebitEntryDollarAmountInFile contains accumulated Batch debit totals within the file. - TotalDebitEntryDollarAmountInFile int - + TotalDebitEntryDollarAmountInFile int `json:"totalDebit"` // TotalCreditEntryDollarAmountInFile contains accumulated Batch credit totals within the file. - TotalCreditEntryDollarAmountInFile int - // Reserved should be blank. - reserved string + TotalCreditEntryDollarAmountInFile int `json:"totalCredit"` // validator is composed for data validation validator // converters is composed for ACH to golang Converters @@ -39,9 +48,14 @@ type FileControl struct { } // Parse takes the input record string and parses the FileControl values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. func (fc *FileControl) Parse(record string) { + if utf8.RuneCountInString(record) < 55 { + return + } + // 1-1 Always "9" - fc.recordType = "9" // 2-7 The total number of Batch Header Record in the file. For example: "000003 fc.BatchCount = fc.parseNumField(record[1:7]) // 8-13 e total number of blocks on the file, including the File Header and File Control records. One block is 10 lines, so it's effectively the number of lines in the file divided by 10. @@ -56,29 +70,26 @@ func (fc *FileControl) Parse(record string) { // 44-55 Number of cents of credit entries within the file fc.TotalCreditEntryDollarAmountInFile = fc.parseNumField(record[43:55]) // 56-94 Reserved Always blank (just fill with spaces) - fc.reserved = " " } // NewFileControl returns a new FileControl with default values for none exported fields func NewFileControl() FileControl { - return FileControl{ - recordType: "9", - reserved: " ", - } + return FileControl{} } // String writes the FileControl struct to a 94 character string. func (fc *FileControl) String() string { - return fmt.Sprintf("%v%v%v%v%v%v%v%v", - fc.recordType, - fc.BatchCountField(), - fc.BlockCountField(), - fc.EntryAddendaCountField(), - fc.EntryHashField(), - fc.TotalDebitEntryDollarAmountInFileField(), - fc.TotalCreditEntryDollarAmountInFileField(), - fc.reserved, - ) + var buf strings.Builder + buf.Grow(94) + buf.WriteString(fileControlPos) + buf.WriteString(fc.BatchCountField()) + buf.WriteString(fc.BlockCountField()) + buf.WriteString(fc.EntryAddendaCountField()) + buf.WriteString(fc.EntryHashField()) + buf.WriteString(fc.TotalDebitEntryDollarAmountInFileField()) + buf.WriteString(fc.TotalCreditEntryDollarAmountInFileField()) + buf.WriteString(" ") + return buf.String() } // Validate performs NACHA format rule checks on the record and returns an error if not Validated @@ -87,30 +98,25 @@ func (fc *FileControl) Validate() error { if err := fc.fieldInclusion(); err != nil { return err } - if fc.recordType != "9" { - msg := fmt.Sprintf(msgRecordType, 9) - return &FieldError{FieldName: "recordType", Value: fc.recordType, Msg: msg} - } return nil } // fieldInclusion validate mandatory fields are not default values. If fields are // invalid the ACH transfer will be returned. func (fc *FileControl) fieldInclusion() error { - if fc.recordType == "" { - return &FieldError{FieldName: "recordType", Value: fc.recordType, Msg: msgFieldInclusion} - } - if fc.BatchCount == 0 { - return &FieldError{FieldName: "BatchCount", Value: fc.BatchCountField(), Msg: msgFieldInclusion} - } if fc.BlockCount == 0 { - return &FieldError{FieldName: "BlockCount", Value: fc.BlockCountField(), Msg: msgFieldInclusion} - } - if fc.EntryAddendaCount == 0 { - return &FieldError{FieldName: "EntryAddendaCount", Value: fc.EntryAddendaCountField(), Msg: msgFieldInclusion} + return fieldError("BlockCount", ErrConstructor, fc.BlockCountField()) } - if fc.EntryHash == 0 { - return &FieldError{FieldName: "EntryAddendaCount", Value: fc.EntryAddendaCountField(), Msg: msgFieldInclusion} + if fc.TotalCreditEntryDollarAmountInFile != 0 || fc.TotalDebitEntryDollarAmountInFile != 0 { + if fc.BatchCount == 0 { + return fieldError("BatchCount", ErrConstructor, fc.BatchCountField()) + } + if fc.EntryAddendaCount == 0 { + return fieldError("EntryAddendaCount", ErrConstructor, fc.EntryAddendaCountField()) + } + if fc.EntryHash == 0 { + return fieldError("EntryHash", ErrConstructor, fc.EntryAddendaCountField()) + } } return nil } diff --git a/fileControl_internal_test.go b/fileControl_internal_test.go deleted file mode 100644 index 7f095b925..000000000 --- a/fileControl_internal_test.go +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2017 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. - -package ach - -import ( - "strings" - "testing" -) - -func mockFileControl() FileControl { - fc := NewFileControl() - fc.BatchCount = 1 - fc.BlockCount = 1 - fc.EntryAddendaCount = 1 - fc.EntryHash = 5320001 - return fc -} - -func TestMockFileControl(t *testing.T) { - fc := mockFileControl() - if err := fc.Validate(); err != nil { - t.Error("mockFileControl does not validate and will break other tests") - } - if fc.BatchCount != 1 { - t.Error("BatchCount depedendent default value has changed") - } - if fc.BlockCount != 1 { - t.Error("BlockCount depedendent default value has changed") - } - if fc.EntryAddendaCount != 1 { - t.Error("EntryAddendaCount depedendent default value has changed") - } - if fc.EntryHash != 5320001 { - t.Error("EntryHash depedendent default value has changed") - } -} - -// TestParseFileControl parses a known File Control Record string. -func TestParseFileControl(t *testing.T) { - var line = "9000001000001000000010005320001000000010500000000000000 " - r := NewReader(strings.NewReader(line)) - r.line = line - err := r.parseFileControl() - if err != nil { - t.Errorf("%T: %s", err, err) - } - record := r.File.Control - - if record.recordType != "9" { - t.Errorf("RecordType Expected '9' got: %v", record.recordType) - } - if record.BatchCountField() != "000001" { - t.Errorf("BatchCount Expected '000001' got: %v", record.BatchCountField()) - } - if record.BlockCountField() != "000001" { - t.Errorf("BlockCount Expected '000001' got: %v", record.BlockCountField()) - } - if record.EntryAddendaCountField() != "00000001" { - t.Errorf("EntryAddendaCount Expected '00000001' got: %v", record.EntryAddendaCountField()) - } - if record.EntryHashField() != "0005320001" { - t.Errorf("EntryHash Expected '0005320001' got: %v", record.EntryHashField()) - } - if record.TotalDebitEntryDollarAmountInFileField() != "000000010500" { - t.Errorf("TotalDebitEntryDollarAmountInFile Expected '0005000000010500' got: %v", record.TotalDebitEntryDollarAmountInFileField()) - } - if record.TotalCreditEntryDollarAmountInFileField() != "000000000000" { - t.Errorf("TotalCreditEntryDollarAmountInFile Expected '000000000000' got: %v", record.TotalCreditEntryDollarAmountInFileField()) - } - if record.reserved != " " { - t.Errorf("Reserved Expected ' ' got: %v", record.reserved) - } -} - -// TestFCString validats that a known parsed file can be return to a string of the same value -func TestFCString(t *testing.T) { - var line = "9000001000001000000010005320001000000010500000000000000 " - r := NewReader(strings.NewReader(line)) - r.line = line - err := r.parseFileControl() - if err != nil { - t.Errorf("%T: %s", err, err) - } - record := r.File.Control - if record.String() != line { - t.Errorf("\nStrings do not match %s\n %s", line, record.String()) - } -} - -// TestValidateFCRecordType ensure error if recordType is not 9 -func TestValidateFCRecordType(t *testing.T) { - fc := mockFileControl() - fc.recordType = "2" - - if err := fc.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "recordType" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestFCFieldInclusion(t *testing.T) { - fc := mockFileControl() - fc.BatchCount = 0 - if err := fc.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestFCFieldInclusionRecordType(t *testing.T) { - fc := mockFileControl() - fc.recordType = "" - if err := fc.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestFCFieldInclusionBlockCount(t *testing.T) { - fc := mockFileControl() - fc.BlockCount = 0 - if err := fc.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestFCFieldInclusionEntryAddendaCount(t *testing.T) { - fc := mockFileControl() - fc.EntryAddendaCount = 0 - if err := fc.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestFCFieldInclusionEntryHash(t *testing.T) { - fc := mockFileControl() - fc.EntryHash = 0 - if err := fc.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} diff --git a/fileControl_test.go b/fileControl_test.go new file mode 100644 index 000000000..b5cc4d2d3 --- /dev/null +++ b/fileControl_test.go @@ -0,0 +1,233 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "testing" + + "github.com/moov-io/base" +) + +// mockFileControl create a file control +func mockFileControl() FileControl { + fc := NewFileControl() + fc.BatchCount = 1 + fc.BlockCount = 1 + fc.EntryAddendaCount = 1 + fc.EntryHash = 5320001 + fc.TotalDebitEntryDollarAmountInFile = 100 + return fc +} + +// testMockFileControl validates a file control record +func testMockFileControl(t testing.TB) { + fc := mockFileControl() + if err := fc.Validate(); err != nil { + t.Error("mockFileControl does not validate and will break other tests") + } + if fc.BatchCount != 1 { + t.Error("BatchCount dependent default value has changed") + } + if fc.BlockCount != 1 { + t.Error("BlockCount dependent default value has changed") + } + if fc.EntryAddendaCount != 1 { + t.Error("EntryAddendaCount dependent default value has changed") + } + if fc.EntryHash != 5320001 { + t.Error("EntryHash dependent default value has changed") + } +} + +// TestMockFileControl tests validating a file control record +func TestMockFileControl(t *testing.T) { + testMockFileControl(t) +} + +// BenchmarkMockFileControl benchmarks validating a file control record +func BenchmarkMockFileControl(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testMockFileControl(b) + } +} + +// testParseFileControl parses a known file control record string +func testParseFileControl(t testing.TB) { + var line = "9000001000001000000010005320001000000010500000000000000 " + r := NewReader(strings.NewReader(line)) + r.line = line + err := r.parseFileControl() + if err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.File.Control + + if record.BatchCountField() != "000001" { + t.Errorf("BatchCount Expected '000001' got: %v", record.BatchCountField()) + } + if record.BlockCountField() != "000001" { + t.Errorf("BlockCount Expected '000001' got: %v", record.BlockCountField()) + } + if record.EntryAddendaCountField() != "00000001" { + t.Errorf("EntryAddendaCount Expected '00000001' got: %v", record.EntryAddendaCountField()) + } + if record.EntryHashField() != "0005320001" { + t.Errorf("EntryHash Expected '0005320001' got: %v", record.EntryHashField()) + } + if record.TotalDebitEntryDollarAmountInFileField() != "000000010500" { + t.Errorf("TotalDebitEntryDollarAmountInFile Expected '0005000000010500' got: %v", record.TotalDebitEntryDollarAmountInFileField()) + } + if record.TotalCreditEntryDollarAmountInFileField() != "000000000000" { + t.Errorf("TotalCreditEntryDollarAmountInFile Expected '000000000000' got: %v", record.TotalCreditEntryDollarAmountInFileField()) + } +} + +// TestParseFileControl tests parsing a known file control record string +func TestParseFileControl(t *testing.T) { + testParseFileControl(t) +} + +// BenchmarkParseFileControl benchmarks parsing a known file control record string +func BenchmarkParseFileControl(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testParseFileControl(b) + } +} + +// testFCString validates that a known parsed file can be return to a string of the same value +func testFCString(t testing.TB) { + var line = "9000001000001000000010005320001000000010500000000000000 " + r := NewReader(strings.NewReader(line)) + r.line = line + err := r.parseFileControl() + if err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.File.Control + if record.String() != line { + t.Errorf("\nStrings do not match %s\n %s", line, record.String()) + } +} + +// TestFCString tests validating that a known parsed file can be return to a string of the same value +func TestFCString(t *testing.T) { + testFCString(t) +} + +// BenchmarkFCString benchmarks validating that a known parsed file can be return to a string of the same value +func BenchmarkFCString(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFCString(b) + } +} + +// testFCFieldInclusion validates file control field inclusion +func testFCFieldInclusion(t testing.TB) { + fc := mockFileControl() + fc.BatchCount = 0 + err := fc.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFCFieldInclusion tests validating file control field inclusion +func TestFCFieldInclusion(t *testing.T) { + testFCFieldInclusion(t) +} + +// BenchmarkFCFieldInclusion benchmarks validating file control field inclusion +func BenchmarkFCFieldInclusion(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFCFieldInclusion(b) + } +} + +// testFCFieldInclusionBlockCount validates file control block count field inclusion +func testFCFieldInclusionBlockCount(t testing.TB) { + fc := mockFileControl() + fc.BlockCount = 0 + err := fc.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFCFieldInclusionBlockCount tests validating file control block count field inclusion +func TestFCFieldInclusionBlockCount(t *testing.T) { + testFCFieldInclusionBlockCount(t) +} + +// BenchmarkFCFieldInclusionBlockCount benchmarks validating file control block count field inclusion +func BenchmarkFCFieldInclusionBlockCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFCFieldInclusionBlockCount(b) + } +} + +// testFCFieldInclusionEntryAddendaCount validates file control addenda count field inclusion +func testFCFieldInclusionEntryAddendaCount(t testing.TB) { + fc := mockFileControl() + fc.EntryAddendaCount = 0 + err := fc.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFCFieldInclusionEntryAddendaCount tests validating file control addenda count field inclusion +func TestFCFieldInclusionEntryAddendaCount(t *testing.T) { + testFCFieldInclusionEntryAddendaCount(t) +} + +// BenchmarkFCFieldInclusionEntryAddendaCount benchmarks validating file control addenda count field inclusion +func BenchmarkFCFieldInclusionEntryAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFCFieldInclusionEntryAddendaCount(b) + } +} + +// testFCFieldInclusionEntryHash validates file control entry hash field inclusion +func testFCFieldInclusionEntryHash(t testing.TB) { + fc := mockFileControl() + fc.EntryHash = 0 + err := fc.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFCFieldInclusionEntryHash tests validating file control entry hash field inclusion +func TestFCFieldInclusionEntryHash(t *testing.T) { + testFCFieldInclusionEntryHash(t) +} + +// BenchmarkFCFieldInclusionEntryHash benchmarks validating file control entry hash field inclusion +func BenchmarkFCFieldInclusionEntryHash(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFCFieldInclusionEntryHash(b) + } +} diff --git a/fileErrors.go b/fileErrors.go new file mode 100644 index 000000000..7b3f5d6e2 --- /dev/null +++ b/fileErrors.go @@ -0,0 +1,144 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "errors" + "fmt" +) + +var ( + // ErrFileTooLong is the error given when a file exceeds the maximum possible length + ErrFileTooLong = errors.New("file exceeds maximum possible number of lines") + // ErrFileHeader is the error given if there is the wrong number of file headers + ErrFileHeader = errors.New("none or more than one file headers exists") + // ErrFileControl is the error given if there is the wrong number of file control records + ErrFileControl = errors.New("none or more than one file control exists") + // ErrFileEntryOutsideBatch is the error given if an entry is outside of a batch + ErrFileEntryOutsideBatch = errors.New("entry outside of batch") + // ErrFileAddendaOutsideBatch is the error given if an addenda is outside of a batch + ErrFileAddendaOutsideBatch = errors.New("addenda outside of batch") + // ErrFileAddendaOutsideEntry is the error given if an addenda is outside of an entry + ErrFileAddendaOutsideEntry = errors.New("addenda outside of entry") + // ErrFileBatchControlOutsideBatch is the error given if a batch control record is outside of a batch + ErrFileBatchControlOutsideBatch = errors.New("batch control outside of batch") + // ErrFileBatchHeaderInsideBatch is the error given if a batch header record is inside of a batch + ErrFileBatchHeaderInsideBatch = errors.New("batch header inside of batch") + // ErrFileADVOnly is the error given if an ADV only file has a non-ADV batch + ErrFileADVOnly = errors.New("file can only have ADV Batches") + // ErrFileIATSEC is the error given if an IAT batch uses the normal NewBatch + ErrFileIATSEC = errors.New("IAT Standard Entry Class Code should use iatBatch") + // ErrFileNoBatches is the error given if a file has no batches + ErrFileNoBatches = errors.New("must have []*Batches or []*IATBatches to be built") +) + +// RecordWrongLengthErr is the error given when a record is the wrong length +type RecordWrongLengthErr struct { + Message string + Length int +} + +// NewRecordWrongLengthErr creates a new error of the RecordWrongLengthErr type +func NewRecordWrongLengthErr(length int) RecordWrongLengthErr { + return RecordWrongLengthErr{ + Message: fmt.Sprintf("must be 94 characters and found %d", length), + Length: length, + } +} + +func (e RecordWrongLengthErr) Error() string { + return e.Message +} + +// ErrUnknownRecordType is the error given when a record does not have a known type +type ErrUnknownRecordType struct { + Message string + Type string +} + +// NewErrUnknownRecordType creates a new error of the ErrUnknownRecordType type +func NewErrUnknownRecordType(recordType string) ErrUnknownRecordType { + return ErrUnknownRecordType{ + Message: fmt.Sprintf("%s is an unknown record type", recordType), + Type: recordType, + } +} + +func (e ErrUnknownRecordType) Error() string { + return e.Message +} + +// ErrFileUnknownSEC is the error given when a record does not have a known type +type ErrFileUnknownSEC struct { + Message string + SEC string +} + +// NewErrFileUnknownSEC creates a new error of the ErrFileUnknownSEC type +func NewErrFileUnknownSEC(secType string) ErrFileUnknownSEC { + return ErrFileUnknownSEC{ + Message: fmt.Sprintf("%s Standard Entry Class Code is not implemented", secType), + SEC: secType, + } +} + +func (e ErrFileUnknownSEC) Error() string { + return e.Message +} + +// ErrFileCalculatedControlEquality is the error given when the control record does not match the calculated value +type ErrFileCalculatedControlEquality struct { + Message string + Field string + CalculatedValue int + ControlValue int +} + +// NewErrFileCalculatedControlEquality creates a new error of the ErrFileCalculatedControlEquality type +func NewErrFileCalculatedControlEquality(field string, calculated, control int) ErrFileCalculatedControlEquality { + return ErrFileCalculatedControlEquality{ + Message: fmt.Sprintf("%v calculated %v is out-of-balance with file control %v", field, calculated, control), + Field: field, + CalculatedValue: calculated, + ControlValue: control, + } +} + +func (e ErrFileCalculatedControlEquality) Error() string { + return e.Message +} + +// ErrFileBatchNumberAscending is the error given when the batch numbers in a file are not in ascending order +type ErrFileBatchNumberAscending struct { + Message string + PreviousBatch int + CurrentBatch int +} + +// NewErrFileBatchNumberAscending creates a new error of the ErrFileBatchNumberAscending type +func NewErrFileBatchNumberAscending(previous, current int) ErrFileBatchNumberAscending { + return ErrFileBatchNumberAscending{ + Message: fmt.Sprintf("Batch numbers must be in ascending order, batch %v is less than or equal to the previous batch: %v", current, previous), + PreviousBatch: previous, + CurrentBatch: current, + } +} + +func (e ErrFileBatchNumberAscending) Error() string { + return e.Message +} diff --git a/fileHeader.go b/fileHeader.go index d5575f622..5949007bd 100644 --- a/fileHeader.go +++ b/fileHeader.go @@ -1,22 +1,28 @@ -// Copyright 2016 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. package ach import ( - "fmt" "strings" "time" -) + "unicode/utf8" -// Errors specific to a File Header Record -var ( - msgRecordType = "received expecting %d" - msgRecordSize = "is not 094" - msgBlockingFactor = "is not 10" - msgFormatCode = "is not 1" - msgFileCreationDate = "was created before " + time.Now().String() + "github.com/moov-io/base" ) // FileHeader is a Record designating physical file characteristics and identify @@ -24,41 +30,44 @@ var ( // contained in the file. The file header also includes creation date and time // fields which can be used to uniquely identify a file. type FileHeader struct { - // RecordType defines the type of record in the block. headerPos - recordType string - + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` // PriorityCode consists of the numerals 01 priorityCode string // ImmediateDestination contains the Routing Number of the ACH Operator or receiving - // point to which the file is being sent. The 10 character field begins with - // a blank in the first position, followed by the four digit Federal Reserve - // Routing Symbol, the four digit ABA Institution Identifier, and the Check - // Digit (bTTTTAAAAC). - ImmediateDestination int + // point to which the file is being sent. The ach file format specifies a 10 character + // field begins with a blank space in the first position, followed by the four digit + // Federal Reserve Routing Symbol, the four digit ABA Institution Identifier, and the Check + // Digit (bTTTTAAAAC). ImmediateDestinationField will append the blank space to the + // routing number. + ImmediateDestination string `json:"immediateDestination"` // ImmediateOrigin contains the Routing Number of the ACH Operator or sending - // point that is sending the file. The 10 character field begins with - // a blank in the first position, followed by the four digit Federal Reserve - // Routing Symbol, the four digit ABA Institution Identifier, and the Check - // Digit (bTTTTAAAAC). - ImmediateOrigin int - - // FileCreationDate is expressed in a "YYMMDD" format. The File Creation - // Date is the date on which the file is prepared by an ODFI (ACH input files) + // point that is sending the file. The ach file format specifies a 10 character + // field begins with a blank space in the first position, followed by the four digit + // Federal Reserve Routing Symbol, the four digit ABA Institution Identifier, and the Check + // Digit (bTTTTAAAAC). ImmediateOriginField will append the blank space to the + // routing number. + ImmediateOrigin string `json:"immediateOrigin"` + + // FileCreationDate is the date on which the file is prepared by an ODFI (ACH input files) // or the date (exchange date) on which a file is transmitted from ACH Operator // to ACH Operator, or from ACH Operator to RDFIs (ACH output files). - FileCreationDate time.Time + // + // The format is: YYMMDD. Y=Year, M=Month, D=Day + FileCreationDate string `json:"fileCreationDate"` - // FileCreationTime is expressed ina n "HHMM" (24 hour clock) format. - // The system time when the ACH file was created - FileCreationTime time.Time + // FileCreationTime is the system time when the ACH file was created. + // + // The format is: HHmm. H=Hour, m=Minute + FileCreationTime string `json:"fileCreationTime"` // This field should start at zero and increment by 1 (up to 9) and then go to // letters starting at A through Z for each subsequent file that is created for // a single system date. (34-34) 1 numeric 0-9 or uppercase alpha A-Z. // I have yet to see this ID not A - FileIDModifier string + FileIDModifier string `json:"fileIDModifier,omitempty"` // RecordSize indicates the number of characters contained in each // record. At this time, the value "094" must be used. @@ -77,130 +86,166 @@ type FileHeader struct { // ImmediateDestinationName us the name of the ACH or receiving point for which that // file is destined. Name corresponding to the ImmediateDestination - ImmediateDestinationName string + ImmediateDestinationName string `json:"immediateDestinationName"` // ImmediateOriginName is the name of the ACH operator or sending point that is // sending the file. Name corresponding to the ImmediateOrigin - ImmediateOriginName string + ImmediateOriginName string `json:"immediateOriginName"` // ReferenceCode is reserved for information pertinent to the Originator. - ReferenceCode string + ReferenceCode string `json:"referenceCode,omitempty"` // validator is composed for data validation validator // converters is composed for ACH to GoLang Converters converters + + validateOpts *ValidateOpts } // NewFileHeader returns a new FileHeader with default values for none exported fields -func NewFileHeader(params ...FileParam) FileHeader { +func NewFileHeader() FileHeader { fh := FileHeader{ - recordType: "1", priorityCode: "01", FileIDModifier: "A", recordSize: "094", blockingFactor: "10", formatCode: "1", } - if len(params) > 0 { - fh.ImmediateDestination = fh.parseNumField(params[0].ImmediateDestination) - fh.ImmediateOrigin = fh.parseNumField(params[0].ImmediateOrigin) - fh.ImmediateDestinationName = params[0].ImmediateDestinationName - fh.ImmediateOriginName = params[0].ImmediateOriginName - fh.ReferenceCode = params[0].ReferenceCode - fh.FileCreationDate = time.Now() - return fh - } return fh } // Parse takes the input record string and parses the FileHeader values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. func (fh *FileHeader) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + // (character position 1-1) Always "1" - fh.recordType = "1" // (2-3) Always "01" fh.priorityCode = "01" // (4-13) A blank space followed by your ODFI's routing number. For example: " 121140399" - fh.ImmediateDestination = fh.parseNumField(record[3:13]) + fh.ImmediateDestination = trimRoutingNumberLeadingZero(fh.parseStringField(record[3:13])) // (14-23) A 10-digit number assigned to you by the ODFI once they approve you to originate ACH files through them - fh.ImmediateOrigin = fh.parseNumField(record[13:23]) + fh.ImmediateOrigin = trimRoutingNumberLeadingZero(fh.parseStringField(record[13:23])) // 24-29 Today's date in YYMMDD format - // must be after todays date. - fh.FileCreationDate = fh.parseSimpleDate(record[23:29]) - // 30-33 The current time in HHMM format - fh.FileCreationTime = fh.parseSimpleTime(record[29:33]) + // must be after today's date. + fh.FileCreationDate = fh.validateSimpleDate(record[23:29]) + // 30-33 The current time in HHmm format + fh.FileCreationTime = fh.validateSimpleTime(record[29:33]) // 35-37 Always "A" fh.FileIDModifier = record[33:34] // 35-37 always "094" fh.recordSize = "094" - //38-39 always "10" + // 38-39 always "10" fh.blockingFactor = "10" - //40 always "1" + // 40 always "1" fh.formatCode = "1" - //41-63 The name of the ODFI. example "SILICON VALLEY BANK " + // 41-63 The name of the ODFI. example "SILICON VALLEY BANK " fh.ImmediateDestinationName = strings.TrimSpace(record[40:63]) - //64-86 ACH operator or sending point that is sending the file + // 64-86 ACH operator or sending point that is sending the file fh.ImmediateOriginName = strings.TrimSpace(record[63:86]) - //97-94 Optional field that may be used to describe the ACH file for internal accounting purposes + // 97-94 Optional field that may be used to describe the ACH file for internal accounting purposes fh.ReferenceCode = strings.TrimSpace(record[86:94]) } +func trimRoutingNumberLeadingZero(s string) string { + if utf8.RuneCountInString(s) == 10 && s[0] == '0' && s != "0000000000" { + // trim off a leading 0 as ImmediateOriginField or ImmediateDestinationField will pad it back + return strings.TrimSpace(s[1:]) + } + return strings.TrimSpace(s) +} + // String writes the FileHeader struct to a 94 character string. func (fh *FileHeader) String() string { - return fmt.Sprintf("%v%v%v%v%v%v%v%v%v%v%v%v%v", - fh.recordType, - fh.priorityCode, - fh.ImmediateDestinationField(), - fh.ImmediateOriginField(), - fh.FileCreationDateField(), - fh.FileCreationTimeField(), - fh.FileIDModifier, - fh.recordSize, - fh.blockingFactor, - fh.formatCode, - fh.ImmediateDestinationNameField(), - fh.ImmediateOriginNameField(), - fh.ReferenceCodeField(), - ) + var buf strings.Builder + buf.Grow(94) + buf.WriteString(fileHeaderPos) + buf.WriteString(fh.priorityCode) + buf.WriteString(fh.ImmediateDestinationField()) + buf.WriteString(fh.ImmediateOriginField()) + buf.WriteString(fh.FileCreationDateField()) + buf.WriteString(fh.FileCreationTimeField()) + buf.WriteString(fh.FileIDModifier) + buf.WriteString(fh.recordSize) + buf.WriteString(fh.blockingFactor) + buf.WriteString(fh.formatCode) + buf.WriteString(fh.ImmediateDestinationNameField()) + buf.WriteString(fh.ImmediateOriginNameField()) + buf.WriteString(fh.ReferenceCodeField()) + return buf.String() +} +// SetValidation stores ValidateOpts on the FileHeader which are to be used to override +// the default NACHA validation rules. +func (fh *FileHeader) SetValidation(opts *ValidateOpts) { + if fh == nil { + return + } + fh.validateOpts = opts } // Validate performs NACHA format rule checks on the record and returns an error if not Validated // The first error encountered is returned and stops the parsing. func (fh *FileHeader) Validate() error { + return fh.ValidateWith(fh.validateOpts) +} +// ValidateWith performs NACHA format rule checks on each record according to their specification +// overlayed with any custom flags. +// The first error encountered is returned and stops the parsing. +func (fh *FileHeader) ValidateWith(opts *ValidateOpts) error { + if opts == nil { + opts = &ValidateOpts{} + } if err := fh.fieldInclusion(); err != nil { return err } - if fh.recordType != "1" { - msg := fmt.Sprintf(msgRecordType, 1) - return &FieldError{FieldName: "recordType", Value: fh.recordType, Msg: msg} - } if err := fh.isUpperAlphanumeric(fh.FileIDModifier); err != nil { - return &FieldError{FieldName: "FileIDModifier", Value: fh.FileIDModifier, Msg: err.Error()} + return fieldError("FileIDModifier", err, fh.FileIDModifier) } if len(fh.FileIDModifier) != 1 { - msg := fmt.Sprintf(msgValidFieldLength, 1) - return &FieldError{FieldName: "FileIDModifier", Value: fh.FileIDModifier, Msg: msg} + return fieldError("FileIDModifier", NewErrValidFieldLength(1), fh.FileIDModifier) } if fh.recordSize != "094" { - return &FieldError{FieldName: "recordSize", Value: fh.recordSize, Msg: msgRecordSize} + return fieldError("recordSize", ErrRecordSize, fh.recordSize) } if fh.blockingFactor != "10" { - return &FieldError{FieldName: "blockingFactor", Value: fh.blockingFactor, Msg: msgBlockingFactor} + return fieldError("blockingFactor", ErrBlockingFactor, fh.blockingFactor) } if fh.formatCode != "1" { - return &FieldError{FieldName: "formatCode", Value: fh.formatCode, Msg: msgFormatCode} + return fieldError("formatCode", ErrFormatCode, fh.formatCode) } if err := fh.isAlphanumeric(fh.ImmediateDestinationName); err != nil { - return &FieldError{FieldName: "ImmediateDestinationName", Value: fh.ImmediateDestinationName, Msg: err.Error()} + return fieldError("ImmediateDestinationName", err, fh.ImmediateDestinationName) + } + if !opts.BypassOriginValidation { + if opts.RequireABAOrigin { + if err := CheckRoutingNumber(fh.ImmediateOrigin); err != nil { + return fieldError("ImmediateOrigin", ErrConstructor, fh.ImmediateOrigin) + } + } else { + if fh.ImmediateOrigin == "0000000000" { + return fieldError("ImmediateOrigin", ErrConstructor, fh.ImmediateOrigin) + } + } + } + if !opts.BypassDestinationValidation { + if fh.ImmediateDestination == "000000000" { + return fieldError("ImmediateDestination", ErrConstructor, fh.ImmediateDestination) + } + if err := CheckRoutingNumber(fh.ImmediateDestination); err != nil { + return fieldError("ImmediateDestination", err, fh.ImmediateDestination) + } } if err := fh.isAlphanumeric(fh.ImmediateOriginName); err != nil { - return &FieldError{FieldName: "ImmediateOriginName", Value: fh.ImmediateOriginName, Msg: err.Error()} + return fieldError("ImmediateOriginName", err, fh.ImmediateOriginName) } if err := fh.isAlphanumeric(fh.ReferenceCode); err != nil { - return &FieldError{FieldName: "ReferenceCode", Value: fh.ReferenceCode, Msg: err.Error()} + return fieldError("ReferenceCode", err, fh.ReferenceCode) } - // todo: handle test cases for before date /* if fh.fileCreationDate.Before(time.Now()) { @@ -213,51 +258,86 @@ func (fh *FileHeader) Validate() error { // fieldInclusion validate mandatory fields are not default values. If fields are // invalid the ACH transfer will be returned. func (fh *FileHeader) fieldInclusion() error { - if fh.recordType == "" { - return &FieldError{FieldName: "recordType", Value: fh.recordType, Msg: msgFieldInclusion} + if fh.ImmediateDestination == "" { + return fieldError("ImmediateDestination", ErrConstructor, fh.ImmediateDestinationField()) } - if fh.ImmediateDestination == 0 { - return &FieldError{FieldName: "ImmediateDestination", Value: fh.ImmediateDestinationField(), Msg: msgFieldInclusion} + if fh.ImmediateOrigin == "" { + return fieldError("ImmediateOrigin", ErrConstructor, fh.ImmediateOriginField()) } - if fh.ImmediateOrigin == 0 { - return &FieldError{FieldName: "ImmediateOrigin", Value: fh.ImmediateOriginField(), Msg: msgFieldInclusion} - } - if fh.FileCreationDate.IsZero() { - return &FieldError{FieldName: "FileCreationDate", Value: fh.FileCreationDate.String(), Msg: msgFieldInclusion} + if fh.FileCreationDate == "" { + return fieldError("FileCreationDate", ErrConstructor, fh.FileCreationDate) } if fh.FileIDModifier == "" { - return &FieldError{FieldName: "FileIDModifier", Value: fh.FileIDModifier, Msg: msgFieldInclusion} + return fieldError("FileIDModifier", ErrConstructor, fh.FileIDModifier) } if fh.recordSize == "" { - return &FieldError{FieldName: "recordSize", Value: fh.recordSize, Msg: msgFieldInclusion} + return fieldError("recordSize", ErrConstructor, fh.recordSize) } if fh.blockingFactor == "" { - return &FieldError{FieldName: "blockingFactor", Value: fh.blockingFactor, Msg: msgFieldInclusion} + return fieldError("blockingFactor", ErrConstructor, fh.blockingFactor) } if fh.formatCode == "" { - return &FieldError{FieldName: "formatCode", Value: fh.formatCode, Msg: msgFieldInclusion} + return fieldError("formatCode", ErrConstructor, fh.formatCode) } return nil } // ImmediateDestinationField gets the immediate destination number with zero padding func (fh *FileHeader) ImmediateDestinationField() string { - return " " + fh.numericField(fh.ImmediateDestination, 9) + if fh.ImmediateDestination == "" { + return strings.Repeat(" ", 10) + } + fh.ImmediateDestination = strings.TrimSpace(fh.ImmediateDestination) + if fh.validateOpts != nil && fh.validateOpts.BypassDestinationValidation && len(fh.ImmediateDestination) == 10 { + return fh.ImmediateDestination + } + return " " + fh.stringField(fh.ImmediateDestination, 9) } // ImmediateOriginField gets the immediate origin number with 0 padding func (fh *FileHeader) ImmediateOriginField() string { - return " " + fh.numericField(fh.ImmediateOrigin, 9) + if fh.ImmediateOrigin == "" { + return strings.Repeat(" ", 10) + } + fh.ImmediateOrigin = strings.TrimSpace(fh.ImmediateOrigin) + if fh.validateOpts != nil && fh.validateOpts.BypassOriginValidation && len(fh.ImmediateOrigin) == 10 { + return fh.ImmediateOrigin + } + return " " + fh.stringField(fh.ImmediateOrigin, 9) } -// FileCreationDateField gets the file creation date in YYMMDD format +// FileCreationDateField gets the file creation date in YYMMDD (year, month, day) format +// A blank string is returned when an error occurred while parsing the timestamp. ISO 8601 +// is the only other format supported. func (fh *FileHeader) FileCreationDateField() string { - return fh.formatSimpleDate(fh.FileCreationDate) + switch utf8.RuneCountInString(fh.FileCreationDate) { + case 0: + return time.Now().Format("060102") + case 6: + return fh.formatSimpleDate(fh.FileCreationDate) // YYMMDD + } + t, err := time.Parse(base.ISO8601Format, fh.FileCreationDate) + if err != nil { + return "" + } + return t.Format("060102") // YYMMDD } -// FileCreationTimeField gets the file creation time in HHMM format +// FileCreationTimeField gets the file creation time in HHmm (hour, minute) format +// A blank string is returned when an error occurred while parsing the timestamp. ISO 8601 +// is the only other format supported. func (fh *FileHeader) FileCreationTimeField() string { - return fh.formatSimpleTime(fh.FileCreationTime) + switch utf8.RuneCountInString(fh.FileCreationTime) { + case 0: + return time.Now().Format("1504") + case 4: + return fh.formatSimpleTime(fh.FileCreationTime) // HHmm + } + t, err := time.Parse(base.ISO8601Format, fh.FileCreationTime) + if err != nil { + return "" + } + return t.Format("1504") // HHmm } // ImmediateDestinationNameField gets the ImmediateDestinationName field padded diff --git a/fileHeader_internal_test.go b/fileHeader_internal_test.go deleted file mode 100644 index 5cfe9a45a..000000000 --- a/fileHeader_internal_test.go +++ /dev/null @@ -1,318 +0,0 @@ -// Copyright 2017 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE File. - -package ach - -import ( - "strings" - "testing" - "time" -) - -// mockFileHeader build a validate File Header for tests -func mockFileHeader() FileHeader { - fh := NewFileHeader() - fh.ImmediateDestination = 9876543210 - fh.ImmediateOrigin = 1234567890 - fh.FileCreationDate = time.Now() - fh.ImmediateDestinationName = "Federal Reserve Bank" - fh.ImmediateOriginName = "My Bank Name" - return fh -} - -func TestMockFileHeader(t *testing.T) { - fh := mockFileHeader() - if err := fh.Validate(); err != nil { - t.Error("mockFileHeader does not validate and will break other tests") - } - if fh.ImmediateDestination != 9876543210 { - t.Error("ImmediateDestination depedendent default value has changed") - } - if fh.ImmediateOrigin != 1234567890 { - t.Error("ImmediateOrigin depedendent default value has changed") - } - if fh.ImmediateDestinationName != "Federal Reserve Bank" { - t.Error("ImmediateDestinationName depedendent default value has changed") - } - if fh.ImmediateOriginName != "My Bank Name" { - t.Error("ImmediateOriginName depedendent default value has changed") - } -} - -// TestParseFileHeader parses a known File Header Record string. -func TestParseFileHeader(t *testing.T) { - var line = "101 076401251 0764012510807291511A094101achdestname companyname " - r := NewReader(strings.NewReader(line)) - r.line = line - if err := r.parseFileHeader(); err != nil { - t.Errorf("%T: %s", err, err) - } - record := r.File.Header - - if record.recordType != "1" { - t.Errorf("RecordType Expected 1 got: %v", record.recordType) - } - if record.priorityCode != "01" { - t.Errorf("PriorityCode Expected 01 got: %v", record.priorityCode) - } - if record.ImmediateDestinationField() != " 076401251" { - t.Errorf("ImmediateDestination Expected ' 076401251' got: %v", record.ImmediateDestinationField()) - } - if record.ImmediateOriginField() != " 076401251" { - t.Errorf("ImmediateOrigin Expected ' 076401251' got: %v", record.ImmediateOriginField()) - } - - if record.FileCreationDateField() != "080729" { - t.Errorf("FileCreationDate Expected '080729' got:'%v'", record.FileCreationDateField()) - } - if record.FileCreationTimeField() != "1511" { - t.Errorf("FileCreationTime Expected '1511' got:'%v'", record.FileCreationTimeField()) - } - - if record.FileIDModifier != "A" { - t.Errorf("FileIDModifier Expected 'A' got:'%v'", record.FileIDModifier) - } - if record.recordSize != "094" { - t.Errorf("RecordSize Expected '094' got:'%v'", record.recordSize) - } - if record.blockingFactor != "10" { - t.Errorf("BlockingFactor Expected '10' got:'%v'", record.blockingFactor) - } - if record.formatCode != "1" { - t.Errorf("FormatCode Expected '1' got:'%v'", record.formatCode) - } - if record.ImmediateDestinationNameField() != "achdestname " { - t.Errorf("ImmediateDestinationName Expected 'achdestname ' got:'%v'", record.ImmediateDestinationNameField()) - } - if record.ImmediateOriginNameField() != "companyname " { - t.Errorf("ImmidiateOriginName Expected 'companyname ' got: '%v'", record.ImmediateOriginNameField()) - } - if record.ReferenceCodeField() != " " { - t.Errorf("ReferenceCode Expected ' ' got:'%v'", record.ReferenceCodeField()) - } -} - -// TestString validats that a known parsed file can be return to a string of the same value -func TestFHString(t *testing.T) { - var line = "101 076401251 0764012510807291511A094101achdestname companyname " - r := NewReader(strings.NewReader(line)) - r.line = line - if err := r.parseFileHeader(); err != nil { - t.Errorf("%T: %s", err, err) - } - record := r.File.Header - - if record.String() != line { - t.Errorf("Strings do not match") - } -} - -// TestValidateFHRecordType ensure error if recordType is not 1 -func TestValidateFHRecordType(t *testing.T) { - fh := mockFileHeader() - fh.recordType = "2" - if err := fh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "recordType" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -// TestValidateIDModifier ensure ID Modiier is upper alphanumeric -func TestValidateIDModifier(t *testing.T) { - fh := mockFileHeader() - fh.FileIDModifier = "®" - if err := fh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "FileIDModifier" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -// TestValidateRecordSize ensure record size is "094" -func TestValidateRecordSize(t *testing.T) { - fh := mockFileHeader() - fh.recordSize = "666" - if err := fh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "recordSize" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -// TestBlockingFactor ensure blocking factor is "10" -func TestBlockingFactor(t *testing.T) { - fh := mockFileHeader() - fh.blockingFactor = "99" - if err := fh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "blockingFactor" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -// TestFormatCode ensure format code is "1" -func TestFormatCode(t *testing.T) { - fh := mockFileHeader() - fh.formatCode = "2" - if err := fh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "formatCode" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestFHFieldInculsion(t *testing.T) { - fh := mockFileHeader() - fh.ImmediateOrigin = 0 - if err := fh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestUpperLengthFileID(t *testing.T) { - fh := mockFileHeader() - fh.FileIDModifier = "a" - if err := fh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "FileIDModifier" { - t.Errorf("%T: %s", err, err) - } - } - } - - fh.FileIDModifier = "AA" - if err := fh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "FileIDModifier" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestImmediateDestinationNameAlphaNumeric(t *testing.T) { - fh := mockFileHeader() - fh.ImmediateDestinationName = "Super Big Bank" - fh.ImmediateDestinationName = "Big ®$$ Bank" - if err := fh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "ImmediateDestinationName" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestImmediateOriginNameAlphaNumeric(t *testing.T) { - fh := mockFileHeader() - fh.ImmediateOriginName = "Super Big Bank" - fh.ImmediateOriginName = "Bigger ®$$ Bank" - if err := fh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "ImmediateOriginName" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestImmediateReferenceCodeAlphaNumeric(t *testing.T) { - fh := mockFileHeader() - fh.ReferenceCode = " " - fh.ReferenceCode = "®" - if err := fh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.FieldName != "ReferenceCode" { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestFHFieldInclusionRecordType(t *testing.T) { - fh := mockFileHeader() - fh.recordType = "" - if err := fh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestFHFieldInclusionImmediatDestination(t *testing.T) { - fh := mockFileHeader() - fh.ImmediateDestination = 0 - if err := fh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestFHFieldInclusionFileIDModifier(t *testing.T) { - fh := mockFileHeader() - fh.FileIDModifier = "" - if err := fh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestFHFieldInclusionRecordSize(t *testing.T) { - fh := mockFileHeader() - fh.recordSize = "" - if err := fh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestFHFieldInclusionBlockingFactor(t *testing.T) { - fh := mockFileHeader() - fh.blockingFactor = "" - if err := fh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} - -func TestFHFieldInclusionFormatCode(t *testing.T) { - fh := mockFileHeader() - fh.formatCode = "" - if err := fh.Validate(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } - } -} diff --git a/fileHeader_test.go b/fileHeader_test.go new file mode 100644 index 000000000..31e61faf0 --- /dev/null +++ b/fileHeader_test.go @@ -0,0 +1,784 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "testing" + "time" + + "github.com/moov-io/base" +) + +// mockFileHeader build a validate File Header for tests +func mockFileHeader() FileHeader { + fh := NewFileHeader() + fh.ImmediateDestination = "231380104" + fh.ImmediateOrigin = "121042882" + fh.FileCreationDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + return fh +} + +// testMockFileHeader validates a file header +func testMockFileHeader(t testing.TB) { + fh := mockFileHeader() + if err := fh.Validate(); err != nil { + t.Error("mockFileHeader does not validate and will break other tests") + } + if fh.ImmediateDestination != "231380104" { + t.Error("ImmediateDestination dependent default value has changed") + } + if fh.ImmediateOrigin != "121042882" { + t.Error("ImmediateOrigin dependent default value has changed") + } + if fh.ImmediateDestinationName != "Federal Reserve Bank" { + t.Error("ImmediateDestinationName dependent default value has changed") + } + if fh.ImmediateOriginName != "My Bank Name" { + t.Error("ImmediateOriginName dependent default value has changed") + } +} + +// TestMockFileHeader tests validating a file header +func TestMockFileHeader(t *testing.T) { + testMockFileHeader(t) +} + +// BenchmarkMockFileHeader benchmarks validating a file header +func BenchmarkMockFileHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testMockFileHeader(b) + } +} + +func TestFileHeader__ImmediateOrigin(t *testing.T) { + // From https://github.com/moov-io/ach/issues/510 + // We should allow a blank space or '1' followed by a 9 digit routing number and 9 digits + header := NewFileHeader() + header.ImmediateOrigin = " 123456789" // ' ' + routing number + if v := header.ImmediateOriginField(); v != " 123456789" { + t.Errorf("got %q", v) + } + header.ImmediateOrigin = "123456789" // 9 digit routing number + if v := header.ImmediateOriginField(); v != " 123456789" { + t.Errorf("got %q", v) + } + header.ImmediateOrigin = trimRoutingNumberLeadingZero("0123456789") // 0 + routing number + if v := header.ImmediateOriginField(); v != " 123456789" { + t.Errorf("got %q", v) + } + header.ImmediateOrigin = trimRoutingNumberLeadingZero("1123456789") // 1 + routing number + if v := header.ImmediateOriginField(); v != " 112345678" { + t.Errorf("got %q", v) + } + + // Test with BypassOriginValidation + header.SetValidation(&ValidateOpts{BypassOriginValidation: true}) + header.ImmediateOrigin = "1234567899" + if v := header.ImmediateOriginField(); v != header.ImmediateOrigin { + t.Errorf("got %q", v) + } +} + +func TestFileHeader__ImmediateDestination(t *testing.T) { + tests := []struct { + name string + destinationNumber string + expected string + bypassDestinationValidation bool + }{ + { + name: "no change", + destinationNumber: " 123456789", + expected: " 123456789", + }, + { + name: "should append a space", + destinationNumber: "123456789", + expected: " 123456789", + }, + { + name: "0 + routing number", + destinationNumber: trimRoutingNumberLeadingZero("0123456789"), + expected: " 123456789", + }, + { + name: "1 + routing number", + destinationNumber: "1123456789", + expected: " 112345678", + }, + { + name: "bypass destination validation", + destinationNumber: "1234567899", + expected: "1234567899", + bypassDestinationValidation: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + header := NewFileHeader() + header.ImmediateDestination = tt.destinationNumber + + if tt.bypassDestinationValidation { + header.SetValidation(&ValidateOpts{BypassDestinationValidation: true}) + } + + if v := header.ImmediateDestinationField(); v != tt.expected { + t.Errorf("want \"%v\", got \"%v\"", tt.expected, v) + } + }) + } +} + +func TestFileHeader__trimRoutingNumberLeadingZero(t *testing.T) { + tests := []struct { + input string + want string + }{ + { + input: "0123456789", + want: "123456789", + }, + { + input: "0012345678", + want: "012345678", + }, + { + input: strings.Repeat("0", 10), + want: strings.Repeat("0", 10), + }, + } + for _, tt := range tests { + if got := trimRoutingNumberLeadingZero(tt.input); got != tt.want { + t.Errorf("want \"%v\", got \"%v\"", tt.want, got) + } + } +} + +// parseFileHeader validates parsing a file header +func parseFileHeader(t testing.TB) { + var line = "101 076401251 0764012511807291511A094101achdestname companyname " + r := NewReader(strings.NewReader(line)) + r.line = line + if err := r.parseFileHeader(); err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.File.Header + + if record.priorityCode != "01" { + t.Errorf("PriorityCode Expected 01 got: %v", record.priorityCode) + } + if record.ImmediateDestinationField() != " 076401251" { + t.Errorf("ImmediateDestination Expected ' 076401251' got: %v", record.ImmediateDestinationField()) + } + if record.ImmediateOriginField() != " 076401251" { + t.Errorf("ImmediateOrigin Expected ' 076401251' got: %v", record.ImmediateOriginField()) + } + + if record.FileCreationDateField() != "180729" { + t.Errorf("FileCreationDate Expected '180729' got:'%v'", record.FileCreationDateField()) + } + + if record.FileCreationTimeField() != "1511" { + t.Errorf("FileCreationTime Expected '1900' got:'%v'", record.FileCreationTimeField()) + } + + if record.FileIDModifier != "A" { + t.Errorf("FileIDModifier Expected 'A' got:'%v'", record.FileIDModifier) + } + if record.recordSize != "094" { + t.Errorf("RecordSize Expected '094' got:'%v'", record.recordSize) + } + if record.blockingFactor != "10" { + t.Errorf("BlockingFactor Expected '10' got:'%v'", record.blockingFactor) + } + if record.formatCode != "1" { + t.Errorf("FormatCode Expected '1' got:'%v'", record.formatCode) + } + if record.ImmediateDestinationNameField() != "achdestname " { + t.Errorf("ImmediateDestinationName Expected 'achdestname ' got:'%v'", record.ImmediateDestinationNameField()) + } + if record.ImmediateOriginNameField() != "companyname " { + t.Errorf("ImmediateOriginName Expected 'companyname ' got: '%v'", record.ImmediateOriginNameField()) + } + if record.ReferenceCodeField() != " " { + t.Errorf("ReferenceCode Expected ' ' got:'%v'", record.ReferenceCodeField()) + } +} + +// TestParseFileHeader test validates parsing a file header +func TestParseFileHeader(t *testing.T) { + parseFileHeader(t) +} + +// BenchmarkParseFileHeader benchmark validates parsing a file header +func BenchmarkParseFileHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + parseFileHeader(b) + } +} + +// testFHString validates that a known parsed file can return to a string of the same value +func testFHString(t testing.TB) { + var line = "101 076401251 0764012511807291511A094101achdestname companyname " + r := NewReader(strings.NewReader(line)) + r.line = line + if err := r.parseFileHeader(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestFHString tests validating that a known parsed file can return to a string of the same value +func TestFHString(t *testing.T) { + testFHString(t) +} + +// BenchmarkFHString benchmarks validating that a known parsed file +// can return to a string of the same value +func BenchmarkFHString(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFHString(b) + } +} + +// testValidateIDModifier validates ID modifier is upper alphanumeric +func testValidateIDModifier(t testing.TB) { + fh := mockFileHeader() + fh.FileIDModifier = "®" + err := fh.Validate() + if !base.Match(err, ErrUpperAlpha) { + t.Errorf("%T: %s", err, err) + } +} + +// TestValidateIDModifier tests validating ID modifier is upper alphanumeric +func TestValidateIDModifier(t *testing.T) { + testValidateIDModifier(t) +} + +// BenchmarkValidateIDModifier benchmarks validating ID modifier is upper alphanumeric +func BenchmarkValidateIDModifier(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testValidateIDModifier(b) + } +} + +// testValidateRecordSize validates record size is "094" +func testValidateRecordSize(t testing.TB) { + fh := mockFileHeader() + fh.recordSize = "666" + err := fh.Validate() + if !base.Match(err, ErrRecordSize) { + t.Errorf("%T: %s", err, err) + } +} + +// TestValidateRecordSize tests validating record size is "094" +func TestValidateRecordSize(t *testing.T) { + testValidateRecordSize(t) +} + +// BenchmarkValidateRecordSize benchmarks validating record size is "094" +func BenchmarkValidateRecordSize(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testValidateRecordSize(b) + } +} + +// testBlockingFactor validates blocking factor is "10" +func testBlockingFactor(t testing.TB) { + fh := mockFileHeader() + fh.blockingFactor = "99" + err := fh.Validate() + if !base.Match(err, ErrBlockingFactor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBlockingFactor tests validating blocking factor is "10" +func TestBlockingFactor(t *testing.T) { + testBlockingFactor(t) +} + +// BenchmarkBlockingFactor benchmarks validating blocking factor is "10" +func BenchmarkBlockingFactor(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBlockingFactor(b) + } +} + +// testFormatCode validates format code is "1" +func testFormatCode(t testing.TB) { + fh := mockFileHeader() + fh.formatCode = "2" + err := fh.Validate() + if !base.Match(err, ErrFormatCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFormatCode tests validating format code is "1" +func TestFormatCode(t *testing.T) { + testFormatCode(t) +} + +// BenchmarkFormatCode benchmarks validating format code is "1" +func BenchmarkFormatCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFormatCode(b) + } +} + +// testFHFieldInclusion validates file header field inclusion +func testFHFieldInclusion(t testing.TB) { + fh := mockFileHeader() + fh.ImmediateOrigin = "" + err := fh.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFHFieldInclusion tests validating file header field inclusion +func TestFHFieldInclusion(t *testing.T) { + testFHFieldInclusion(t) +} + +// BenchmarkFHFieldInclusion benchmarks validating file header field inclusion +func BenchmarkFHFieldInclusion(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFHFieldInclusion(b) + } +} + +// testUpperLengthFileID validates file ID +func testUpperLengthFileID(t testing.TB) { + fh := mockFileHeader() + fh.FileIDModifier = "a" + err := fh.Validate() + if !base.Match(err, ErrUpperAlpha) { + t.Errorf("%T: %s", err, err) + } + + fh.FileIDModifier = "AA" + err = fh.Validate() + if !base.Match(err, NewErrValidFieldLength(1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestUpperLengthFileID tests validating file ID +func TestUpperLengthFileID(t *testing.T) { + testUpperLengthFileID(t) +} + +// BenchmarkUpperLengthFileID benchmarks validating file ID +func BenchmarkUpperLengthFileID(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testUpperLengthFileID(b) + } +} + +// testImmediateDestinationNameAlphaNumeric validates immediate destination name is alphanumeric +func testImmediateDestinationNameAlphaNumeric(t testing.TB) { + fh := mockFileHeader() + fh.ImmediateDestinationName = "Super Big Bank" + fh.ImmediateDestinationName = "Big ®$$ Bank" + err := fh.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestImmediateDestinationNameAlphaNumeric tests validating +// immediate destination name is alphanumeric +func TestImmediateDestinationNameAlphaNumeric(t *testing.T) { + testImmediateDestinationNameAlphaNumeric(t) +} + +// BenchmarkImmediateDestinationNameAlphaNumeric benchmarks validating +// immediate destination name is alphanumeric +func BenchmarkImmediateDestinationNameAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testImmediateDestinationNameAlphaNumeric(b) + } +} + +// testImmediateOriginNameAlphaNumeric validates immediate origin name is alphanumeric +func testImmediateOriginNameAlphaNumeric(t testing.TB) { + fh := mockFileHeader() + fh.ImmediateOriginName = "Super Big Bank" + fh.ImmediateOriginName = "Bigger ®$$ Bank" + err := fh.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestImmediateOriginNameAlphaNumeric tests validating immediate origin name is alphanumeric +func TestImmediateOriginNameAlphaNumeric(t *testing.T) { + testImmediateOriginNameAlphaNumeric(t) +} + +// BenchmarkImmediateOriginNameAlphaNumeric benchmarks validating +// immediate origin name is alphanumeric +func BenchmarkImmediateOriginNameAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testImmediateOriginNameAlphaNumeric(b) + } +} + +// testImmediateReferenceCodeAlphaNumeric validates immediate reference is alphanumeric +func testImmediateReferenceCodeAlphaNumeric(t testing.TB) { + fh := mockFileHeader() + fh.ReferenceCode = " " + fh.ReferenceCode = "®" + err := fh.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestImmediateReferenceCodeAlphaNumeric tests validating immediate reference is alphanumeric +func TestImmediateReferenceCodeAlphaNumeric(t *testing.T) { + testImmediateReferenceCodeAlphaNumeric(t) +} + +// BenchmarkImmediateReferenceCodeAlphaNumeric benchmarks validating immediate reference is alphanumeric +func BenchmarkImmediateReferenceCodeAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testImmediateReferenceCodeAlphaNumeric(b) + } +} + +// testFHFieldInclusionImmediateDestination validates immediate destination field inclusion +func testFHFieldInclusionImmediateDestination(t testing.TB) { + fh := mockFileHeader() + fh.ImmediateDestination = "" + err := fh.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFHFieldInclusionImmediateDestination tests validates immediate destination field inclusion +func TestFHFieldInclusionImmediateDestination(t *testing.T) { + testFHFieldInclusionImmediateDestination(t) +} + +// BenchmarkFHFieldInclusionImmediateDestination benchmarks validates immediate destination field inclusion +func BenchmarkFHFieldInclusionImmediateDestination(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFHFieldInclusionImmediateDestination(b) + } +} + +// testFHFieldInclusionFileIDModifier validates file ID modifier field inclusion +func testFHFieldInclusionFileIDModifier(t testing.TB) { + fh := mockFileHeader() + fh.FileIDModifier = "" + err := fh.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFHFieldInclusionFileIDModifier tests validating file ID modifier field inclusion +func TestFHFieldInclusionFileIDModifier(t *testing.T) { + testFHFieldInclusionFileIDModifier(t) +} + +// BenchmarkFHFieldInclusionFileIDModifier benchmarks validating file ID modifier field inclusion +func BenchmarkFHFieldInclusionFileIDModifier(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFHFieldInclusionFileIDModifier(b) + } +} + +// testFHFieldInclusionRecordSize validates record size field inclusion +func testFHFieldInclusionRecordSize(t testing.TB) { + fh := mockFileHeader() + fh.recordSize = "" + err := fh.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFHFieldInclusionRecordSize tests validating record size field inclusion +func TestFHFieldInclusionRecordSize(t *testing.T) { + testFHFieldInclusionRecordSize(t) +} + +// BenchmarkFHFieldInclusionRecordSize benchmarks validating record size field inclusion +func BenchmarkFHFieldInclusionRecordSize(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFHFieldInclusionRecordSize(b) + } +} + +// testFHFieldInclusionBlockingFactor validates blocking factor field inclusion +func testFHFieldInclusionBlockingFactor(t testing.TB) { + fh := mockFileHeader() + fh.blockingFactor = "" + err := fh.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFHFieldInclusionBlockingFactor tests validating blocking factor field inclusion +func TestFHFieldInclusionBlockingFactor(t *testing.T) { + testFHFieldInclusionBlockingFactor(t) +} + +// BenchmarkFHFieldInclusionBlockingFactor benchmarks +// validating blocking factor field inclusion +func BenchmarkFHFieldInclusionBlockingFactor(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFHFieldInclusionBlockingFactor(b) + } +} + +// testFHFieldInclusionFormatCode validates format code field inclusion +func testFHFieldInclusionFormatCode(t testing.TB) { + fh := mockFileHeader() + fh.formatCode = "" + err := fh.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFHFieldInclusionFormatCode tests validating format code field inclusion +func TestFHFieldInclusionFormatCode(t *testing.T) { + testFHFieldInclusionFormatCode(t) +} + +// BenchmarkFHFieldInclusionFormatCode benchmarks validating format code field inclusion +func BenchmarkFHFieldInclusionFormatCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFHFieldInclusionFormatCode(b) + } +} + +func TestFHImmediateDestinationInvalidLength(t *testing.T) { + fh := mockFileHeader() + fh.ImmediateDestination = "198387" + err := fh.Validate() + if !strings.Contains(err.Error(), "invalid routing number length") { + t.Errorf("%T: %s", err, err) + } +} + +func TestFHImmediateDestinationInvalidCheckSum(t *testing.T) { + fh := mockFileHeader() + fh.ImmediateDestination = "121042880" + err := fh.Validate() + if !strings.Contains(err.Error(), "routing number checksum mismatch") { + t.Errorf("%T: %s", err, err) + } +} + +func TestFHImmediateOriginValidate(t *testing.T) { + fh := mockFileHeader() + fh.ImmediateOrigin = "0000000000" + if err := fh.Validate(); err == nil { + t.Error("expected error") + } + + // use an alphanumeric code (NACHA rules allow this with specific + // agreements between the ODFI and originator) + fh.ImmediateOrigin = "ABC124" + if err := fh.Validate(); err != nil { + t.Error(err) + } +} + +func TestFHFieldInclusionFileCreationDate(t *testing.T) { + fh := mockFileHeader() + fh.FileCreationDate = "" + if err := fh.Validate(); !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// testFileHeaderCreationDate validates creation date field inclusion +func testFileHeaderCreationDate(t testing.TB) { + fh := mockFileHeader() + fh.FileCreationDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + if err := fh.Validate(); !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } + + fh.FileCreationDate = time.Now().Format(base.ISO8601Format) + if err := fh.Validate(); !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } + yymmdd := time.Now().Format("060102") + if v := fh.FileCreationDateField(); v != yymmdd { + t.Errorf("got %q", v) + } + + fh.FileCreationDate = " " + if err := fh.Validate(); !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } + + fh.FileCreationDate = "" + if v := fh.FileCreationDateField(); len(v) != 6 { + t.Errorf("got %q", v) + } + + fh.FileCreationDate = "05/01/2019" // non ISO 8601 date + if err := fh.Validate(); !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } + if v := fh.FileCreationDateField(); v != "" { + t.Errorf("got %q", v) + } +} + +// TestFileHeaderCreationDate tests validating creation date field inclusion +func TestFileHeaderCreationDate(t *testing.T) { + testFileHeaderCreationDate(t) +} + +// BenchmarkFileHeaderCreationDate benchmarks validating creation date field inclusion +func BenchmarkFileHeaderCreationDate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileHeaderCreationDate(b) + } +} + +// testFileHeaderCreationTime validates creation date field inclusion +func testFileHeaderCreationTime(t testing.TB) { + fh := mockFileHeader() + fh.FileCreationTime = time.Now().AddDate(0, 0, 1).Format("1504") // HHmm + if err := fh.Validate(); !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } + + fh.FileCreationTime = time.Now().Format(base.ISO8601Format) + if err := fh.Validate(); !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } + if v := fh.FileCreationTimeField(); len(v) != 4 { + t.Errorf("got %q", v) + } + + fh.FileCreationTime = " " + if err := fh.Validate(); !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } + + fh.FileCreationTime = "" + if v := fh.FileCreationTimeField(); len(v) != 4 { + t.Errorf("got %q", v) + } + + fh.FileCreationTime = "05/01/2019" // non ISO 8601 date + if err := fh.Validate(); !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } + if v := fh.FileCreationTimeField(); v != "" { + t.Errorf("got %q", v) + } +} + +// TestFileHeaderCreationTime tests validating creation date field inclusion +func TestFileHeaderCreationTime(t *testing.T) { + testFileHeaderCreationTime(t) +} + +// BenchmarkFileHeaderCreationTime benchmarks validating creation date field inclusion +func BenchmarkFileHeaderCreationTime(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileHeaderCreationTime(b) + } +} + +func TestFileHeader__ValidateOrigin(t *testing.T) { + fh := mockFileHeader() + fh.ImmediateOrigin = "0000000000" + + err := fh.ValidateWith(&ValidateOpts{ + RequireABAOrigin: true, + }) + if err != nil { + if !strings.Contains(err.Error(), ErrConstructor.Error()) { + t.Errorf("unexpected error: %v", err) + } + } + + err = fh.ValidateWith(&ValidateOpts{}) + if err == nil { + t.Error("expected error") + } + + err = fh.ValidateWith(&ValidateOpts{ + BypassOriginValidation: true, + }) + if err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +func TestFileHeader__SetValidation(t *testing.T) { + fh := mockFileHeader() + fh.SetValidation(nil) + fh.SetValidation(&ValidateOpts{}) +} + +func TestFileHeader__ValidateDestination(t *testing.T) { + fh := mockFileHeader() + fh.ImmediateDestination = "123456789" // invalid routing number + + if err := fh.ValidateWith(&ValidateOpts{}); err != nil { + if !strings.Contains(err.Error(), "routing number checksum mismatch") { + t.Error(err) + } + } + + // skip ImmediateDestination validation + if err := fh.ValidateWith(&ValidateOpts{BypassDestinationValidation: true}); err != nil { + t.Error(err) + } +} diff --git a/file_test.go b/file_test.go index ea07c1ba8..8b14a09c1 100644 --- a/file_test.go +++ b/file_test.go @@ -1,123 +1,299 @@ -// Copyright 2017 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. package ach import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "strconv" + "strings" "testing" + "time" + + "github.com/moov-io/base" + + "github.com/stretchr/testify/require" ) +// mockFilePPD creates an ACH file with PPD batch and entry func mockFilePPD() *File { mockFile := NewFile() + mockFile.ID = "fileId" mockFile.SetHeader(mockFileHeader()) + mockFile.Header.ID = mockFile.ID + mockFile.Control = mockFileControl() + mockFile.Control.ID = mockFile.ID mockBatch := mockBatchPPD() mockFile.AddBatch(mockBatch) if err := mockFile.Create(); err != nil { panic(err) } + if mockFile.ID != mockFile.Header.ID { + panic(fmt.Sprintf("mockFile.ID=%s mockFile.Header.ID=%s", mockFile.ID, mockFile.Header.ID)) + } + if mockFile.ID != mockFile.Control.ID { + panic(fmt.Sprintf("mockFile.ID=%s mockFile.Control.ID=%s", mockFile.ID, mockFile.Control.ID)) + } return mockFile } -func TestFileError(t *testing.T) { +func mockFileADV() *File { + mockFile := NewFile() + mockFile.ID = "fileId" + mockFile.SetHeader(mockFileHeader()) + mockFile.Header.ID = mockFile.ID + mockFile.ADVControl = mockADVFileControl() + mockFile.Control.ID = mockFile.ID + mockBatchADV := mockBatchADV() + mockFile.AddBatch(mockBatchADV) + if err := mockFile.Create(); err != nil { + panic(err) + } + if mockFile.ID != mockFile.Header.ID { + panic(fmt.Sprintf("mockFile.ID=%s mockFile.Header.ID=%s", mockFile.ID, mockFile.Header.ID)) + } + if mockFile.ID != mockFile.Control.ID { + panic(fmt.Sprintf("mockFile.ID=%s mockFile.Control.ID=%s", mockFile.ID, mockFile.Control.ID)) + } + return mockFile +} + +// testFileError validates a file error +func testFileError(t testing.TB) { err := &FileError{FieldName: "mock", Msg: "test message"} if err.Error() != "mock test message" { t.Error("FileError Error has changed formatting") } } -// TestFileBatchCount if calculated count is different from control -func TestFileBatchCount(t *testing.T) { +// TestFileError tests validating a file error +func TestFileError(t *testing.T) { + testFileError(t) +} + +// BenchmarkFileError benchmarks validating a file error +func BenchmarkFileError(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileError(b) + } +} + +// TestFileEmptyError tests an empty file error +func TestFileEmptyError(t *testing.T) { + file := &File{} + if err := file.Create(); err == nil { + t.Error("expected error") + } + err := file.Validate() + msg := err.Error() + if !strings.HasPrefix(msg, "ImmediateDestination") || !strings.Contains(msg, "is a mandatory field") { + t.Errorf("got %q", err) + } +} + +// testFileBatchCount validates if calculated count is different from control +func testFileBatchCount(t testing.TB) { file := mockFilePPD() // More batches than the file control count. file.AddBatch(mockBatchPPD()) - if err := file.Validate(); err != nil { - if e, ok := err.(*FileError); ok { - if e.FieldName != "BatchCount" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + err := file.Validate() + if err != NewErrFileCalculatedControlEquality("BatchCount", 2, 1) { + t.Errorf("%T: %s", err, err) } } -func TestFileEntryAddenda(t *testing.T) { +// TestFileBatchCount tests validating if calculated count is different from control +func TestFileBatchCount(t *testing.T) { + testFileBatchCount(t) +} + +// BenchmarkFileBatchCount benchmarks validating if calculated count is different from control +func BenchmarkFileBatchCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileBatchCount(b) + } +} + +// testFileEntryAddenda validates an addenda entry +func testFileEntryAddenda(t testing.TB) { file := mockFilePPD() // more entries than the file control file.Control.EntryAddendaCount = 5 - if err := file.Validate(); err != nil { - if e, ok := err.(*FileError); ok { - if e.FieldName != "EntryAddendaCount" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + err := file.Validate() + if err != NewErrFileCalculatedControlEquality("EntryAddendaCount", 1, 5) { + t.Errorf("%T: %s", err, err) } } -func TestFileDebitAmount(t *testing.T) { +// TestFileEntryAddenda tests validating an addenda entry +func TestFileEntryAddenda(t *testing.T) { + testFileEntryAddenda(t) +} + +// BenchmarkFileEntryAddenda benchmarks validating an addenda entry +func BenchmarkFileEntryAddenda(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileEntryAddenda(b) + } +} + +// testFileDebitAmount validates file total debit amount +func testFileDebitAmount(t testing.TB) { file := mockFilePPD() // inequality in total debit amount file.Control.TotalDebitEntryDollarAmountInFile = 63 - if err := file.Validate(); err != nil { - if e, ok := err.(*FileError); ok { - if e.FieldName != "TotalDebitEntryDollarAmountInFile" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + err := file.Validate() + if err != NewErrFileCalculatedControlEquality("TotalDebitEntryDollarAmountInFile", 0, 63) { + t.Errorf("%T: %s", err, err) } } -func TestFileCreditAmount(t *testing.T) { +// TestFileDebitAmount tests validating file total debit amount +func TestFileDebitAmount(t *testing.T) { + testFileDebitAmount(t) +} + +// BenchmarkFileDebitAmount benchmarks validating file total debit amount +func BenchmarkFileDebitAmount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileDebitAmount(b) + } +} + +// testFileCreditAmount validates file total credit amount +func testFileCreditAmount(t testing.TB) { file := mockFilePPD() - // inequality in total debit amount + // inequality in total credit amount file.Control.TotalCreditEntryDollarAmountInFile = 63 - if err := file.Validate(); err != nil { - if e, ok := err.(*FileError); ok { - if e.FieldName != "TotalCreditEntryDollarAmountInFile" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + err := file.Validate() + if err != NewErrFileCalculatedControlEquality("TotalCreditEntryDollarAmountInFile", 100000000, 63) { + t.Errorf("%T: %s", err, err) } } -func TestFileEntryHash(t *testing.T) { +// TestFileCreditAmount tests validating file total credit amount +func TestFileCreditAmount(t *testing.T) { + testFileCreditAmount(t) +} + +// BenchmarkFileCreditAmount benchmarks validating file total credit amount +func BenchmarkFileCreditAmount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileCreditAmount(b) + } +} + +// testFileEntryHash validates entry hash +func testFileEntryHash(t testing.TB) { file := mockFilePPD() file.AddBatch(mockBatchPPD()) - file.Create() + if err := file.Create(); err != nil { + t.Fatal(err) + } file.Control.EntryHash = 63 - if err := file.Validate(); err != nil { - if e, ok := err.(*FileError); ok { - if e.FieldName != "EntryHash" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + err := file.Validate() + if err != NewErrFileCalculatedControlEquality("EntryHash", 46276020, 63) { + t.Errorf("%T: %s", err, err) } } -func TestFileBlockCount10(t *testing.T) { +// TestFileEntryHash tests validating entry hash +func TestFileEntryHash(t *testing.T) { + testFileEntryHash(t) +} + +// BenchmarkFileEntryHash benchmarks validating entry hash +func BenchmarkFileEntryHash(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileEntryHash(b) + } +} + +func TestFileEntryHashOverflow(t *testing.T) { + t.Run("Create should force a 10 digit entry hash", func(t *testing.T) { + invalidEntryHash := 12345678912 // 11 digits + validEntryHash := 2345678912 // 10 digits + + file := mockFilePPD() + file.Batches[0].GetControl().EntryHash = invalidEntryHash + if err := file.Create(); err != nil { + t.Fatal(err) + } + + if file.Control.EntryHash != validEntryHash { + t.Fatalf("file control's entry hash: want %d, got %d", validEntryHash, invalidEntryHash) + } + }) + + t.Run("Validate should not error on entry hash overflow", func(t *testing.T) { + // Read a file where the calculated entry hash will overflow to 11 digits + file, err := readACHFilepath(filepath.Join("cmd", "readACH", "201805101354.ach")) + if err != nil { + t.Fatal(err) + } + + if err := file.Validate(); err != nil { + t.Fatal(err) + } + }) +} + +// testFileBlockCount10 validates file block count +func testFileBlockCount10(t testing.TB) { file := NewFile().SetHeader(mockFileHeader()) - batch := NewBatchPPD() - batch.SetHeader(mockBatchHeader()) - batch.AddEntry(mockEntryDetail()) - batch.AddEntry(mockEntryDetail()) - batch.AddEntry(mockEntryDetail()) - batch.AddEntry(mockEntryDetail()) - batch.AddEntry(mockEntryDetail()) - batch.AddEntry(mockEntryDetail()) - batch.Create() + batch := NewBatchPPD(mockBatchPPDHeader()) + + ed1 := mockEntryDetail() + ed1.SetTraceNumber(mockBatchHeader().ODFIIdentification, 1) + batch.AddEntry(ed1) + ed2 := mockEntryDetail() + ed2.SetTraceNumber(mockBatchHeader().ODFIIdentification, 2) + batch.AddEntry(ed2) + ed3 := mockEntryDetail() + ed3.SetTraceNumber(mockBatchHeader().ODFIIdentification, 3) + batch.AddEntry(ed3) + ed4 := mockEntryDetail() + ed4.SetTraceNumber(mockBatchHeader().ODFIIdentification, 4) + batch.AddEntry(ed4) + ed5 := mockEntryDetail() + ed5.SetTraceNumber(mockBatchHeader().ODFIIdentification, 5) + batch.AddEntry(ed5) + ed6 := mockEntryDetail() + ed6.SetTraceNumber(mockBatchHeader().ODFIIdentification, 6) + batch.AddEntry(ed6) + + if err := batch.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } file.AddBatch(batch) if err := file.Create(); err != nil { t.Errorf("%T: %s", err, err) @@ -128,8 +304,13 @@ func TestFileBlockCount10(t *testing.T) { t.Error("BlockCount on 10 records is not equal to 1") } // make 11th record which should produce BlockCount of 2 - file.Batches[0].AddEntry(mockEntryDetail()) - file.Batches[0].Create() // File.Build does not re-build Batches + ed7 := mockEntryDetail() + ed7.SetTraceNumber(mockBatchHeader().ODFIIdentification, 7) + file.Batches[0].AddEntry(ed7) + + if err := file.Batches[0].Create(); err != nil { + t.Fatal(err) + } // File.Build does not re-build Batches if err := file.Create(); err != nil { t.Errorf("%T: %s", err, err) } @@ -138,28 +319,1790 @@ func TestFileBlockCount10(t *testing.T) { } } -func TestFileBuildBadFileHeader(t *testing.T) { +// TestFileBlockCount10 tests validating file block count +func TestFileBlockCount10(t *testing.T) { + testFileBlockCount10(t) +} + +// BenchmarkFileBlockCount10 benchmarks validating file block count +func BenchmarkFileBlockCount10(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileBlockCount10(b) + } +} + +// testFileBuildBadFileHeader validates a bad file header +func testFileBuildBadFileHeader(t testing.TB) { file := NewFile().SetHeader(FileHeader{}) - if err := file.Create(); err != nil { - if e, ok := err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + err := file.Create() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileBuildBadFileHeader tests validating a bad file header +func TestFileBuildBadFileHeader(t *testing.T) { + testFileBuildBadFileHeader(t) +} + +// BenchmarkFileBuildBadFileHeader benchmarks validating a bad file header +func BenchmarkFileBuildBadFileHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileBuildBadFileHeader(b) } } +// testFileBuildNoBatch validates a file with no batches +func testFileBuildNoBatch(t testing.TB) { + file := NewFile().SetHeader(mockFileHeader()) + err := file.Create() + if !base.Match(err, ErrFileNoBatches) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileBuildNoBatch tests validating a file with no batches func TestFileBuildNoBatch(t *testing.T) { + testFileBuildNoBatch(t) +} + +// BenchmarkFileBuildNoBatch benchmarks validating a file with no batches +func BenchmarkFileBuildNoBatch(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileBuildNoBatch(b) + } +} + +// testFileNotificationOfChange validates if a file contains +// BatchNOC notification of change +func testFileNotificationOfChange(t testing.TB) { file := NewFile().SetHeader(mockFileHeader()) + mockBatch := NewBatchPPD(mockBatchPPDHeader()) + mockBatch.AddEntry(mockPPDEntryDetailNOC()) + file.AddBatch(mockBatch) if err := file.Create(); err != nil { - if e, ok := err.(*FileError); ok { - if e.FieldName != "Batchs" { - t.Errorf("%T: %s", err, err) - } - } else { - t.Errorf("%T: %s", err, err) - } + t.Fatal(err) + } + if file.NotificationOfChange[0] != mockBatch { + t.Error("BatchCOR added to File.AddBatch should exist in NotificationOfChange") + } +} + +// TestFileNotificationOfChange tests validating if a file contains +// BatchNOC notification of change +func TestFileNotificationOfChange(t *testing.T) { + testFileNotificationOfChange(t) +} + +// BenchmarkFileNotificationOfChange benchmarks validating if a file contains +// BatchNOC notification of change +func BenchmarkFileNotificationOfChange(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileNotificationOfChange(b) + } +} + +// testFileReturnEntries validates file return entries +func testFileReturnEntries(t testing.TB) { + // create or copy the entry to be returned record + entry := mockEntryDetail() + entry.AddendaRecordIndicator = 1 + // Add the addenda return with appropriate ReturnCode and addenda information + entry.Addenda99 = mockAddenda99() + entry.Category = CategoryReturn + // create or copy the previous batch header of the item being returned + batchHeader := mockBatchHeader() + // create or copy the batch to be returned + + //batch, err := NewBatch(BatchParam{StandardEntryClass: batchHeader.StandardEntryClassCode}) + batch, err := NewBatch(batchHeader) + + if err != nil { + t.Error(err.Error()) + } + // Add the entry to be returned to the batch + batch.AddEntry(entry) + // Create the batch + if err := batch.Create(); err != nil { + t.Fatal(err) + } + // Add the batch to your file. + file := NewFile().SetHeader(mockFileHeader()) + file.AddBatch(batch) + // Create the return file + if err := file.Create(); err != nil { + t.Error(err.Error()) + } +} + +// TestFileReturnEntries tests validating file return entries +func TestFileReturnEntries(t *testing.T) { + testFileReturnEntries(t) +} + +// BenchmarkFileReturnEntries benchmarks validating file return entries +func BenchmarkFileReturnEntries(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileReturnEntries(b) + } +} + +func TestFile__ValidateOptsJSON(t *testing.T) { + file, err := ReadFile(filepath.Join("test", "testdata", "ppd-debit.ach")) + require.NoError(t, err) + + file.SetValidation(&ValidateOpts{ + BypassOriginValidation: true, + }) + + bs, err := file.MarshalJSON() + require.NoError(t, err) + + // zero out the file and verify we can read it all back + file = NewFile() + + err = json.Unmarshal(bs, file) + require.NoError(t, err) + + opts := file.GetValidation() + require.True(t, opts.BypassOriginValidation) + require.False(t, opts.BypassDestinationValidation) +} + +func TestFile__readFromJson(t *testing.T) { + path := filepath.Join("test", "testdata", "ppd-valid.json") + bs, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + + file, err := FileFromJSON(bs) + if err != nil { + t.Fatal(err) + } + require.Nil(t, file.GetValidation()) + + // Ensure the file is valid + if err := file.Create(); err != nil { + t.Error(err) + } + if err := file.Validate(); err != nil { + t.Error(err) + } + + require.Equal(t, "1f707c97-da19-49d0-a3c9-49eebc042e68", file.ID) + + // Header + if file.Header.ImmediateOrigin != "121042882" || file.Header.ImmediateOriginName != "Wells Fargo" { + t.Errorf("origin=%s name=%s", file.Header.ImmediateOrigin, file.Header.ImmediateOriginName) + } + if file.Header.ImmediateDestination != "231380104" || file.Header.ImmediateDestinationName != "Citadel" { + t.Errorf("destination=%s name=%s", file.Header.ImmediateDestination, file.Header.ImmediateDestinationName) + } + if file.Header.FileCreationTime != "" || file.Header.FileCreationDate != "181008" { + t.Errorf("time=%v date=%v", file.Header.FileCreationTime, file.Header.FileCreationDate) + } + if v := file.Header.FileCreationTimeField(); v == "" || len(v) != 4 { + t.Errorf("time=%v", v) + } + if v := file.Header.FileCreationDateField(); v != "181008" { + t.Errorf("date=%v", v) + } + + // Batches + if len(file.Batches) != 1 { + t.Errorf("got %d batches: %v", len(file.Batches), file.Batches) + } + batch := file.Batches[0] + batchControl := batch.GetControl() + if batchControl.EntryAddendaCount != 1 { + t.Errorf("EntryAddendaCount: %d", batchControl.EntryAddendaCount) + } + + // Control + if file.Control.BatchCount != 1 { + t.Errorf("BatchCount: %d", file.Control.BatchCount) + } + if file.Control.EntryAddendaCount != 1 { + t.Errorf("File Control EntryAddendaCount: %d", file.Control.EntryAddendaCount) + } + if file.Control.TotalDebitEntryDollarAmountInFile != 0 || file.Control.TotalCreditEntryDollarAmountInFile != 100000 { + t.Errorf("debit=%d credit=%d", file.Control.TotalDebitEntryDollarAmountInFile, file.Control.TotalCreditEntryDollarAmountInFile) + } + + // ensure we error on struct tag unmarshal + var f File + if err := json.Unmarshal(bs, &f); err != nil { + t.Error(err) + } + if f.Header.ImmediateOrigin != "121042882" || f.Header.ImmediateDestination != "231380104" { + t.Errorf("FileHeader=%#v", f.Header) + } + if err := f.Validate(); err != nil { + t.Error(err) + } + + if err := file.Validate(); err != nil { + t.Error(err) + } +} + +// TestFile__jsonFileNoControlBlobs will read an ach.File from its JSON form, but the JSON has no +// batchControl or fileControl sub-objects. +func TestFile__jsonFileNoControlBlobs(t *testing.T) { + path := filepath.Join("test", "testdata", "ppd-no-control-blobs-valid.json") + bs, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + + file, err := FileFromJSON(bs) + if err != nil { + t.Fatal(err) + } + + if err := file.Create(); err != nil { + t.Fatal(err) + } + if err := file.Validate(); err != nil { + t.Fatal(err) + } + + if file.ID != "adam-01" { + t.Errorf("file.ID: %s", file.ID) + } +} + +func TestFile__rfc3339JSON(t *testing.T) { + bs, err := os.ReadFile(filepath.Join("test", "testdata", "rfc3339.json")) + if err != nil { + t.Fatal(err) + } + file, err := FileFromJSON(bs) + if err != nil { + t.Fatal(err) + } + + if file.ID != "rfc3339" { + t.Fatalf("file.ID=%s", file.ID) + } + + if file.Header.FileCreationDate != "091110" { + t.Errorf("file.Header.FileCreationDate=%s", file.Header.FileCreationDate) + } + if file.Header.FileCreationTime != "2300" { + t.Errorf("file.Header.FileCreationTime=%s", file.Header.FileCreationTime) + } + + if len(file.Batches) != 1 { + t.Fatalf("got %d Batches", len(file.Batches)) + } + + header := file.Batches[0].GetHeader() + if header.CompanyDescriptiveDate != "SD2300" { + t.Errorf("header.CompanyDescriptiveDate=%s", header.CompanyDescriptiveDate) + } + if header.EffectiveEntryDate != "091110" { + t.Errorf("header.EffectiveEntryDate=%s", header.EffectiveEntryDate) + } +} + +func TestFile__iso8601JSON(t *testing.T) { + bs, err := os.ReadFile(filepath.Join("test", "testdata", "iso8601.json")) + if err != nil { + t.Fatal(err) + } + file, err := FileFromJSON(bs) + if err != nil { + t.Fatal(err) + } + + if file.ID != "iso8601" { + t.Fatalf("file.ID=%s", file.ID) + } + + if file.Header.FileCreationDate != "190920" { + t.Errorf("file.Header.FileCreationDate=%s", file.Header.FileCreationDate) + } + if file.Header.FileCreationTime != "2114" { + t.Errorf("file.Header.FileCreationTime=%s", file.Header.FileCreationTime) + } + + if len(file.Batches) != 1 { + t.Fatalf("got %d Batches", len(file.Batches)) + } + + header := file.Batches[0].GetHeader() + if header.CompanyDescriptiveDate != "SD2114" { + t.Errorf("header.CompanyDescriptiveDate=%s", header.CompanyDescriptiveDate) + } + if header.EffectiveEntryDate != "190920" { + t.Errorf("header.EffectiveEntryDate=%s", header.EffectiveEntryDate) + } +} + +func TestFile__IATdatetimeParse(t *testing.T) { + bs, err := os.ReadFile(filepath.Join("test", "testdata", "iat-debit.json")) + if err != nil { + t.Fatal(err) + } + file, err := FileFromJSON(bs) + if err != nil { + t.Fatal(err) + } + + if file.ID != "iat-datetime" { + t.Fatalf("file.ID=%s", file.ID) + } + if len(file.IATBatches) != 1 { + t.Errorf("got %d IAT batches", len(file.IATBatches)) + } + if date := file.IATBatches[0].Header.EffectiveEntryDate; date != "190923" { + t.Errorf("file.IATBatches[0].Header.EffectiveEntryDate=%s", date) + } +} + +func TestFile__JsonBypassOrigin(t *testing.T) { + bs, err := os.ReadFile(filepath.Join("test", "testdata", "json-bypass-origin.json")) + require.NoError(t, err) + + file, err := FileFromJSONWith(bs, &ValidateOpts{ + BypassOriginValidation: true, + }) + require.NoError(t, err) + require.NoError(t, file.Validate()) + + require.Equal(t, "adam-01", file.ID) + require.Equal(t, "000000000", file.Header.ImmediateOrigin) + require.Equal(t, "231380104", file.Header.ImmediateDestination) + require.Equal(t, "181008", file.Header.FileCreationDate) +} + +func TestFile__JsonBypassDestinationAndOrigin(t *testing.T) { + bs, err := os.ReadFile(filepath.Join("test", "testdata", "json-bypass-origin-and-destination.json")) + require.NoError(t, err) + + file, err := FileFromJSONWith(bs, &ValidateOpts{ + BypassOriginValidation: true, + BypassDestinationValidation: true, + }) + require.NoError(t, err) + require.NoError(t, file.Validate()) + + require.Equal(t, "adam-01", file.ID) + require.Equal(t, "000000000", file.Header.ImmediateOrigin) + require.Equal(t, "000000000", file.Header.ImmediateDestination) + require.Equal(t, "181008", file.Header.FileCreationDate) +} + +func TestFile__datetimeParse(t *testing.T) { + // from javascript: (new Date).toISOString() + if ts, err := datetimeParse("2019-09-20T20:49:35.177Z"); err != nil { + t.Error(err) + } else { + if v := ts.Format("060102"); v != "190920" { + t.Errorf("got %s", v) + } + } + + // RFC3339 format + if ts, err := datetimeParse("2019-09-23T09:50:52-07:00"); err != nil { + t.Error(err) + } else { + if v := ts.Format("060102"); v != "190923" { + t.Errorf("got %s", v) + } + } + + // other, expect zero time + if ts, err := datetimeParse(""); !ts.IsZero() || err == nil { + t.Errorf("ts=%v error=%v", ts, err) + } +} + +func TestFileADV__Success(t *testing.T) { + fh := mockFileHeader() + bh := mockBatchADVHeader() + + entryOne := mockADVEntryDetail() + entryTwo := mockADVEntryDetail() + entryTwo.SequenceNumber = 2 + + // build the batch + batch := NewBatchADV(bh) + batch.AddADVEntry(entryOne) + batch.AddADVEntry(entryTwo) + if err := batch.Create(); err != nil { + t.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + t.Fatalf("Unexpected error building file: %s\n", err) + } +} + +func TestFileADVInvalid__StandardEntryClassCode(t *testing.T) { + fh := mockFileHeader() + + // ADV + bhADV := mockBatchADVHeader() + entryADV := mockADVEntryDetail() + + // build the ADV batch + batchADV := NewBatchADV(bhADV) + batchADV.AddADVEntry(entryADV) + + if err := batchADV.Create(); err != nil { + t.Fatalf("Unexpected error building batch: %s\n", err) + } + + // PPD + bhPPD := mockBatchPPDHeader() + entryPPD := mockPPDEntryDetail() + + // build the PPD batch + batchPPD, err := NewBatch(bhPPD) + if err != nil { + t.Fatalf("Unexpected error with NewBatch: %s\n", err) + } + batchPPD.AddEntry(entryPPD) + + if err := batchPPD.Create(); err != nil { + t.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := NewFile() + file.SetHeader(fh) + file.AddBatch(batchADV) + file.AddBatch(batchPPD) + err = file.Create() + if err != ErrFileADVOnly { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileADVEntryHash validates entry hash +func TestFileADVEntryHash(t *testing.T) { + file := mockFileADV() + file.AddBatch(mockBatchADV()) + if err := file.Create(); err != nil { + t.Fatal(err) + } + file.ADVControl.EntryHash = 63 + err := file.Validate() + if err != NewErrFileCalculatedControlEquality("EntryHash", 46276020, 63) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileADVDebitAmount validates file total debit amount +func TestFileADVDebitAmount(t *testing.T) { + file := mockFileADV() + + // inequality in total debit amount + file.ADVControl.TotalDebitEntryDollarAmountInFile = 06 + err := file.Validate() + if err != NewErrFileCalculatedControlEquality("TotalDebitEntryDollarAmountInFile", 0, 6) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileADVCreditAmount validates file total credit amount +func TestFileADVCreditAmount(t *testing.T) { + file := mockFileADV() + + // inequality in total credit amount + file.ADVControl.TotalCreditEntryDollarAmountInFile = 07 + err := file.Validate() + if err != NewErrFileCalculatedControlEquality("TotalCreditEntryDollarAmountInFile", 50000, 7) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileADVEntryAddenda validates an addenda entry +func TestFileADVEntryAddenda(t *testing.T) { + file := mockFileADV() + + // more entries than the file control + file.ADVControl.EntryAddendaCount = 5 + err := file.Validate() + if err != NewErrFileCalculatedControlEquality("EntryAddendaCount", 1, 5) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileADVBatchCount validates if calculated count is different from control +func TestFileADVBatchCount(t *testing.T) { + file := mockFileADV() + + // More batches than the file control count. + file.AddBatch(mockBatchADV()) + err := file.Validate() + if err != NewErrFileCalculatedControlEquality("BatchCount", 2, 1) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileADVBlockCount10 validates file block count +func TestFileADVBlockCount10(t *testing.T) { + file := NewFile().SetHeader(mockFileHeader()) + batchADV := NewBatchADV(mockBatchADVHeader()) + + ed1 := mockADVEntryDetail() + batchADV.AddADVEntry(ed1) + + ed2 := mockADVEntryDetail() + ed2.SequenceNumber = 2 + batchADV.AddADVEntry(ed2) + + ed3 := mockADVEntryDetail() + ed3.SequenceNumber = 3 + batchADV.AddADVEntry(ed3) + + ed4 := mockADVEntryDetail() + ed4.SequenceNumber = 4 + batchADV.AddADVEntry(ed4) + + ed5 := mockADVEntryDetail() + ed5.SequenceNumber = 5 + batchADV.AddADVEntry(ed5) + + ed6 := mockADVEntryDetail() + ed6.SequenceNumber = 6 + batchADV.AddADVEntry(ed6) + + if err := batchADV.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + file.AddBatch(batchADV) + if err := file.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + + // ensure with 10 records in file we don't get 2 for a block count + if file.ADVControl.BlockCount != 1 { + t.Error("BlockCount on 10 records is not equal to 1") + } + // make 11th record which should produce BlockCount of 2 + ed7 := mockADVEntryDetail() + ed7.SequenceNumber = 7 + file.Batches[0].AddADVEntry(ed7) + + if err := file.Batches[0].Create(); err != nil { + t.Fatal(err) + } // File.Build does not re-build Batches + if err := file.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + if file.ADVControl.BlockCount != 2 { + t.Error("BlockCount on 11 records is not equal to 2") + } +} + +// TestFileADVControlValidate validates ADV File Control +func TestFileADVControlValidate(t *testing.T) { + file := mockFileADV() + + file.ADVControl.TotalDebitEntryDollarAmountInFile = -100 + err := file.Validate() + if !base.Match(err, NewErrFileCalculatedControlEquality("TotalDebitEntryDollarAmountInFile", 0, 0)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileControlValidate validates PPD File Control +func TestFileControlValidate(t *testing.T) { + file := mockFilePPD() + + file.Control.TotalDebitEntryDollarAmountInFile = 22 + err := file.Validate() + if !base.Match(err, NewErrFileCalculatedControlEquality("TotalDebitEntryDollarAmountInFile", 0, 22)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchHeaderNil Batch Header Nil +func TestBatchHeaderNil(t *testing.T) { + fh := mockFileHeader() + bh := mockBatchPPDHeader() + + entryOne := mockPPDEntryDetail() + + // build the batch + batch := NewBatchPPD(bh) + batch.AddEntry(entryOne) + + if err := batch.Create(); err != nil { + t.Fatalf("Unexpected error building batch: %s\n", err) + } + + batch.Header = nil + + // build the file + file := NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + t.Fatalf("Unexpected error building file: %s\n", err) + } + +} + +// TestBatchControlNil Batch Control Nil +func TestBatchControlNil(t *testing.T) { + fh := mockFileHeader() + bh := mockBatchPPDHeader() + + entryOne := mockPPDEntryDetail() + + // build the batch + batch := NewBatchPPD(bh) + batch.AddEntry(entryOne) + + if err := batch.Create(); err != nil { + t.Fatalf("Unexpected error building batch: %s\n", err) + } + + batch.Control = nil + + // build the file + file := NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + t.Fatalf("Unexpected error building file: %s\n", err) + } + +} + +func TestFileADV__readFromJson(t *testing.T) { + path := filepath.Join("test", "testdata", "adv-valid.json") + bs, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + + file, err := FileFromJSON(bs) + if err != nil { + t.Fatal(err) + } + + // Ensure the file is valid + if err := file.Create(); err != nil { + t.Error(err) + } + if err := file.Validate(); err != nil { + t.Error(err) + } + + if file.ID != "adv-01" { + t.Errorf("file.ID: %s", file.ID) + } + + // Header + if file.Header.ImmediateOrigin != "121042882" || file.Header.ImmediateOriginName != "Wells Fargo" { + t.Errorf("origin=%s name=%s", file.Header.ImmediateOrigin, file.Header.ImmediateOriginName) + } + if file.Header.ImmediateDestination != "231380104" || file.Header.ImmediateDestinationName != "Citadel" { + t.Errorf("destination=%s name=%s", file.Header.ImmediateDestination, file.Header.ImmediateDestinationName) + } + if file.Header.FileCreationTime == "" || file.Header.FileCreationDate == "" { + t.Errorf("time=%v date=%v", file.Header.FileCreationTime, file.Header.FileCreationDate) + } + + // Batches + if len(file.Batches) != 1 { + t.Errorf("got %d batches: %v", len(file.Batches), file.Batches) + } + batch := file.Batches[0] + batchADVControl := batch.GetADVControl() + if batchADVControl.EntryAddendaCount != 1 { + t.Errorf("EntryAddendaCount: %d", batchADVControl.EntryAddendaCount) + } + + // Control + if file.ADVControl.BatchCount != 1 { + t.Errorf("BatchCount: %d", file.ADVControl.BatchCount) + } + if file.ADVControl.EntryAddendaCount != 1 { + t.Errorf("File Control EntryAddendaCount: %d", file.ADVControl.EntryAddendaCount) + } + if file.ADVControl.TotalDebitEntryDollarAmountInFile != 0 || file.ADVControl.TotalCreditEntryDollarAmountInFile != 100000 { + t.Errorf("debit=%d credit=%d", file.ADVControl.TotalDebitEntryDollarAmountInFile, file.ADVControl.TotalCreditEntryDollarAmountInFile) + } + + // ensure we error on struct tag unmarshal + var f File + if err := json.Unmarshal(bs, &f); err != nil { + t.Error(err) + } + if f.Header.ImmediateOrigin != "121042882" || f.Header.ImmediateDestination != "231380104" { + t.Errorf("FileHeader=%#v", f.Header) + } + if err := f.Validate(); err != nil { + t.Error(err) + } + + if err := file.Validate(); err != nil { + t.Error(err) + } +} + +func TestFile__readInvalidJson(t *testing.T) { + path := filepath.Join("test", "testdata", "ppd-invalid.json") + bs, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + if _, err := FileFromJSON(bs); err == nil { + t.Error("expected error") + } else { + if !strings.Contains(err.Error(), "problem reading File") { + t.Fatal(err) + } + } + + // a file which fails .Validate() + path = filepath.Join("test", "testdata", "ppd-invalid-EntryDetail-checkDigit.json") + bs, err = os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + if _, err := FileFromJSON(bs); err == nil { + t.Error("expected error") + } else { + if !strings.Contains(err.Error(), "batch #1 (PPD) FieldError RDFIIdentification 1 does not match calculated check digit 4") { + t.Error(err) + } + } +} + +func TestFile__readEmptyJson(t *testing.T) { + bs := make([]byte, 0) + _, err := FileFromJSON(bs) + + if err != nil { + if strings.Contains(err.Error(), "no JSON data provided") { + } else { + t.Fatal(err) + } + } +} + +func TestFile__readNoBatchesJson(t *testing.T) { + path := filepath.Join("test", "testdata", "ppd-noBatches.json") + bs, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + + _, err = FileFromJSON(bs) + + if !base.Match(err, ErrConstructor) { + t.Fatal(err) + } +} + +func TestFile__readInvalidFilesJson(t *testing.T) { + path := filepath.Join("test", "testdata", "ppd-invalidFile.json") + bs, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + + _, err = FileFromJSON(bs) + + if !base.Match(err, ErrUpperAlpha) { + t.Errorf("%T: %s", err, err) + } +} + +func TestFile_largeFileEntryHash(t *testing.T) { + // To create a file + fh := NewFileHeader() + fh.ImmediateDestination = "231380104" + fh.ImmediateOrigin = "121042882" + fh.FileCreationDate = time.Now().Format("060102") + fh.ImmediateDestinationName = "Citadel" + fh.ImmediateOriginName = "Wells Fargo" + file := NewFile() + file.SetHeader(fh) + + // Create 2 Batches of SEC Code PPD + + bh := NewBatchHeader() + bh.ServiceClassCode = MixedDebitsAndCredits + bh.CompanyName = "Wells Fargo" + bh.CompanyIdentification = "121042882" + bh.StandardEntryClassCode = PPD + bh.CompanyEntryDescription = "Trans. Description" + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") + bh.ODFIIdentification = "121042882" + + batch, _ := NewBatch(bh) + + // Create Entry + entrySeq := 0 + for i := 0; i < 1250; i++ { + entrySeq = entrySeq + 1 + + entryEntrySeq := NewEntryDetail() + entryEntrySeq.TransactionCode = CheckingCredit + entryEntrySeq.SetRDFI("231380104") + entryEntrySeq.DFIAccountNumber = "81967038518" + entryEntrySeq.Amount = 100000 + entryEntrySeq.IndividualName = "Steven Tander" + entryEntrySeq.SetTraceNumber(bh.ODFIIdentification, entrySeq) + entryEntrySeq.IdentificationNumber = "#83738AB#" + entryEntrySeq.Category = CategoryForward + entryEntrySeq.AddendaRecordIndicator = 1 + + // Add addenda record for an entry + addendaEntrySeq := NewAddenda05() + addendaEntrySeq.PaymentRelatedInformation = "bonus pay for amazing work on #OSS" + entryEntrySeq.AddAddenda05(addendaEntrySeq) + + // Add entries + batch.AddEntry(entryEntrySeq) + + } + + // Create the batch. + if err := batch.Create(); err != nil { + fmt.Printf("%T: %s", err, err) + } + + // Add batch to the file + file.AddBatch(batch) + + // Create the file + if err := file.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + // ensure we have a validated file structure + if err := file.Validate(); err != nil { + t.Errorf("Could not validate entire file: %v", err) + } + + // Per BatchControl.Parse() and Batch.calculateEntryHash() + // EntryHash is essentially the sum of all the RDFI routing numbers in the batch. If the sum exceeds 10 digits + // (because you have lots of Entry Detail Records), lop off the most significant digits of the sum until there + // are only 10. This allows the entryHash to be 10 digits as per the ACH specification. + + testHash := 0 + for _, batch := range file.Batches { + + for _, entry := range batch.GetEntries() { + + entryRDFI, _ := strconv.Atoi(entry.RDFIIdentification) + + testHash = testHash + entryRDFI + } + + if testHash != 28922512500 { + t.Errorf("Expected '28922512500' Calculated Entry Hash: %d", testHash) + } + + s := strconv.Itoa(testHash) + ln := uint(len(s)) + if ln > 10 { + s = s[ln-10:] + } + + testHash, _ = strconv.Atoi(s) + + if testHash != 8922512500 { + t.Errorf("Expected '8922512500' Calculated Entry Hash: %d", testHash) + } + } +} + +func TestFile__RemoveBatch(t *testing.T) { + file, err := readACHFilepath(filepath.Join("test", "testdata", "ppd-debit.ach")) + if err != nil { + t.Fatal(err) + } + if len(file.Batches) != 1 { + t.Errorf("unexpected number of batches: %d", len(file.Batches)) + } + + // remove the batch and check + file.RemoveBatch(file.Batches[0]) + if len(file.Batches) != 0 { + t.Errorf("unexpected number of batches: %d", len(file.Batches)) + } + + // NOC Entries + nocHeader := NewBatchHeader() + nocHeader.ServiceClassCode = CreditsOnly + nocHeader.StandardEntryClassCode = COR + nocHeader.CompanyName = "Your Company, inc" + nocHeader.CompanyIdentification = "121042882" + nocHeader.CompanyEntryDescription = "Vendor Pay" + nocHeader.ODFIIdentification = "121042882" + noc := NewBatchCOR(nocHeader) + nocED := mockCOREntryDetail() + nocED.Addenda98 = mockAddenda98() + nocED.Category = CategoryNOC + nocED.AddendaRecordIndicator = 1 + noc.AddEntry(nocED) + if err := noc.Create(); err != nil { + t.Fatal(err) + } + file.AddBatch(noc) + if len(file.NotificationOfChange) != 1 { + t.Errorf("unexpected number of NOC batches: %d", len(file.NotificationOfChange)) + } + file.RemoveBatch(noc) + if len(file.NotificationOfChange) != 0 { + t.Errorf("unexpected number of NOC batches: %d", len(file.NotificationOfChange)) + } + + // Returns + file, err = readACHFilepath(filepath.Join("test", "testdata", "ppd-debit.ach")) + if err != nil { + t.Fatal(err) + } + if len(file.ReturnEntries) != 0 { + t.Errorf("unexpected number of return entries: %d", len(file.ReturnEntries)) + } + ppdHeader := mockBatchPPDHeader() + ppd := NewBatchPPD(ppdHeader) + ppdED := mockPPDEntryDetail() + ppdED.Addenda99 = mockAddenda99() + ppdED.Category = CategoryReturn + ppd.AddEntry(ppdED) + + file.AddBatch(ppd) + if len(file.ReturnEntries) != 1 { + t.Errorf("unexpected number of return entries: %d", len(file.ReturnEntries)) + } + file.RemoveBatch(ppd) + if len(file.ReturnEntries) != 0 { + t.Errorf("unexpected number of return entries: %d", len(file.ReturnEntries)) + } +} + +func TestFile__SegmentFile(t *testing.T) { + // open a file for reading. Any io.Reader Can be used + f, err := os.Open(filepath.Join("test", "testdata", "ppd-mixedDebitCredit.ach")) + + if err != nil { + t.Fatal(err) + } + r := NewReader(f) + achFile, err := r.Read() + if err != nil { + t.Fatalf("Issue reading file: %+v \n", err) + } + + // ensure we have a validated file structure + if achFile.Validate(); err != nil { + t.Fatalf("Could not validate entire read file: %v", err) + } + + sfc := NewSegmentFileConfiguration() + creditFile, debitFile, err := achFile.SegmentFile(sfc) + + if err != nil { + t.Fatalf("Could not segment the file: %+v \n", err) + } + + if err := creditFile.Validate(); err != nil { + t.Fatalf("Credit file did not validate: %+v \n", err) + } + + if err := debitFile.Validate(); err != nil { + t.Fatalf("Debit File did not validate: %+v \n", err) + } +} + +func TestFile__SegmentFileCredit(t *testing.T) { + f, err := os.Open(filepath.Join("test", "ach-ppd-read", "ppd-credit.ach")) + if err != nil { + t.Fatal(err) + } + r := NewReader(f) + achFile, err := r.Read() + if err != nil { + t.Fatalf("Issue reading file: %+v \n", err) + } + + // ensure we have a validated file structure + if achFile.Validate(); err != nil { + t.Fatalf("Could not validate entire read file: %v", err) + } + + sfc := NewSegmentFileConfiguration() + _, _, err = achFile.SegmentFile(sfc) + + if err != nil { + if !base.Match(err, ErrFileNoBatches) { + t.Errorf("%T: %s", err, err) + } + } +} + +func TestFile__SegmentFileDebitOnly(t *testing.T) { + f, err := os.Open(filepath.Join("test", "ach-ppd-read", "ppd-debit.ach")) + if err != nil { + t.Fatal(err) + } + r := NewReader(f) + achFile, err := r.Read() + if err != nil { + t.Fatalf("Issue reading file: %+v \n", err) + } + + // ensure we have a validated file structure + if achFile.Validate(); err != nil { + t.Fatalf("Could not validate entire read file: %v", err) + } + + sfc := NewSegmentFileConfiguration() + _, _, err = achFile.SegmentFile(sfc) + + if err != nil { + if !base.Match(err, ErrFileNoBatches) { + t.Errorf("%T: %s", err, err) + } + } + +} + +func TestFile__SegmentADVFile(t *testing.T) { + bs, err := os.ReadFile(filepath.Join("test", "testdata", "adv-valid.json")) + if err != nil { + t.Fatal(err) + } + f, err := FileFromJSON(bs) + if err != nil { + t.Fatal(err) + } + + creditFile, debitFile, err := f.SegmentFile(nil) + if err != nil { + t.Fatal(err) + } + if len(debitFile.Batches) != 0 { + t.Fatalf("debitFile.Batches=%#v", debitFile.Batches) + } + if !creditFile.IsADV() { + t.Errorf("creditFile.IsADV=%v", creditFile.IsADV()) + } + if len(creditFile.Batches) != 1 { + t.Fatalf("credit: batches=%d", len(creditFile.Batches)) + } + + creditADVEntries := creditFile.Batches[0].GetADVEntries() + if len(creditADVEntries) != 1 { + t.Errorf("creditADVEntries=%d", len(creditADVEntries)) + } + + entry := creditADVEntries[0] + if entry.ID != "adv-01" { + t.Errorf("ADV entry: %#v", entry) + } + if entry.TransactionCode != CreditForDebitsOriginated { + t.Errorf("ADV entry: %#v", entry) + } + if entry.AdviceRoutingNumber != "121042882" { + t.Errorf("ADV entry: %#v", entry) + } + if entry.FileIdentification != "11131" { + t.Errorf("ADV entry: %#v", entry) + } + if entry.ACHOperatorRoutingNumber != "01100001" { + t.Errorf("ADV entry: %#v", entry) + } +} + +func TestFile__SegmentADVFileDebit(t *testing.T) { + bs, err := os.ReadFile(filepath.Join("test", "testdata", "adv-valid.json")) + if err != nil { + t.Fatal(err) + } + f, err := FileFromJSON(bs) + if err != nil { + t.Fatal(err) + } + + // Force Batch Header and Control to ADV types + bh := f.Batches[0].GetHeader() + bh.ServiceClassCode = AutomatedAccountingAdvices + f.Batches[0].SetHeader(bh) + + bc := f.Batches[0].GetControl() + bc.ServiceClassCode = AutomatedAccountingAdvices + f.Batches[0].SetControl(bc) + + // Force the ADVEntryDetail to Debit + f.Batches[0].GetADVEntries()[0].TransactionCode = DebitForDebitsReceived + + creditFile, debitFile, err := f.SegmentFile(nil) + if err != nil { + t.Fatal(err) + } + if len(creditFile.Batches) != 0 { + t.Fatalf("creditFile.Batches=%#v", creditFile.Batches) + } + if !debitFile.IsADV() { + t.Fatalf("debitFile.IsADV=%v", debitFile.IsADV()) + } + + debitADVEntries := debitFile.Batches[0].GetADVEntries() + + if len(debitADVEntries) != 1 { + t.Errorf("debitADVEntries=%d", len(debitADVEntries)) + } + + entry := debitADVEntries[0] + if entry.ID != "adv-01" { + t.Errorf("ADV entry: %#v", entry) + } + if entry.TransactionCode != DebitForDebitsReceived { + t.Errorf("ADV entry: %#v", entry) + } + if entry.AdviceRoutingNumber != "121042882" { + t.Errorf("ADV entry: %#v", entry) + } + if entry.FileIdentification != "11131" { + t.Errorf("ADV entry: %#v", entry) + } + if entry.ACHOperatorRoutingNumber != "01100001" { + t.Errorf("ADV entry: %#v", entry) + } +} + +func TestSegmentFile_FileHeaderError(t *testing.T) { + achFile := NewFile() + + sfc := NewSegmentFileConfiguration() + _, _, err := achFile.SegmentFile(sfc) + + if err != nil { + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } + } +} + +func TestFileSegmentFileBatchControlCreditAmount(t *testing.T) { + // open a file for reading. Any io.Reader Can be used + f, err := os.Open(filepath.Join("test", "testdata", "ppd-mixedDebitCredit.ach")) + if err != nil { + t.Fatal(err) + } + r := NewReader(f) + achFile, err := r.Read() + if err != nil { + t.Fatalf("Issue reading file: %+v \n", err) + } + + // ensure we have a validated file structure + if achFile.Validate(); err != nil { + t.Fatalf("Could not validate entire read file: %v", err) + } + + sfc := NewSegmentFileConfiguration() + creditFile, debitFile, err := achFile.SegmentFile(sfc) + + if err != nil { + t.Fatalf("Could not segment the file: %+v \n", err) + } + + if err := creditFile.Validate(); err != nil { + t.Fatalf("Credit file did not validate: %+v \n", err) + } + + if err := debitFile.Validate(); err != nil { + t.Fatalf("Debit File did not validate: %+v \n", err) + } + + if creditFile.Batches[0].GetControl().TotalCreditEntryDollarAmount != 200000000 { + t.Errorf("expected %s received %v", "200000000", creditFile.Batches[0].GetControl().TotalCreditEntryDollarAmount) + } +} + +func TestFileSegmentFileBatchControlDebitAmount(t *testing.T) { + // open a file for reading. Any io.Reader Can be used + f, err := os.Open(filepath.Join("test", "testdata", "ppd-mixedDebitCredit.ach")) + if err != nil { + t.Fatal(err) + } + r := NewReader(f) + achFile, err := r.Read() + if err != nil { + t.Fatalf("Issue reading file: %+v \n", err) + } + + // ensure we have a validated file structure + if achFile.Validate(); err != nil { + t.Fatalf("Could not validate entire read file: %v", err) + } + + sfc := NewSegmentFileConfiguration() + creditFile, debitFile, err := achFile.SegmentFile(sfc) + + if err != nil { + t.Fatalf("Could not segment the file: %+v \n", err) + } + + if err := creditFile.Validate(); err != nil { + t.Fatalf("Credit file did not validate: %+v \n", err) + } + + if err := debitFile.Validate(); err != nil { + t.Fatalf("Debit File did not validate: %+v \n", err) + } + + if debitFile.Batches[0].GetControl().TotalDebitEntryDollarAmount != 200000000 { + t.Errorf("expected %s received %v", "200000000", debitFile.Batches[0].GetControl().TotalDebitEntryDollarAmount) + } +} + +func TestFileSegmentFileCreditBatches(t *testing.T) { + // open a file for reading. Any io.Reader Can be used + f, err := os.Open(filepath.Join("test", "testdata", "ppd-mixedDebitCredit.ach")) + if err != nil { + t.Fatal(err) + } + r := NewReader(f) + achFile, err := r.Read() + if err != nil { + t.Fatalf("Issue reading file: %+v \n", err) + } + + // ensure we have a validated file structure + if achFile.Validate(); err != nil { + t.Fatalf("Could not validate entire read file: %v", err) + } + + sfc := NewSegmentFileConfiguration() + creditFile, debitFile, err := achFile.SegmentFile(sfc) + + if err != nil { + t.Fatalf("Could not segment the file: %+v \n", err) + } + + if err := creditFile.Validate(); err != nil { + t.Fatalf("Credit file did not validate: %+v \n", err) + } + + if err := debitFile.Validate(); err != nil { + t.Fatalf("Debit File did not validate: %+v \n", err) + } + + if len(creditFile.Batches) != 1 { + t.Errorf("expected %s received %v", "1", len(creditFile.Batches)) + } +} + +func TestFileSegmentFileDebitBatches(t *testing.T) { + // open a file for reading. Any io.Reader Can be used + f, err := os.Open(filepath.Join("test", "testdata", "ppd-mixedDebitCredit.ach")) + if err != nil { + t.Fatal(err) + } + r := NewReader(f) + achFile, err := r.Read() + if err != nil { + t.Fatalf("Issue reading file: %+v \n", err) + } + + // ensure we have a validated file structure + if achFile.Validate(); err != nil { + t.Fatalf("Could not validate entire read file: %v", err) + } + + sfc := NewSegmentFileConfiguration() + creditFile, debitFile, err := achFile.SegmentFile(sfc) + + if err != nil { + t.Fatalf("Could not segment the file: %+v \n", err) + } + + if err := creditFile.Validate(); err != nil { + t.Fatalf("Credit file did not validate: %+v \n", err) + } + + if err := debitFile.Validate(); err != nil { + t.Fatalf("Debit File did not validate: %+v \n", err) + } + + if len(debitFile.Batches) != 1 { + t.Errorf("expected %s received %v", "1", len(debitFile.Batches)) + } +} + +func TestSegmentFileCreditOnly(t *testing.T) { + // write an ACH file into repository + fd, err := os.Open(filepath.Join("test", "testdata", "ppd-valid.json")) + if fd == nil { + t.Fatalf("empty ACH file: %v", err) + } + defer fd.Close() + bs, _ := io.ReadAll(fd) + file, _ := FileFromJSON(bs) + + sfc := NewSegmentFileConfiguration() + creditFile, debitFile, err := file.SegmentFile(sfc) + + if err != nil { + t.Fatalf("Error: %v", err) + } + + if len(creditFile.Batches) != 1 { + t.Errorf("expected %s received %v", "1", len(creditFile.Batches)) + } + + if debitFile.ID != "" { + t.Error("No Debit File") + } +} + +func TestSegmentFileDebitOnly(t *testing.T) { + // write an ACH file into repository + fd, err := os.Open(filepath.Join("test", "testdata", "ppd-valid-debit.json")) + if fd == nil { + t.Fatalf("empty ACH file: %v", err) + } + defer fd.Close() + bs, _ := io.ReadAll(fd) + file, _ := FileFromJSON(bs) + + sfc := NewSegmentFileConfiguration() + creditFile, debitFile, err := file.SegmentFile(sfc) + + if err != nil { + t.Fatalf("Error: %v", err) + } + + if creditFile.ID != "" { + t.Error("No Debit File") + } + + if len(debitFile.Batches) != 1 { + t.Errorf("expected %s received %v", "1", len(debitFile.Batches)) + } +} + +func TestFileIATSegmentFileCreditOnly(t *testing.T) { + // open a file for reading. Any io.Reader Can be used + f, err := os.Open(filepath.Join("test", "ach-iat-read", "iat-credit.ach")) + + if err != nil { + t.Fatal(err) + } + r := NewReader(f) + achFile, err := r.Read() + if err != nil { + t.Fatalf("Issue reading file: %+v \n", err) + } + + // ensure we have a validated file structure + if achFile.Validate(); err != nil { + t.Fatalf("Could not validate entire read file: %v", err) + } + + sfc := NewSegmentFileConfiguration() + creditFile, debitFile, err := achFile.SegmentFile(sfc) + + if err != nil { + t.Fatalf("Could not segment the file: %+v \n", err) + } + + if err := creditFile.Validate(); err != nil { + t.Fatalf("Credit file did not validate: %+v \n", err) + } + + if len(debitFile.IATBatches) > 0 { + t.Fatalf("IATFile should not have IAT batches: %+v \n", err) + } +} + +func TestFileIATSegmentFileDebitOnly(t *testing.T) { + // open a file for reading. Any io.Reader Can be used + f, err := os.Open(filepath.Join("test", "testdata", "iat-debit.ach")) + + if err != nil { + t.Fatal(err) + } + r := NewReader(f) + achFile, err := r.Read() + if err != nil { + t.Fatalf("Issue reading file: %+v \n", err) + } + + // ensure we have a validated file structure + if achFile.Validate(); err != nil { + t.Fatalf("Could not validate entire read file: %v", err) + } + + sfc := NewSegmentFileConfiguration() + creditFile, debitFile, err := achFile.SegmentFile(sfc) + + if err != nil { + t.Fatalf("Could not segment the file: %+v \n", err) + } + + if len(creditFile.IATBatches) > 0 { + t.Fatalf("IATFile should not have IAT credit batches: %+v \n", err) + } + + if err := debitFile.Validate(); err != nil { + t.Fatalf("Debit file did not validate: %+v \n", err) + } +} + +// TestFileIAT__SegmentFile test segmenting and IAT File +func TestFileIAT__SegmentFile(t *testing.T) { + // open a file for reading. Any io.Reader Can be used + f, err := os.Open(filepath.Join("test", "testdata", "iat-mixedDebitCredit.ach")) + + if err != nil { + t.Fatal(err) + } + r := NewReader(f) + achFile, err := r.Read() + if err != nil { + t.Fatalf("Issue reading file: %+v \n", err) + } + + // ensure we have a validated file structure + if achFile.Validate(); err != nil { + t.Fatalf("Could not validate entire read file: %v", err) + } + + sfc := NewSegmentFileConfiguration() + creditFile, debitFile, err := achFile.SegmentFile(sfc) + + if err != nil { + t.Fatalf("Could not segment the file: %+v \n", err) + } + + if err := creditFile.Validate(); err != nil { + t.Fatalf("Credit file did not validate: %+v \n", err) + } + + if err := debitFile.Validate(); err != nil { + t.Fatalf("Debit File did not validate: %+v \n", err) + } +} + +// TestFile_FlattenFileOneBatchHeader +func TestFile_FlattenFileOneBatchHeader(t *testing.T) { + // open a file for reading. Any io.Reader Can be used + f, err := os.Open(filepath.Join("test", "testdata", "flattenBatchesOneBatchHeader.ach")) + + if err != nil { + t.Fatal(err) + } + r := NewReader(f) + achFile, err := r.Read() + if err != nil { + t.Fatalf("Issue reading file: %+v \n", err) + } + + ff, err := achFile.FlattenBatches() + if err != nil { + t.Fatalf("Could not flatten the file: %+v \n", err) + } + if err := ff.Validate(); err != nil { + t.Fatalf("Flattend file did not validate: %+v \n", err) + } +} + +// TestFileFlattenFileMultipleBatchHeaders +func TestFileFlattenFileMultipleBatchHeaders(t *testing.T) { + // open a file for reading. Any io.Reader Can be used + f, err := os.Open(filepath.Join("test", "testdata", "flattenBatchesMultipleBatchHeaders.ach")) + + if err != nil { + t.Fatal(err) + } + r := NewReader(f) + achFile, err := r.Read() + if err != nil { + t.Fatalf("Issue reading file: %+v \n", err) + } + + ff, err := achFile.FlattenBatches() + if err != nil { + t.Fatalf("Could not flatten the file: %+v \n", err) + } + + if err := ff.Validate(); err != nil { + t.Fatalf("Flattend file did not validate: %+v \n", err) + } +} + +func TestFlattenFile_FileHeaderError(t *testing.T) { + achFile := NewFile() + + _, err := achFile.FlattenBatches() + + if err != nil { + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } + } +} + +// TestFile_FlattenFileOneIATBatchHeader +func TestFile_FlattenFileOneIATBatchHeader(t *testing.T) { + // open a file for reading. Any io.Reader Can be used + f, err := os.Open(filepath.Join("test", "testdata", "flattenIATBatchesOneBatchHeader.ach")) + + if err != nil { + t.Fatal(err) + } + r := NewReader(f) + achFile, err := r.Read() + if err != nil { + t.Fatalf("Issue reading file: %+v \n", err) + } + + ff, err := achFile.FlattenBatches() + + if err != nil { + t.Fatalf("Could not flatten the file: %+v \n", err) + } + + if err := ff.Validate(); err != nil { + t.Fatalf("Flattend file did not validate: %+v \n", err) + } +} + +// TestFileFlattenFileMultipleIATBatchHeaders +func TestFileFlattenFileMultipleIATBatchHeaders(t *testing.T) { + // open a file for reading. Any io.Reader Can be used + f, err := os.Open(filepath.Join("test", "testdata", "flattenIATBatchesMultipleBatchHeaders.ach")) + + if err != nil { + t.Fatal(err) + } + r := NewReader(f) + achFile, err := r.Read() + if err != nil { + t.Fatalf("Issue reading file: %+v \n", err) + } + + ff, err := achFile.FlattenBatches() + + if err != nil { + t.Fatalf("Could not flatten the file: %+v \n", err) + } + + if err := ff.Validate(); err != nil { + t.Fatalf("Flattend file did not validate: %+v \n", err) + } +} + +// TestFile_FlattenFileOneADVBatchHeader +func TestFile_FlattenFileOneADVBatchHeader(t *testing.T) { + // open a file for reading. Any io.Reader Can be used + f, err := os.Open(filepath.Join("test", "testdata", "flattenADVBatchesOneBatchHeader.ach")) + + if err != nil { + t.Fatal(err) + } + r := NewReader(f) + achFile, err := r.Read() + if err != nil { + t.Fatalf("Issue reading file: %+v \n", err) + } + + ff, err := achFile.FlattenBatches() + + if err != nil { + t.Fatalf("Could not flatten the file: %+v \n", err) + } + + if err := ff.Validate(); err != nil { + t.Fatalf("Flattend file did not validate: %+v \n", err) + } +} + +// TestFileFlattenFileMultipleADVBatchHeaders +func TestFileFlattenFileMultipleADVTBatchHeaders(t *testing.T) { + // open a file for reading. Any io.Reader Can be used + f, err := os.Open(filepath.Join("test", "testdata", "flattenADVBatchesMultipleBatchHeaders.ach")) + + if err != nil { + t.Fatal(err) + } + r := NewReader(f) + achFile, err := r.Read() + if err != nil { + t.Fatalf("Issue reading file: %+v \n", err) + } + + ff, err := achFile.FlattenBatches() + + if err != nil { + t.Fatalf("Could not flatten the file: %+v \n", err) + } + + if err := ff.Validate(); err != nil { + t.Fatalf("Flattend file did not validate: %+v \n", err) + } +} + +func TestFile__FlattenMicroDeposits(t *testing.T) { + in, err := readACHFilepath(filepath.Join("test", "testdata", "two-micro-deposits.ach")) + if err != nil { + t.Fatal(err) + } + if len(in.Batches) != 2 { + t.Fatalf("got %d batches", len(in.Batches)) + } + if len(in.Batches[0].GetEntries()) != 3 || len(in.Batches[1].GetEntries()) != 3 { + t.Fatal("unexpected EntryDetails in Batches") + } + + out, err := in.FlattenBatches() + if err != nil { + t.Fatal(err) + } + if len(out.Batches) != 1 { + t.Fatalf("got %d batches", len(out.Batches)) + } + + entries := out.Batches[0].GetEntries() + if len(entries) != 6 { + t.Errorf("got %d entries", len(entries)) + } + if err := out.Validate(); err != nil { + t.Fatal(err) + } +} + +func TestFile__SetValidation(t *testing.T) { + file, err := readACHFilepath(filepath.Join("test", "testdata", "web-debit.ach")) + if err != nil { + t.Fatal(err) + } + // Check before any modifications + if err := file.Create(); err != nil { + t.Fatal(err) + } + + // modify Header + file.Header.ImmediateOrigin = "123456789" // invalid routing number + file.SetValidation(&ValidateOpts{ + BypassOriginValidation: true, + }) + // Check we don't error + if err := file.Create(); err != nil { + t.Fatal(err) + } + + // Override + if err := file.Header.ValidateWith(&ValidateOpts{RequireABAOrigin: true}); err == nil { + t.Error("expected error") + } + + t.Logf("file.validateOpts=%#v", file.validateOpts) + + // nil File and set + file = nil + file.SetValidation(nil) + file.SetValidation(&ValidateOpts{}) +} + +func TestFile__FileHeaderFormattingWithValidation(t *testing.T) { + f := NewFile() + f.ID = "foo" + f.Header = mockFileHeader() + f.AddBatch(mockBatchPPD()) + f.SetValidation(&ValidateOpts{ + BypassOriginValidation: true, + BypassDestinationValidation: true, + }) + if err := f.Create(); err != nil { + t.Fatal(err) + } + + var b bytes.Buffer + writer := NewWriter(&b) + if err := writer.Write(f); err != nil { + t.Fatal(err) + } + + s, err := b.ReadString('\n') + if err != nil { + t.Fatal(err) + } + + want := fmt.Sprintf("101 %s %s", f.Header.ImmediateDestination, f.Header.ImmediateOrigin) + if !strings.HasPrefix(s, want) { + t.Fatalf("want \"%v\", got \"%v\"", want, s[:len(want)]) + } +} + +func TestFile__AscendingBatchSequence(t *testing.T) { + tests := []struct { + desc string + sequence []int + expectedErr error + }{ + { + desc: "success: strictly ascending", + sequence: []int{2, 5, 7, 9}, + expectedErr: nil, + }, + { + desc: "descending then ascending", + sequence: []int{3, 2, 4}, + expectedErr: NewErrFileBatchNumberAscending(3, 2), + }, + { + desc: "ascending then descending", + sequence: []int{2, 3, 2}, + expectedErr: NewErrFileBatchNumberAscending(3, 2), + }, + { + desc: "equal values", + sequence: []int{2, 3, 3, 4}, + expectedErr: NewErrFileBatchNumberAscending(3, 3), + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + file := mockFilePPD() + + for _, num := range tt.sequence { + mockBatch := mockBatchPPD() + mockBatch.GetHeader().BatchNumber = num + mockBatch.GetControl().BatchNumber = num + file.AddBatch(mockBatch) + } + + if err := file.Create(); err != nil { + t.Fatal(err) + } + + // None of the tests should error if unordered batch numbers are allowed + if err := file.ValidateWith(&ValidateOpts{AllowUnorderedBatchNumbers: true}); err != nil { + t.Fatal(err) + } + + err := file.Validate() + if err != tt.expectedErr { + t.Errorf("got: %v, want: %v", err, tt.expectedErr) + } + }) } } diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..c1a9e0bcf --- /dev/null +++ b/go.mod @@ -0,0 +1,38 @@ +module github.com/moov-io/ach + +go 1.19 + +require ( + github.com/Pallinder/go-randomdata v1.2.0 + github.com/aws/aws-lambda-go v1.34.1 + github.com/go-kit/kit v0.12.0 + github.com/go-kit/log v0.2.1 + github.com/gorilla/mux v1.8.0 + github.com/juju/ansiterm v0.0.0-20210929141451-8b71cc96ebdc + github.com/mattn/go-isatty v0.0.16 + github.com/moov-io/base v0.34.1 + github.com/prometheus/client_golang v1.13.0 + github.com/stretchr/testify v1.8.0 + golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-logfmt/logfmt v0.5.1 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/lunixbochs/vtclean v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect + github.com/rickar/cal/v2 v2.1.6 // indirect + golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect + golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..db85520b8 --- /dev/null +++ b/go.sum @@ -0,0 +1,531 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Pallinder/go-randomdata v1.2.0 h1:DZ41wBchNRb/0GfsePLiSwb0PHZmT67XY00lCDlaYPg= +github.com/Pallinder/go-randomdata v1.2.0/go.mod h1:yHmJgulpD2Nfrm0cR9tI/+oAgRqCQQixsA8HyRZfV9Y= +github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/aws/aws-lambda-go v1.34.1 h1:M3a/uFYBjii+tDcOJ0wL/WyFi2550FHoECdPf27zvOs= +github.com/aws/aws-lambda-go v1.34.1/go.mod h1:jwFe2KmMsHmffA1X2R09hH6lFzJQxzI8qK17ewzbQMM= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= +github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/juju/ansiterm v0.0.0-20210929141451-8b71cc96ebdc h1:ZQrgZFsLzkw7o3CoDzsfBhx0bf/1rVBXrLy8dXKRe8o= +github.com/juju/ansiterm v0.0.0-20210929141451-8b71cc96ebdc/go.mod h1:PyXUpnI3olx3bsPcHt98FGPX/KCFZ1Fi+hw1XLI6384= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= +github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/mattn/go-colorable v0.1.10/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/moov-io/base v0.34.1 h1:oOVHb+sFS0wj4aWBAr/LWdbgRIYI1RmP7XiclIWyREA= +github.com/moov-io/base v0.34.1/go.mod h1:SKRDVIgWvK4lEPenNK4saVVg87z/L8wMDqrWnsdnkjg= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU= +github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/rickar/cal/v2 v2.1.6 h1:a3U6CmALL25lBT6KBosITXCnekcNzcQGqGnpRgsVldc= +github.com/rickar/cal/v2 v2.1.6/go.mod h1:/fdlMcx7GjPlIBibMzOM9gMvDBsrK+mOtRXdTzUqV/A= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 h1:2o1E+E8TpNLklK9nHiPiK1uzIYrIHt+cQx3ynCwq9V8= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/iatBatch.go b/iatBatch.go new file mode 100644 index 000000000..25fe7d041 --- /dev/null +++ b/iatBatch.go @@ -0,0 +1,594 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "encoding/json" + "strconv" +) + +// IATBatch holds the Batch Header and Batch Control and all Entry Records for an IAT batch +// +// An IAT entry is a credit or debit ACH entry that is part of a payment transaction involving +// a financial agency's office (i.e., depository financial institution or business issuing money +// orders) that is not located in the territorial jurisdiction of the United States. IAT entries +// can be made to or from a corporate or consumer account and must be accompanied by seven (7) +// mandatory addenda records identifying the name and physical address of the Originator, name +// and physical address of the Receiver, Receiver's account number, Receiver's bank identity and +// reason for the payment. +type IATBatch struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + Header *IATBatchHeader `json:"IATBatchHeader"` + Entries []*IATEntryDetail `json:"IATEntryDetails"` + Control *BatchControl `json:"batchControl"` + + // category defines if the entry is a Forward, Return, or NOC + category string + // Converters is composed for ACH to GoLang Converters + converters +} + +// NewIATBatch takes a BatchHeader and returns a matching SEC code batch type that is a batcher. Returns an error if the SEC code is not supported. +func NewIATBatch(bh *IATBatchHeader) IATBatch { + if bh == nil { + bh = NewIATBatchHeader() + } + + iatBatch := IATBatch{} + iatBatch.SetControl(NewBatchControl()) + iatBatch.SetHeader(bh) + iatBatch.ID = bh.ID + return iatBatch +} + +// UnmarshalJSON un-marshals JSON IATBatch +func (iatBatch *IATBatch) UnmarshalJSON(p []byte) error { + if iatBatch == nil { + b := NewIATBatch(nil) + iatBatch = &b + } else { + iatBatch.Header = NewIATBatchHeader() + iatBatch.Control = NewBatchControl() + } + + type Alias IATBatch + aux := struct { + *Alias + }{ + (*Alias)(iatBatch), + } + if err := json.Unmarshal(p, &aux); err != nil { + return err + } + return nil +} + +// verify checks basic valid NACHA batch rules. Assumes properly parsed records. This does not mean it is a valid batch as validity is tied to each batch type +func (iatBatch *IATBatch) verify() error { + // No entries in batch + if len(iatBatch.Entries) <= 0 { + return iatBatch.Error("entries", ErrBatchNoEntries) + } + // verify field inclusion in all the records of the iatBatch. + if err := iatBatch.isFieldInclusion(); err != nil { + // wrap the field error in to a batch error for a consistent api + return iatBatch.Error("FieldError", err) + } + // validate batch header and control codes are the same + if iatBatch.Header.ServiceClassCode != iatBatch.Control.ServiceClassCode { + return iatBatch.Error("ServiceClassCode", + NewErrBatchHeaderControlEquality(iatBatch.Header.ServiceClassCode, iatBatch.Control.ServiceClassCode)) + } + // Control ODFIIdentification must be the same as batch header + if iatBatch.Header.ODFIIdentification != iatBatch.Control.ODFIIdentification { + return iatBatch.Error("ODFIIdentification", + NewErrBatchHeaderControlEquality(iatBatch.Header.ODFIIdentification, iatBatch.Control.ODFIIdentification)) + } + // batch number header and control must match + if iatBatch.Header.BatchNumber != iatBatch.Control.BatchNumber { + return iatBatch.Error("BatchNumber", + NewErrBatchHeaderControlEquality(iatBatch.Header.BatchNumber, iatBatch.Control.BatchNumber)) + } + if err := iatBatch.isBatchEntryCount(); err != nil { + return err + } + if err := iatBatch.isSequenceAscending(); err != nil { + return err + } + if err := iatBatch.isBatchAmount(); err != nil { + return err + } + if err := iatBatch.isEntryHash(); err != nil { + return err + } + if err := iatBatch.isTraceNumberODFI(); err != nil { + return err + } + if err := iatBatch.isAddendaSequence(); err != nil { + return err + } + if err := iatBatch.isCategory(); err != nil { + return err + } + return nil +} + +// Build creates valid batch by building sequence numbers and batch batch control. An error is returned if +// the batch being built has invalid records. +func (iatBatch *IATBatch) build() error { + // Requires a valid BatchHeader + if err := iatBatch.Header.Validate(); err != nil { + return err + } + if len(iatBatch.Entries) <= 0 { + return iatBatch.Error("entries", ErrBatchNoEntries) + } + // Create record sequence numbers + entryCount := 0 + seq := 1 + for i, entry := range iatBatch.Entries { + entryCount = entryCount + 1 + 7 + len(entry.Addenda17) + len(entry.Addenda18) + + if entry.Addenda98 != nil { + entryCount = entryCount + 1 + } + if entry.Addenda99 != nil { + entryCount = entryCount + 1 + } + + // Verifies the required addenda* properties for an IAT entry detail are defined + if err := iatBatch.addendaFieldInclusion(entry); err != nil { + return err + } + + currentTraceNumberODFI, err := strconv.Atoi(entry.TraceNumberField()[:8]) + if err != nil { + return err + } + + batchHeaderODFI, err := strconv.Atoi(iatBatch.Header.ODFIIdentificationField()[:8]) + if err != nil { + return err + } + + // Add a sequenced TraceNumber if one is not already set. + if currentTraceNumberODFI != batchHeaderODFI { + iatBatch.Entries[i].SetTraceNumber(iatBatch.Header.ODFIIdentification, seq) + } + + if entry.Category != CategoryNOC { + // Set TraceNumber for IATEntryDetail Addenda10-16 Record Properties + entry.Addenda10.EntryDetailSequenceNumber = iatBatch.parseNumField(iatBatch.Entries[i].TraceNumberField()[8:]) + entry.Addenda11.EntryDetailSequenceNumber = iatBatch.parseNumField(iatBatch.Entries[i].TraceNumberField()[8:]) + entry.Addenda12.EntryDetailSequenceNumber = iatBatch.parseNumField(iatBatch.Entries[i].TraceNumberField()[8:]) + entry.Addenda13.EntryDetailSequenceNumber = iatBatch.parseNumField(iatBatch.Entries[i].TraceNumberField()[8:]) + entry.Addenda14.EntryDetailSequenceNumber = iatBatch.parseNumField(iatBatch.Entries[i].TraceNumberField()[8:]) + entry.Addenda15.EntryDetailSequenceNumber = iatBatch.parseNumField(iatBatch.Entries[i].TraceNumberField()[8:]) + entry.Addenda16.EntryDetailSequenceNumber = iatBatch.parseNumField(iatBatch.Entries[i].TraceNumberField()[8:]) + } + // Set TraceNumber for Addendumer Addenda17 and Addenda18 SequenceNumber and EntryDetailSequenceNumber + seq++ + addenda17Seq := 1 + addenda18Seq := 1 + + for _, addenda17 := range entry.Addenda17 { + addenda17.SequenceNumber = addenda17Seq + addenda17.EntryDetailSequenceNumber = iatBatch.parseNumField(iatBatch.Entries[i].TraceNumberField()[8:]) + addenda17Seq++ + } + + for _, addenda18 := range entry.Addenda18 { + addenda18.SequenceNumber = addenda18Seq + addenda18.EntryDetailSequenceNumber = iatBatch.parseNumField(iatBatch.Entries[i].TraceNumberField()[8:]) + addenda18Seq++ + } + } + + // build a BatchControl record + bc := NewBatchControl() + bc.ServiceClassCode = iatBatch.Header.ServiceClassCode + bc.ODFIIdentification = iatBatch.Header.ODFIIdentification + bc.BatchNumber = iatBatch.Header.BatchNumber + bc.EntryAddendaCount = entryCount + bc.EntryHash = iatBatch.calculateEntryHash() + bc.TotalCreditEntryDollarAmount, bc.TotalDebitEntryDollarAmount = iatBatch.calculateBatchAmounts() + iatBatch.Control = bc + + return nil +} + +// SetHeader appends an BatchHeader to the Batch +func (iatBatch *IATBatch) SetHeader(batchHeader *IATBatchHeader) { + iatBatch.Header = batchHeader +} + +// GetHeader returns the current Batch header +func (iatBatch *IATBatch) GetHeader() *IATBatchHeader { + return iatBatch.Header +} + +// SetControl appends an BatchControl to the Batch +func (iatBatch *IATBatch) SetControl(batchControl *BatchControl) { + iatBatch.Control = batchControl +} + +// GetControl returns the current Batch Control +func (iatBatch *IATBatch) GetControl() *BatchControl { + return iatBatch.Control +} + +// GetEntries returns a slice of entry details for the batch +func (iatBatch *IATBatch) GetEntries() []*IATEntryDetail { + return iatBatch.Entries +} + +// AddEntry appends an EntryDetail to the Batch +func (iatBatch *IATBatch) AddEntry(entry *IATEntryDetail) { + iatBatch.category = entry.Category + iatBatch.Entries = append(iatBatch.Entries, entry) +} + +// Category returns IATBatch Category +func (iatBatch *IATBatch) Category() string { + return iatBatch.category +} + +// isFieldInclusion iterates through all the records in the batch and verifies against default fields +func (iatBatch *IATBatch) isFieldInclusion() error { + if err := iatBatch.Header.Validate(); err != nil { + return err + } + for _, entry := range iatBatch.Entries { + if err := entry.Validate(); err != nil { + return err + } + // Verifies the required Addenda* properties for an IAT entry detail are included + if err := iatBatch.addendaFieldInclusion(entry); err != nil { + return err + } + if entry.Category != CategoryNOC { + // Verifies each Addenda* record is valid + if err := entry.Addenda10.Validate(); err != nil { + return err + } + if err := entry.Addenda11.Validate(); err != nil { + return err + } + if err := entry.Addenda12.Validate(); err != nil { + return err + } + if err := entry.Addenda13.Validate(); err != nil { + return err + } + if err := entry.Addenda14.Validate(); err != nil { + return err + } + if err := entry.Addenda15.Validate(); err != nil { + return err + } + if err := entry.Addenda16.Validate(); err != nil { + return err + } + for _, Addenda17 := range entry.Addenda17 { + if err := Addenda17.Validate(); err != nil { + return err + } + } + for _, Addenda18 := range entry.Addenda18 { + if err := Addenda18.Validate(); err != nil { + return err + } + } + } + if entry.Category == CategoryNOC { + if entry.Addenda98 == nil { + return fieldError("Addenda98", ErrFieldInclusion) + } + if err := entry.Addenda98.Validate(); err != nil { + return err + } + } + if entry.Category == CategoryReturn { + if entry.Addenda99 == nil { + return fieldError("Addenda99", ErrFieldInclusion) + } + + if err := entry.Addenda99.Validate(); err != nil { + return err + } + } + } + return iatBatch.Control.Validate() +} + +// isBatchEntryCount validate Entry count is accurate +// The Entry/Addenda Count Field is a tally of each Entry Detail and Addenda +// Record processed within the batch +func (iatBatch *IATBatch) isBatchEntryCount() error { + entryCount := 0 + for _, entry := range iatBatch.Entries { + entryCount = entryCount + 1 + 7 + len(entry.Addenda17) + len(entry.Addenda18) + + if entry.Addenda98 != nil { + entryCount = entryCount + 1 + } + if entry.Addenda99 != nil { + entryCount = entryCount + 1 + } + } + if entryCount != iatBatch.Control.EntryAddendaCount { + return iatBatch.Error("EntryAddendaCount", + NewErrBatchCalculatedControlEquality(entryCount, iatBatch.Control.EntryAddendaCount)) + } + return nil +} + +// isBatchAmount validate Amount is the same as what is in the Entries +// The Total Debit and Credit Entry Dollar Amount fields contain accumulated +// Entry Detail debit and credit totals within a given batch +func (iatBatch *IATBatch) isBatchAmount() error { + credit, debit := iatBatch.calculateBatchAmounts() + if debit != iatBatch.Control.TotalDebitEntryDollarAmount { + return iatBatch.Error("TotalDebitEntryDollarAmount", + NewErrBatchCalculatedControlEquality(debit, iatBatch.Control.TotalDebitEntryDollarAmount)) + } + + if credit != iatBatch.Control.TotalCreditEntryDollarAmount { + return iatBatch.Error("TotalCreditEntryDollarAmount", + NewErrBatchCalculatedControlEquality(credit, iatBatch.Control.TotalCreditEntryDollarAmount)) + } + return nil +} + +func (iatBatch *IATBatch) calculateBatchAmounts() (credit int, debit int) { + for _, entry := range iatBatch.Entries { + switch entry.TransactionCode { + case CheckingCredit, CheckingReturnNOCCredit, CheckingPrenoteCredit, CheckingZeroDollarRemittanceCredit, + SavingsCredit, SavingsReturnNOCCredit, SavingsPrenoteCredit, SavingsZeroDollarRemittanceCredit, GLCredit, + GLReturnNOCCredit, GLPrenoteCredit, GLZeroDollarRemittanceCredit, LoanCredit, LoanReturnNOCCredit, + LoanPrenoteCredit, LoanZeroDollarRemittanceCredit: + credit = credit + entry.Amount + case CheckingDebit, CheckingReturnNOCDebit, CheckingPrenoteDebit, CheckingZeroDollarRemittanceDebit, + SavingsDebit, SavingsReturnNOCDebit, SavingsPrenoteDebit, SavingsZeroDollarRemittanceDebit, GLDebit, + GLReturnNOCDebit, GLPrenoteDebit, GLZeroDollarRemittanceDebit, LoanDebit, LoanReturnNOCDebit: + debit = debit + entry.Amount + } + } + return credit, debit +} + +// isSequenceAscending Individual Entry Detail Records within individual batches must +// be in ascending Trace Number order (although Trace Numbers need not necessarily be consecutive). +func (iatBatch *IATBatch) isSequenceAscending() error { + lastSeq := "-1" + for _, entry := range iatBatch.Entries { + if entry.TraceNumber <= lastSeq { + return iatBatch.Error("TraceNumber", NewErrBatchAscending(lastSeq, entry.TraceNumber)) + } + lastSeq = entry.TraceNumber + } + return nil +} + +// isEntryHash validates the hash by recalculating the result +func (iatBatch *IATBatch) isEntryHash() error { + hashField := iatBatch.calculateEntryHash() + if hashField != iatBatch.Control.EntryHash { + return iatBatch.Error("EntryHash", + NewErrBatchCalculatedControlEquality(hashField, iatBatch.Control.EntryHash)) + } + return nil +} + +// calculateEntryHash This field is prepared by hashing the 8-digit Routing Number in each entry. +// The Entry Hash provides a check against inadvertent alteration of data +func (iatBatch *IATBatch) calculateEntryHash() int { + hash := 0 + for _, entry := range iatBatch.Entries { + entryRDFI, _ := strconv.Atoi(aba8(entry.RDFIIdentification)) + hash += entryRDFI + } + + // EntryHash is essentially the sum of all the RDFI routing numbers in the batch. If the sum exceeds 10 digits + // (because you have lots of Entry Detail Records), lop off the most significant digits of the sum until there + // are only 10. + return iatBatch.leastSignificantDigits(hash, 10) +} + +// isTraceNumberODFI checks if the first 8 positions of the entry detail trace number +// match the batch header ODFI +func (iatBatch *IATBatch) isTraceNumberODFI() error { + for _, entry := range iatBatch.Entries { + if iatBatch.Header.ODFIIdentificationField() != entry.TraceNumberField()[:8] { + return iatBatch.Error("ODFIIdentificationField", + NewErrBatchTraceNumberNotODFI(iatBatch.Header.ODFIIdentificationField(), entry.TraceNumberField()[:8])) + } + } + + return nil +} + +// isAddendaSequence check multiple errors on addenda records in the batch entries +func (iatBatch *IATBatch) isAddendaSequence() error { + for _, entry := range iatBatch.Entries { + // addenda without indicator flag of 1 + if entry.AddendaRecordIndicator != 1 { + return iatBatch.Error("AddendaRecordIndicator", ErrIATBatchAddendaIndicator) + } + + if entry.Category == CategoryNOC { + return nil + } + + // Verify Addenda* entry detail sequence numbers are valid + entryTN := entry.TraceNumberField()[8:] + if entry.Addenda10.EntryDetailSequenceNumberField() != entryTN { + return iatBatch.Error("TraceNumber", NewErrBatchAddendaTraceNumber(entry.Addenda10.EntryDetailSequenceNumberField(), entryTN)) + } + if entry.Addenda11.EntryDetailSequenceNumberField() != entryTN { + return iatBatch.Error("TraceNumber", NewErrBatchAddendaTraceNumber(entry.Addenda11.EntryDetailSequenceNumberField(), entryTN)) + } + if entry.Addenda12.EntryDetailSequenceNumberField() != entryTN { + return iatBatch.Error("TraceNumber", NewErrBatchAddendaTraceNumber(entry.Addenda12.EntryDetailSequenceNumberField(), entryTN)) + } + if entry.Addenda13.EntryDetailSequenceNumberField() != entryTN { + return iatBatch.Error("TraceNumber", NewErrBatchAddendaTraceNumber(entry.Addenda13.EntryDetailSequenceNumberField(), entryTN)) + } + if entry.Addenda14.EntryDetailSequenceNumberField() != entryTN { + return iatBatch.Error("TraceNumber", NewErrBatchAddendaTraceNumber(entry.Addenda14.EntryDetailSequenceNumberField(), entryTN)) + } + if entry.Addenda15.EntryDetailSequenceNumberField() != entryTN { + return iatBatch.Error("TraceNumber", NewErrBatchAddendaTraceNumber(entry.Addenda15.EntryDetailSequenceNumberField(), entryTN)) + } + if entry.Addenda16.EntryDetailSequenceNumberField() != entryTN { + return iatBatch.Error("TraceNumber", NewErrBatchAddendaTraceNumber(entry.Addenda16.EntryDetailSequenceNumberField(), entryTN)) + } + + // check if sequence is ascending for addendumer - Addenda17 and Addenda18 + lastAddenda17Seq := -1 + lastAddenda18Seq := -1 + + for _, addenda17 := range entry.Addenda17 { + if addenda17.SequenceNumber < lastAddenda17Seq { + return iatBatch.Error("SequenceNumber", NewErrBatchAscending(lastAddenda17Seq, addenda17.SequenceNumber)) + } + lastAddenda17Seq = addenda17.SequenceNumber + // check that we are in the correct Entry Detail + if !(addenda17.EntryDetailSequenceNumberField() == entry.TraceNumberField()[8:]) { + return iatBatch.Error("TraceNumber", NewErrBatchAddendaTraceNumber(addenda17.EntryDetailSequenceNumberField(), entryTN)) + } + } + + for _, addenda18 := range entry.Addenda18 { + if addenda18.SequenceNumber < lastAddenda18Seq { + return iatBatch.Error("SequenceNumber", NewErrBatchAscending(lastAddenda18Seq, addenda18.SequenceNumber)) + } + lastAddenda18Seq = addenda18.SequenceNumber + // check that we are in the correct Entry Detail + if !(addenda18.EntryDetailSequenceNumberField() == entry.TraceNumberField()[8:]) { + return iatBatch.Error("TraceNumber", NewErrBatchAddendaTraceNumber(addenda18.EntryDetailSequenceNumberField(), entryTN)) + } + } + } + return nil +} + +// isCategory verifies that a Forward and Return Category are not in the same batch +func (iatBatch *IATBatch) isCategory() error { + category := iatBatch.GetEntries()[0].Category + if len(iatBatch.Entries) > 1 { + for i := 1; i < len(iatBatch.Entries); i++ { + if iatBatch.Entries[i].Category == CategoryNOC { + continue + } + if iatBatch.Entries[i].Category != category { + return iatBatch.Error("Category", NewErrBatchCategory(iatBatch.Entries[i].Category, category)) + } + } + } + return nil +} + +func (iatBatch *IATBatch) addendaFieldInclusion(entry *IATEntryDetail) error { + if entry.Category == CategoryNOC { + return nil + } + if entry.Addenda10 == nil { + return fieldError("Addenda10", ErrFieldInclusion) + } + if entry.Addenda11 == nil { + return fieldError("Addenda11", ErrFieldInclusion) + } + if entry.Addenda12 == nil { + return fieldError("Addenda12", ErrFieldInclusion) + } + if entry.Addenda13 == nil { + return fieldError("Addenda13", ErrFieldInclusion) + } + if entry.Addenda14 == nil { + return fieldError("Addenda14", ErrFieldInclusion) + } + if entry.Addenda15 == nil { + return fieldError("Addenda15", ErrFieldInclusion) + } + if entry.Addenda16 == nil { + return fieldError("Addenda16", ErrFieldInclusion) + } + return nil +} + +// Create will tabulate and assemble an ACH batch into a valid state. This includes +// setting any posting dates, sequence numbers, counts, and sums. +// +// Create implementations are free to modify computable fields in a file and should +// call the Batch's Validate function at the end of their execution. +func (iatBatch *IATBatch) Create() error { + // generates sequence numbers and batch control + if err := iatBatch.build(); err != nil { + return err + } + // Additional steps specific to batch type + // ... + return iatBatch.Validate() +} + +// Validate checks properties of the ACH batch to ensure they match NACHA guidelines. +// This includes computing checksums, totals, and sequence orderings. +// +// Validate will never modify the iatBatch. +func (iatBatch *IATBatch) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := iatBatch.verify(); err != nil { + return err + } + // Add configuration based validation for this type. + + for _, entry := range iatBatch.Entries { + if len(entry.Addenda17) > 2 { + return iatBatch.Error("Addenda17", NewErrBatchAddendaCount(len(entry.Addenda17), 2)) + } + + if len(entry.Addenda18) > 5 { + return iatBatch.Error("Addenda18", NewErrBatchAddendaCount(len(entry.Addenda18), 5)) + } + if iatBatch.Header.ServiceClassCode == AutomatedAccountingAdvices { + return iatBatch.Error("ServiceClassCode", ErrBatchServiceClassCode, iatBatch.Header.ServiceClassCode) + } + if entry.Category == CategoryNOC { + if iatBatch.GetHeader().IATIndicator != IATCOR { + return iatBatch.Error("IATIndicator", NewErrBatchIATNOC(iatBatch.GetHeader().IATIndicator, IATCOR)) + } + if iatBatch.GetHeader().StandardEntryClassCode != COR { + return iatBatch.Error("StandardEntryClassCode", NewErrBatchIATNOC(iatBatch.GetHeader().StandardEntryClassCode, COR)) + } + switch entry.TransactionCode { + case CheckingCredit, CheckingDebit, CheckingPrenoteCredit, CheckingPrenoteDebit, + CheckingZeroDollarRemittanceCredit, CheckingZeroDollarRemittanceDebit, + SavingsCredit, SavingsDebit, SavingsPrenoteCredit, SavingsPrenoteDebit, + SavingsZeroDollarRemittanceCredit, SavingsZeroDollarRemittanceDebit, + GLCredit, GLDebit, GLPrenoteCredit, GLPrenoteDebit, + GLZeroDollarRemittanceCredit, GLZeroDollarRemittanceDebit, + LoanCredit, LoanDebit, LoanPrenoteCredit, LoanZeroDollarRemittanceCredit: + return iatBatch.Error("TransactionCode", ErrBatchTransactionCode, entry.TransactionCode) + } + } + + } + return nil +} diff --git a/iatBatchHeader.go b/iatBatchHeader.go new file mode 100644 index 000000000..54918a82e --- /dev/null +++ b/iatBatchHeader.go @@ -0,0 +1,414 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "fmt" + "strconv" + "strings" + "unicode/utf8" + + "github.com/moov-io/ach/internal/iso3166" + "github.com/moov-io/ach/internal/iso4217" +) + +// msgServiceClass + +// IATBatchHeader identifies the originating entity and the type of transactions +// contained in the batch for SEC Code IAT. This record also contains the effective +// date, or desired settlement date, for all entries contained in this batch. The +// settlement date field is not entered as it is determined by the ACH operator. +// +// An IAT entry is a credit or debit ACH entry that is part of a payment transaction +// involving a financial agency's office (i.e., depository financial institution or +// business issuing money orders) that is not located in the territorial jurisdiction +// of the United States. IAT entries can be made to or from a corporate or consumer +// account and must be accompanied by seven (7) mandatory addenda records identifying +// the name and physical address of the Originator, name and physical address of the +// Receiver, Receiver's account number, Receiver's bank identity and reason for the payment. +type IATBatchHeader struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + + // ServiceClassCode ACH Mixed Debits and Credits '200' + // ACH Credits Only '220' + // ACH Debits Only '225' + ServiceClassCode int `json:"serviceClassCode"` + + // IATIndicator - Leave Blank - It is only used for corrected IAT entries + IATIndicator string `json:"IATIndicator,omitempty"` + + // ForeignExchangeIndicator is a code indicating currency conversion + // + // FV Fixed-to-Variable – Entry is originated in a fixed-value amount + // and is to be received in a variable amount resulting from the + // execution of the foreign exchange conversion. + // + // VF Variable-to-Fixed – Entry is originated in a variable-value + // amount based on a specific foreign exchange rate for conversion to a + // fixed-value amount in which the entry is to be received. + // + // FF Fixed-to-Fixed – Entry is originated in a fixed-value amount and + // is to be received in the same fixed-value amount in the same + // currency denomination. There is no foreign exchange conversion for + // entries transmitted using this code. For entries originated in a fixed value + // amount, the foreign Exchange Reference Field will be space + // filled. + ForeignExchangeIndicator string `json:"foreignExchangeIndicator"` + + // ForeignExchangeReferenceIndicator is a code used to indicate the content of the + // Foreign Exchange Reference Field and is filled by the gateway operator. + // Valid entries are: + // 1 - Foreign Exchange Rate; + // 2 - Foreign Exchange Reference Number; or + // 3 - Space Filled + ForeignExchangeReferenceIndicator int `json:"foreignExchangeReferenceIndicator"` + + // ForeignExchangeReference Contains either the foreign exchange rate used to execute + // the foreign exchange conversion of a cross-border entry or another reference to the foreign + // exchange transaction. + ForeignExchangeReference string `json:"foreignExchangeReference"` + + // ISODestinationCountryCode is the two-character code, as approved by the International + // Organization for Standardization (ISO), to identify the country in which the entry is + // to be received. Values can be found on the International Organization for Standardization + // website: www.iso.org. For entries destined to account holder in the U.S., this would be US. + ISODestinationCountryCode string `json:"ISODestinationCountryCode"` + + // OriginatorIdentification identifies the following: + // For U.S. entities: the number assigned will be your tax ID + // For non-U.S. entities: the number assigned will be your DDA number, + // or the last 9 characters of your account number if it exceeds 9 characters + OriginatorIdentification string `json:"originatorIdentification"` + + // StandardEntryClassCode for consumer and non consumer international payments is IAT + // Identifies the payment type (product) found within an ACH batch-using a 3-character code. + // The SEC Code pertains to all items within batch. + // Determines format of the detail records. + // Determines addenda records (required or optional PLUS one or up to 9,999 records). + // Determines rules to follow (return time frames). + // Some SEC codes require specific data in predetermined fields within the ACH record + StandardEntryClassCode string `json:"standardEntryClassCode"` + + // CompanyEntryDescription A description of the entries contained in the batch + // + //The Originator establishes the value of this field to provide a + // description of the purpose of the entry to be displayed back to + // the receive For example, "GAS BILL," "REG. SALARY," "INS. PREM," + // "SOC. SEC.," "DTC," "TRADE PAY," "PURCHASE," etc. + // + // This field must contain the word "REVERSAL" (left justified) when the + // batch contains reversing entries. + // + // This field must contain the word "RECLAIM" (left justified) when the + // batch contains reclamation entries. + // + // This field must contain the word "NONSETTLED" (left justified) when the + // batch contains entries which could not settle. + CompanyEntryDescription string `json:"companyEntryDescription,omitempty"` + + // ISOOriginatingCurrencyCode is the three-character code, as approved by the International + // Organization for Standardization (ISO), to identify the currency denomination in which the + // entry was first originated. If the source of funds is within the territorial jurisdiction + // of the U.S., enter 'USD', otherwise refer to International Organization for Standardization + // website for value: www.iso.org -- (Account Currency) + ISOOriginatingCurrencyCode string `json:"ISOOriginatingCurrencyCode"` + + // ISODestinationCurrencyCode is the three-character code, as approved by the International + // Organization for Standardization (ISO), to identify the currency denomination in which the + // entry will ultimately be settled. If the final destination of funds is within the territorial + // jurisdiction of the U.S., enter “USD”, otherwise refer to International Organization for + // Standardization website for value: www.iso.org -- (Payment Currency) + ISODestinationCurrencyCode string `json:"ISODestinationCurrencyCode"` + + // EffectiveEntryDate the date on which the entries are to settle. Format: YYMMDD (Y=Year, M=Month, D=Day) + EffectiveEntryDate string `json:"effectiveEntryDate,omitempty"` + + // SettlementDate Leave blank, this field is inserted by the ACH operator + SettlementDate string `json:"settlementDate,omitempty"` + + // OriginatorStatusCode refers to the ODFI initiating the Entry. + // 0 ADV File prepared by an ACH Operator. + // 1 This code identifies the Originator as a depository financial institution. + // 2 This code identifies the Originator as a Federal Government entity or agency. + OriginatorStatusCode int `json:"originatorStatusCode,omitempty"` + + // ODFIIdentification First 8 digits of the originating DFI transit routing number + // For Inbound IAT Entries, this field contains the routing number of the U.S. Gateway + // Operator. For Outbound IAT Entries, this field contains the standard routing number, + // as assigned by Accuity, that identifies the U.S. ODFI initiating the Entry. + // Format - TTTTAAAA + ODFIIdentification string `json:"ODFIIdentification"` + + // BatchNumber is assigned in ascending sequence to each batch by the ODFI + // or its Sending Point in a given file of entries. Since the batch number + // in the Batch Header Record and the Batch Control Record is the same, + // the ascending sequence number should be assigned by batch and not by + // record. + BatchNumber int `json:"batchNumber"` + + // validator is composed for data validation + validator + + // converters is composed for ACH to golang Converters + converters +} + +const ( + // IATCOR is the valid value for IATBatchHeader.IATIndicator for IAT Notification Of Changr + IATCOR = "IATCOR" +) + +// NewIATBatchHeader returns a new BatchHeader with default values for non exported fields +func NewIATBatchHeader() *IATBatchHeader { + iatBh := &IATBatchHeader{ + OriginatorStatusCode: 0, //Prepared by an Originator + BatchNumber: 1, + } + return iatBh +} + +// Parse takes the input record string and parses the BatchHeader values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. +func (iatBh *IATBatchHeader) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + // 1-1 Always "5" + // 2-4 MixedCreditsAnDebits (220), CReditsOnly 9220), DebitsOnly (225)" + iatBh.ServiceClassCode = iatBh.parseNumField(record[1:4]) + // 05-20 Blank except for corrected IAT entries + iatBh.IATIndicator = iatBh.parseStringField(record[4:20]) + // 21-22 A code indicating currency conversion + // “FV” Fixed-to-Variable + // “VF” Variable-to-Fixed + // “FF” Fixed-to-Fixed + iatBh.ForeignExchangeIndicator = iatBh.parseStringField(record[20:22]) + // 23-23 Foreign Exchange Reference Indicator – Refers to “Foreign Exchange Reference” + // field and is filled by the gateway operator. Valid entries are: + // 1 - Foreign Exchange Rate; + // 2 - Foreign Exchange Reference Number; or + // 3 - Space Filled + iatBh.ForeignExchangeReferenceIndicator = iatBh.parseNumField(record[22:23]) + // 24-38 Contains either the foreign exchange rate used to execute the + // foreign exchange conversion of a cross-border entry or another + // reference to the foreign exchange transaction. + iatBh.ForeignExchangeReference = iatBh.parseStringField(record[23:38]) + // 39-40 Receiver ISO Country Code - For entries + // destined to account holder in the U.S., this would be 'US'. + iatBh.ISODestinationCountryCode = iatBh.parseStringField(record[38:40]) + // 41-50 For U.S. entities: the number assigned will be your tax ID + // For non-U.S. entities: the number assigned will be your DDA number, + // or the last 9 characters of your account number if it exceeds 9 characters + iatBh.OriginatorIdentification = iatBh.parseStringField(record[40:50]) + // 51-53 IAT for both consumer and non consumer international payments + iatBh.StandardEntryClassCode = record[50:53] + // 54-63 Your description of the transaction. This text will appear on the receivers' bank statement. + // For example: "Payroll " + iatBh.CompanyEntryDescription = strings.TrimSpace(record[53:63]) + // 64-66 Originator ISO Currency Code + iatBh.ISOOriginatingCurrencyCode = iatBh.parseStringField(record[63:66]) + // 67-69 Receiver ISO Currency Code + iatBh.ISODestinationCurrencyCode = iatBh.parseStringField(record[66:69]) + // 70-75 Date transactions are to be posted to the receivers' account. + // You almost always want the transaction to post as soon as possible, so put tomorrow's date in YYMMDD format + iatBh.EffectiveEntryDate = iatBh.validateSimpleDate(record[69:75]) + // 76-78 Always blank (just fill with spaces) + iatBh.SettlementDate = iatBh.validateSettlementDate(record[75:78]) + // 79-79 Always 1 + iatBh.OriginatorStatusCode = iatBh.parseNumField(record[78:79]) + // 80-87 Your ODFI's routing number without the last digit. The last digit is simply a + // checksum digit, which is why it is not necessary + iatBh.ODFIIdentification = iatBh.parseStringField(record[79:87]) + // 88-94 Sequential number of this Batch Header Record + // For example, put "1" if this is the first Batch Header Record in the file + iatBh.BatchNumber = iatBh.parseNumField(record[87:94]) +} + +// String writes the BatchHeader struct to a 94 character string. +func (iatBh *IATBatchHeader) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(batchHeaderPos) + buf.WriteString(fmt.Sprintf("%v", iatBh.ServiceClassCode)) + buf.WriteString(iatBh.IATIndicatorField()) + buf.WriteString(iatBh.ForeignExchangeIndicatorField()) + buf.WriteString(iatBh.ForeignExchangeReferenceIndicatorField()) + buf.WriteString(iatBh.ForeignExchangeReferenceField()) + buf.WriteString(iatBh.ISODestinationCountryCodeField()) + buf.WriteString(iatBh.OriginatorIdentificationField()) + buf.WriteString(iatBh.StandardEntryClassCode) + buf.WriteString(iatBh.CompanyEntryDescriptionField()) + buf.WriteString(iatBh.ISOOriginatingCurrencyCodeField()) + buf.WriteString(iatBh.ISODestinationCurrencyCodeField()) + buf.WriteString(iatBh.EffectiveEntryDateField()) + buf.WriteString(iatBh.SettlementDateField()) + buf.WriteString(fmt.Sprintf("%v", iatBh.OriginatorStatusCode)) + buf.WriteString(iatBh.ODFIIdentificationField()) + buf.WriteString(iatBh.BatchNumberField()) + return buf.String() +} + +// Validate performs NACHA format rule checks on the record and returns an error if not Validated +// The first error encountered is returned and stops that parsing. +func (iatBh *IATBatchHeader) Validate() error { + if err := iatBh.fieldInclusion(); err != nil { + return err + } + if err := iatBh.isServiceClass(iatBh.ServiceClassCode); err != nil { + return fieldError("ServiceClassCode", err, strconv.Itoa(iatBh.ServiceClassCode)) + } + if err := iatBh.isForeignExchangeIndicator(iatBh.ForeignExchangeIndicator); err != nil { + return fieldError("ForeignExchangeIndicator", err, iatBh.ForeignExchangeIndicator) + } + if err := iatBh.isForeignExchangeReferenceIndicator(iatBh.ForeignExchangeReferenceIndicator); err != nil { + return fieldError("ForeignExchangeReferenceIndicator", err, strconv.Itoa(iatBh.ForeignExchangeReferenceIndicator)) + } + if !iso3166.Valid(iatBh.ISODestinationCountryCode) { + return fieldError("ISODestinationCountryCode", ErrValidISO3166, iatBh.ISODestinationCountryCode) + } + if err := iatBh.isSECCode(iatBh.StandardEntryClassCode); err != nil { + return fieldError("StandardEntryClassCode", err, iatBh.StandardEntryClassCode) + } + if err := iatBh.isAlphanumeric(iatBh.CompanyEntryDescription); err != nil { + return fieldError("CompanyEntryDescription", err, iatBh.CompanyEntryDescription) + } + if !iso4217.Valid(iatBh.ISOOriginatingCurrencyCode) { + return fieldError("ISOOriginatingCurrencyCode", ErrValidISO4217, iatBh.ISOOriginatingCurrencyCode) + } + if !iso4217.Valid(iatBh.ISODestinationCurrencyCode) { + return fieldError("ISODestinationCurrencyCode", ErrValidISO4217, iatBh.ISODestinationCurrencyCode) + } + if err := iatBh.isOriginatorStatusCode(iatBh.OriginatorStatusCode); err != nil { + return fieldError("OriginatorStatusCode", err, strconv.Itoa(iatBh.OriginatorStatusCode)) + } + return nil +} + +// fieldInclusion validate mandatory fields are not default values. If fields are +// invalid the ACH transfer will be returned. +func (iatBh *IATBatchHeader) fieldInclusion() error { + if iatBh.ServiceClassCode == 0 { + return fieldError("ServiceClassCode", ErrFieldInclusion, strconv.Itoa(iatBh.ServiceClassCode)) + } + if iatBh.ForeignExchangeIndicator == "" { + return fieldError("ForeignExchangeIndicator", ErrFieldInclusion, iatBh.ForeignExchangeIndicator) + } + if iatBh.ForeignExchangeReferenceIndicator == 0 { + return fieldError("ForeignExchangeReferenceIndicator", ErrFieldRequired, strconv.Itoa(iatBh.ForeignExchangeReferenceIndicator)) + } + // ToDo: It can be space filled based on ForeignExchangeReferenceIndicator just use a validator to handle - + // ToDo: Calling Field ok for validation? + /* if iatBh.ForeignExchangeReference == "" { + return fieldError("ForeignExchangeReference", ErrFieldRequired, iatBh.ForeignExchangeReference) + }*/ + if iatBh.ISODestinationCountryCode == "" { + return fieldError("ISODestinationCountryCode", ErrFieldInclusion, iatBh.ISODestinationCountryCode) + } + if iatBh.OriginatorIdentification == "" { + return fieldError("OriginatorIdentification", ErrFieldInclusion, iatBh.OriginatorIdentification) + } + if iatBh.StandardEntryClassCode == "" { + return fieldError("StandardEntryClassCode", ErrFieldInclusion, iatBh.StandardEntryClassCode) + } + if iatBh.CompanyEntryDescription == "" { + return fieldError("CompanyEntryDescription", ErrFieldInclusion, iatBh.CompanyEntryDescription) + } + if iatBh.ISOOriginatingCurrencyCode == "" { + return fieldError("ISOOriginatingCurrencyCode", ErrFieldInclusion, iatBh.ISOOriginatingCurrencyCode) + } + if iatBh.ISODestinationCurrencyCode == "" { + return fieldError("ISODestinationCurrencyCode", ErrFieldInclusion, iatBh.ISODestinationCurrencyCode) + } + if iatBh.ODFIIdentification == "" { + return fieldError("ODFIIdentification", ErrFieldInclusion, iatBh.ODFIIdentificationField()) + } + return nil +} + +// IATIndicatorField gets the IATIndicator left padded +func (iatBh *IATBatchHeader) IATIndicatorField() string { + // should this be left padded + return iatBh.alphaField(iatBh.IATIndicator, 16) +} + +// ForeignExchangeIndicatorField gets the ForeignExchangeIndicator +func (iatBh *IATBatchHeader) ForeignExchangeIndicatorField() string { + return iatBh.alphaField(iatBh.ForeignExchangeIndicator, 2) +} + +// ForeignExchangeReferenceIndicatorField gets the ForeignExchangeReferenceIndicator +func (iatBh *IATBatchHeader) ForeignExchangeReferenceIndicatorField() string { + return iatBh.numericField(iatBh.ForeignExchangeReferenceIndicator, 1) +} + +// ForeignExchangeReferenceField gets the ForeignExchangeReference left padded +func (iatBh *IATBatchHeader) ForeignExchangeReferenceField() string { + if iatBh.ForeignExchangeReferenceIndicator == 3 { + //blank space + return " " + } + return iatBh.alphaField(iatBh.ForeignExchangeReference, 15) +} + +// ISODestinationCountryCodeField gets the ISODestinationCountryCode +func (iatBh *IATBatchHeader) ISODestinationCountryCodeField() string { + return iatBh.alphaField(iatBh.ISODestinationCountryCode, 2) +} + +// OriginatorIdentificationField gets the OriginatorIdentification left padded +func (iatBh *IATBatchHeader) OriginatorIdentificationField() string { + return iatBh.alphaField(iatBh.OriginatorIdentification, 10) +} + +// CompanyEntryDescriptionField gets the CompanyEntryDescription left padded +func (iatBh *IATBatchHeader) CompanyEntryDescriptionField() string { + return iatBh.alphaField(iatBh.CompanyEntryDescription, 10) +} + +// ISOOriginatingCurrencyCodeField gets the ISOOriginatingCurrencyCode +func (iatBh *IATBatchHeader) ISOOriginatingCurrencyCodeField() string { + return iatBh.alphaField(iatBh.ISOOriginatingCurrencyCode, 3) +} + +// ISODestinationCurrencyCodeField gets the ISODestinationCurrencyCode +func (iatBh *IATBatchHeader) ISODestinationCurrencyCodeField() string { + return iatBh.alphaField(iatBh.ISODestinationCurrencyCode, 3) +} + +// EffectiveEntryDateField get the EffectiveEntryDate in YYMMDD format +func (iatBh *IATBatchHeader) EffectiveEntryDateField() string { + return iatBh.stringField(iatBh.EffectiveEntryDate, 6) // YYMMDD +} + +// ODFIIdentificationField get the odfi number zero padded +func (iatBh *IATBatchHeader) ODFIIdentificationField() string { + return iatBh.stringField(iatBh.ODFIIdentification, 8) +} + +// BatchNumberField get the batch number zero padded +func (iatBh *IATBatchHeader) BatchNumberField() string { + return iatBh.numericField(iatBh.BatchNumber, 7) +} + +// SettlementDateField gets the SettlementDate +func (iatBh *IATBatchHeader) SettlementDateField() string { + return iatBh.alphaField(iatBh.SettlementDate, 3) +} diff --git a/iatBatchHeader_test.go b/iatBatchHeader_test.go new file mode 100644 index 000000000..bdca78b01 --- /dev/null +++ b/iatBatchHeader_test.go @@ -0,0 +1,745 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strings" + "testing" + + "github.com/moov-io/base" +) + +// mockIATBatchHeaderFF creates a IAT BatchHeader that is Fixed-Fixed +func mockIATBatchHeaderFF() *IATBatchHeader { + bh := NewIATBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.ForeignExchangeIndicator = "FF" + bh.ForeignExchangeReferenceIndicator = 3 + bh.ISODestinationCountryCode = "US" + bh.OriginatorIdentification = "123456789" + bh.StandardEntryClassCode = IAT + bh.CompanyEntryDescription = "TRADEPAYMT" + bh.ISOOriginatingCurrencyCode = "CAD" + bh.ISODestinationCurrencyCode = "USD" + bh.ODFIIdentification = "23138010" + return bh +} + +// mockIATBatchReturnHeaderFF creates a IAT Return BatchHeader that is Fixed-Fixed +func mockIATReturnBatchHeaderFF() *IATBatchHeader { + bh := NewIATBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.ForeignExchangeIndicator = "FF" + bh.ForeignExchangeReferenceIndicator = 3 + bh.ISODestinationCountryCode = "US" + bh.OriginatorIdentification = "123456789" + bh.StandardEntryClassCode = IAT + bh.CompanyEntryDescription = "TRADEPAYMT" + bh.ISOOriginatingCurrencyCode = "CAD" + bh.ISODestinationCurrencyCode = "USD" + bh.ODFIIdentification = "12104288" + return bh +} + +// mockIATNOCBatchHeaderFF creates a IAT Return BatchHeader that is Fixed-Fixed +func mockIATNOCBatchHeaderFF() *IATBatchHeader { + bh := NewIATBatchHeader() + bh.ServiceClassCode = CreditsOnly + bh.IATIndicator = "IATCOR" + bh.ForeignExchangeIndicator = "FF" + bh.ForeignExchangeReferenceIndicator = 3 + bh.ISODestinationCountryCode = "US" + bh.OriginatorIdentification = "123456789" + bh.StandardEntryClassCode = COR + bh.CompanyEntryDescription = "TRADEPAYMT" + bh.ISOOriginatingCurrencyCode = "CAD" + bh.ISODestinationCurrencyCode = "USD" + bh.ODFIIdentification = "12104288" + return bh +} + +// testMockIATBatchHeaderFF creates a IAT BatchHeader Fixed-Fixed +func testMockIATBatchHeaderFF(t testing.TB) { + bh := mockIATBatchHeaderFF() + if err := bh.Validate(); err != nil { + t.Error("mockIATBatchHeaderFF does not validate and will break other tests: ", err) + } + if bh.ServiceClassCode != CreditsOnly { + t.Error("ServiceClassCode dependent default value has changed") + } + if bh.ForeignExchangeIndicator != "FF" { + t.Error("ForeignExchangeIndicator does not validate and will break other tests") + } + if bh.ForeignExchangeReferenceIndicator != 3 { + t.Error("ForeignExchangeReferenceIndicator does not validate and will break other tests") + } + if bh.ISODestinationCountryCode != "US" { + t.Error("DestinationCountryCode dependent default value has changed") + } + if bh.OriginatorIdentification != "123456789" { + t.Error("OriginatorIdentification dependent default value has changed") + } + if bh.StandardEntryClassCode != IAT { + t.Error("StandardEntryClassCode dependent default value has changed") + } + if bh.CompanyEntryDescription != "TRADEPAYMT" { + t.Error("CompanyEntryDescription dependent default value has changed") + } + if bh.ISOOriginatingCurrencyCode != "CAD" { + t.Error("ISOOriginatingCurrencyCode dependent default value has changed") + } + if bh.ISODestinationCurrencyCode != "USD" { + t.Error("ISODestinationCurrencyCode dependent default value has changed") + } + if bh.ODFIIdentification != "23138010" { + t.Error("ODFIIdentification dependent default value has changed") + } +} + +// TestMockIATBatchHeaderFF tests creating a IAT BatchHeader Fixed-Fixed +func TestMockIATBatchHeaderFF(t *testing.T) { + testMockIATBatchHeaderFF(t) +} + +// BenchmarkMockIATBatchHeaderFF benchmarks creating a IAT BatchHeader Fixed-Fixed +func BenchmarkMockIATBatchHeaderFF(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testMockIATBatchHeaderFF(b) + } +} + +// testParseIATBatchHeader parses a known IAT BatchHeader record string +func testParseIATBatchHeader(t testing.TB) { + var line = "5220 FF3 US123456789 IATTRADEPAYMTCADUSD180621 1231380100000001" + r := NewReader(strings.NewReader(line)) + r.line = line + if err := r.parseIATBatchHeader(); err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.IATCurrentBatch.GetHeader() + + if record.ServiceClassCode != CreditsOnly { + t.Errorf("ServiceClassCode Expected '220' got: %v", record.ServiceClassCode) + } + if record.IATIndicator != "" { + t.Errorf("IATIndicator Expected '' got: %v", record.IATIndicator) + } + if record.ForeignExchangeIndicator != "FF" { + t.Errorf("ForeignExchangeIndicator Expected ' ' got: %v", + record.ForeignExchangeIndicator) + } + if record.ForeignExchangeReferenceIndicator != 3 { + t.Errorf("ForeignExchangeReferenceIndicator Expected ' ' got: %v", + record.ForeignExchangeReferenceIndicator) + } + if record.ForeignExchangeReferenceField() != " " { + t.Errorf("ForeignExchangeReference Expected ' ' got: %v", + record.ForeignExchangeReference) + } + if record.StandardEntryClassCode != IAT { + t.Errorf("StandardEntryClassCode Expected 'PPD' got: %v", record.StandardEntryClassCode) + } + if record.CompanyEntryDescription != "TRADEPAYMT" { + t.Errorf("CompanyEntryDescription Expected 'TRADEPAYMT' got: %v", record.CompanyEntryDescriptionField()) + } + + if record.EffectiveEntryDateField() != "180621" { + t.Errorf("EffectiveEntryDate Expected '180621' got: %v", record.EffectiveEntryDateField()) + } + if record.SettlementDate != " " { + t.Errorf("SettlementDate Expected ' ' got: %v", record.SettlementDate) + } + if record.OriginatorStatusCode != 1 { + t.Errorf("OriginatorStatusCode Expected 1 got: %v", record.OriginatorStatusCode) + } + if record.ODFIIdentification != "23138010" { + t.Errorf("OdfiIdentification Expected '23138010' got: %v", record.ODFIIdentificationField()) + } + if record.BatchNumberField() != "0000001" { + t.Errorf("BatchNumber Expected '0000001' got: %v", record.BatchNumberField()) + } +} + +// TestParseIATBatchHeader tests parsing a known IAT BatchHeader record string +func TestParseIATBatchHeader(t *testing.T) { + testParseIATBatchHeader(t) +} + +// BenchmarkParseBatchHeader benchmarks parsing a known IAT BatchHeader record string +func BenchmarkParseIATBatchHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testParseIATBatchHeader(b) + } +} + +// testIATBHString validates that a known parsed IAT Batch Header +// can be return to a string of the same value +func testIATBHString(t testing.TB) { + var line = "5220 FF3 US123456789 IATTRADEPAYMTCADUSD180621 1231380100000001" + r := NewReader(strings.NewReader(line)) + r.line = line + if err := r.parseIATBatchHeader(); err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.IATCurrentBatch.GetHeader() + + if v := record.String(); v != line { + t.Errorf("Strings do not match:\n v=%q\nline=%q", v, line) // vertically aligned + } +} + +// TestIATBHString tests validating that a known parsed IAT BatchHeader +// can be return to a string of the same value +func TestIATBHString(t *testing.T) { + testIATBHString(t) +} + +// BenchmarkIATBHString benchmarks validating that a known parsed IAT BatchHeader +// can be return to a string of the same value +func BenchmarkIATBHString(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBHString(b) + } +} + +// testIATBHFVString validates that a known parsed IAT Batch Header +// can be return to a string of the same value +func testIATBHFVString(t testing.TB) { + var line = "5220 FV2123456789012345US123456789 IATTRADEPAYMTCADUSD180621 1231380100000001" + r := NewReader(strings.NewReader(line)) + r.line = line + if err := r.parseIATBatchHeader(); err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.IATCurrentBatch.GetHeader() + + if v := record.String(); v != line { + t.Errorf("Strings do not match:\n v=%q\nline=%q", v, line) + } +} + +// TestIATBHFVString tests validating that a known parsed IAT BatchHeader +// can be return to a string of the same value +func TestIATBHFVString(t *testing.T) { + testIATBHFVString(t) +} + +// BenchmarkIATBHFVString benchmarks validating that a known parsed IAT BatchHeader +// can be return to a string of the same value +func BenchmarkIATBHFVString(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBHFVString(b) + } +} + +// testValidateIATBHServiceClassCode validates error if IATBatchHeader +// ServiceClassCode is invalid +func testValidateIATBHServiceClassCode(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.ServiceClassCode = 999 + err := bh.Validate() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestValidateIATBHServiceClassCode tests validating error if IATBatchHeader +// ServiceClassCode is invalid +func TestValidateIATBHServiceClassCode(t *testing.T) { + testValidateIATBHServiceClassCode(t) +} + +// BenchmarkValidateIATBHServiceClassCode benchmarks validating error if IATBatchHeader +// ServiceClassCode is invalid +func BenchmarkValidateIATBHServiceClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testValidateIATBHServiceClassCode(b) + } +} + +// testValidateIATBHForeignExchangeIndicator validates error if IATBatchHeader +// ForeignExchangeIndicator is invalid +func testValidateIATBHForeignExchangeIndicator(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.ForeignExchangeIndicator = "XY" + err := bh.Validate() + if !base.Match(err, ErrForeignExchangeIndicator) { + t.Errorf("%T: %s", err, err) + } +} + +// TestValidateIATBHForeignExchangeIndicator tests validating error if IATBatchHeader +// ForeignExchangeIndicator is invalid +func TestValidateIATBHForeignExchangeIndicator(t *testing.T) { + testValidateIATBHForeignExchangeIndicator(t) +} + +// BenchmarkValidateIATBHForeignExchangeIndicator benchmarks validating error if IATBatchHeader +// ForeignExchangeIndicator is invalid +func BenchmarkValidateIATBHForeignExchangeIndicator(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testValidateIATBHForeignExchangeIndicator(b) + } +} + +// testValidateIATBHForeignExchangeReferenceIndicator validates error if IATBatchHeader +// ForeignExchangeReferenceIndicator is invalid +func testValidateIATBHForeignExchangeReferenceIndicator(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.ForeignExchangeReferenceIndicator = 5 + err := bh.Validate() + if !base.Match(err, ErrForeignExchangeReferenceIndicator) { + t.Errorf("%T: %s", err, err) + } +} + +// TestValidateIATBHForeignExchangeReferenceIndicator tests validating error if IATBatchHeader +// ForeignExchangeReferenceIndicator is invalid +func TestValidateIATBHForeignExchangeReferenceIndicator(t *testing.T) { + testValidateIATBHForeignExchangeReferenceIndicator(t) +} + +// BenchmarkValidateIATBHForeignExchangeReferenceIndicator benchmarks validating error if IATBatchHeader +// ForeignExchangeReferenceIndicator is invalid +func BenchmarkValidateIATBHForeignExchangeReferenceIndicator(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testValidateIATBHForeignExchangeReferenceIndicator(b) + } +} + +// testValidateIATBHISODestinationCountryCode validates error if IATBatchHeader +// ISODestinationCountryCode is invalid +func testValidateIATBHISODestinationCountryCode(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.ISODestinationCountryCode = "®" + err := bh.Validate() + if !base.Match(err, ErrValidISO3166) { + t.Errorf("%T: %s", err, err) + } +} + +// TestValidateIATBHISODestinationCountryCode tests validating error if IATBatchHeader +// ISODestinationCountryCode is invalid +func TestValidateIATBHISODestinationCountryCode(t *testing.T) { + testValidateIATBHISODestinationCountryCode(t) +} + +// BenchmarkValidateIATBHISODestinationCountryCode benchmarks validating error if IATBatchHeader +// ISODestinationCountryCode is invalid +func BenchmarkValidateIATBHISODestinationCountryCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testValidateIATBHISODestinationCountryCode(b) + } +} + +// testValidateIATBHOriginatorIdentification validates error if IATBatchHeader +// OriginatorIdentification is invalid +func testValidateIATBHOriginatorIdentification(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.OriginatorIdentification = "®" + err := bh.Validate() + // TODO: are we expecting there to be no errors here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestValidateIATBHOriginatorIdentification tests validating error if IATBatchHeader +// OriginatorIdentification is invalid +func TestValidateIATBHOriginatorIdentification(t *testing.T) { + testValidateIATBHOriginatorIdentification(t) +} + +// BenchmarkValidateIATBHOriginatorIdentification benchmarks validating error if IATBatchHeader +// OriginatorIdentification is invalid +func BenchmarkValidateIATBHOriginatorIdentification(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testValidateIATBHOriginatorIdentification(b) + } +} + +// testValidateIATBHStandardEntryClassCode validates error if IATBatchHeader +// StandardEntryClassCode is invalid +func testValidateIATBHStandardEntryClassCode(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.StandardEntryClassCode = "ABC" + err := bh.Validate() + if !base.Match(err, ErrSECCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestValidateIATBHStandardEntryClassCode tests validating error if IATBatchHeader +// StandardEntryClassCode is invalid +func TestValidateIATBHStandardEntryClassCode(t *testing.T) { + testValidateIATBHStandardEntryClassCode(t) +} + +// BenchmarkValidateIATBHStandardEntryClassCode benchmarks validating error if IATBatchHeader +// StandardEntryClassCode is invalid +func BenchmarkValidateIATBHStandardEntryClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testValidateIATBHStandardEntryClassCode(b) + } +} + +// testValidateIATBHCompanyEntryDescription validates error if IATBatchHeader +// CompanyEntryDescription is invalid +func testValidateIATBHCompanyEntryDescription(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.CompanyEntryDescription = "®" + err := bh.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestValidateIATBHCompanyEntryDescription tests validating error if IATBatchHeader +// CompanyEntryDescription is invalid +func TestValidateIATBHCompanyEntryDescription(t *testing.T) { + testValidateIATBHCompanyEntryDescription(t) +} + +// BenchmarkValidateIATBHCompanyEntryDescription benchmarks validating error if IATBatchHeader +// CompanyEntryDescription is invalid +func BenchmarkValidateIATBHCompanyEntryDescription(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testValidateIATBHCompanyEntryDescription(b) + } +} + +// testValidateIATBHISOOriginatingCurrencyCode validates error if IATBatchHeader +// ISOOriginatingCurrencyCode is invalid +func testValidateIATBHISOOriginatingCurrencyCode(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.ISOOriginatingCurrencyCode = "®" + err := bh.Validate() + if !base.Match(err, ErrValidISO4217) { + t.Errorf("%T: %s", err, err) + } +} + +// TestValidateIATBHISOOriginatingCurrencyCode tests validating error if IATBatchHeader +// ISOOriginatingCurrencyCode is invalid +func TestValidateIATBHISOOriginatingCurrencyCode(t *testing.T) { + testValidateIATBHISOOriginatingCurrencyCode(t) +} + +// BenchmarkValidateIATBHISOOriginatingCurrencyCode benchmarks validating error if IATBatchHeader +// ISOOriginatingCurrencyCode is invalid +func BenchmarkValidateIATBHISOOriginatingCurrencyCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testValidateIATBHISOOriginatingCurrencyCode(b) + } +} + +// testValidateIATBHISODestinationCurrencyCode validates error if IATBatchHeader +// ISODestinationCurrencyCode is invalid +func testValidateIATBHISODestinationCurrencyCode(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.ISODestinationCurrencyCode = "®" + err := bh.Validate() + if !base.Match(err, ErrValidISO4217) { + t.Errorf("%T: %s", err, err) + } +} + +// TestValidateIATBHISODestinationCurrencyCode tests validating error if IATBatchHeader +// ISODestinationCurrencyCode is invalid +func TestValidateIATBHISODestinationCurrencyCode(t *testing.T) { + testValidateIATBHISODestinationCurrencyCode(t) +} + +// BenchmarkValidateIATBHISODestinationCurrencyCode benchmarks validating error if IATBatchHeader +// ISODestinationCurrencyCode is invalid +func BenchmarkValidateIATBHISODestinationCurrencyCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testValidateIATBHISODestinationCurrencyCode(b) + } +} + +// testValidateIATBHOriginatorStatusCode validates error if IATBatchHeader +// OriginatorStatusCode is invalid +func testValidateIATBHOriginatorStatusCode(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.OriginatorStatusCode = 7 + err := bh.Validate() + if !base.Match(err, ErrOrigStatusCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestValidateIATBHOriginatorStatusCode tests validating error if IATBatchHeader +// OriginatorStatusCode is invalid +func TestValidateIATBHOriginatorStatusCode(t *testing.T) { + testValidateIATBHOriginatorStatusCode(t) +} + +// BenchmarkValidateIATBHOriginatorStatusCode benchmarks validating error if IATBatchHeader +// OriginatorStatusCode is invalid +func BenchmarkValidateIATBHOriginatorStatusCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testValidateIATBHOriginatorStatusCode(b) + } +} + +//FieldInclusion + +// testIATBHServiceClassCode validates IATBatchHeader ServiceClassCode fieldInclusion +func testIATBHServiceClassCode(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.ServiceClassCode = 0 + err := bh.Validate() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBHServiceClassCode tests validating IATBatchHeader ServiceClassCode fieldInclusion +func TestIATBHServiceClassCode(t *testing.T) { + testIATBHServiceClassCode(t) +} + +// BenchmarkIATBHServiceClassCode benchmarks validating IATBatchHeader ServiceClassCode fieldInclusion +func BenchmarkIATBHServiceClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBHServiceClassCode(b) + } +} + +// testIATBHForeignExchangeIndicator validates IATBatchHeader ForeignExchangeIndicator fieldInclusion +func testIATBHForeignExchangeIndicator(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.ForeignExchangeIndicator = "" + err := bh.Validate() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBHForeignExchangeIndicator tests validating IATBatchHeader ForeignExchangeIndicator fieldInclusion +func TestIATBHForeignExchangeIndicator(t *testing.T) { + testIATBHForeignExchangeIndicator(t) +} + +// BenchmarkIATBHForeignExchangeIndicator benchmarks validating IATBatchHeader ForeignExchangeIndicator fieldInclusion +func BenchmarkIATBHForeignExchangeIndicator(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBHForeignExchangeIndicator(b) + } +} + +// testIATBHForeignExchangeReferenceIndicator validates IATBatchHeader ForeignExchangeReferenceIndicator fieldInclusion +func testIATBHForeignExchangeReferenceIndicator(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.ForeignExchangeReferenceIndicator = 0 + err := bh.Validate() + if !base.Match(err, ErrFieldRequired) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBHForeignExchangeReferenceIndicator tests validating IATBatchHeader ForeignExchangeReferenceIndicator fieldInclusion +func TestIATBHForeignExchangeReferenceIndicator(t *testing.T) { + testIATBHForeignExchangeReferenceIndicator(t) +} + +// BenchmarkIATBHForeignExchangeReferenceIndicator benchmarks validating IATBatchHeader ForeignExchangeReferenceIndicator fieldInclusion +func BenchmarkIATBHForeignExchangeReferenceIndicator(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBHForeignExchangeReferenceIndicator(b) + } +} + +// testIATBHISODestinationCountryCode validates IATBatchHeader ISODestinationCountryCode fieldInclusion +func testIATBHISODestinationCountryCode(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.ISODestinationCountryCode = "" + err := bh.Validate() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBHISODestinationCountryCode tests validating IATBatchHeader ISODestinationCountryCode fieldInclusion +func TestIATBHISODestinationCountryCode(t *testing.T) { + testIATBHISODestinationCountryCode(t) +} + +// BenchmarkIATBHISODestinationCountryCode benchmarks validating IATBatchHeader ISODestinationCountryCode fieldInclusion +func BenchmarkIATBHISODestinationCountryCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBHISODestinationCountryCode(b) + } +} + +// testIATBHOriginatorIdentification validates IATBatchHeader OriginatorIdentification fieldInclusion +func testIATBHOriginatorIdentification(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.OriginatorIdentification = "" + err := bh.Validate() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBHOriginatorIdentification tests validating IATBatchHeader OriginatorIdentification fieldInclusion +func TestIATBHOriginatorIdentification(t *testing.T) { + testIATBHOriginatorIdentification(t) +} + +// BenchmarkIATBHOriginatorIdentification benchmarks validating IATBatchHeader OriginatorIdentification fieldInclusion +func BenchmarkIATBHOriginatorIdentification(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBHOriginatorIdentification(b) + } +} + +// testIATBHStandardEntryClassCode validates IATBatchHeader StandardEntryClassCode fieldInclusion +func testIATBHStandardEntryClassCode(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.StandardEntryClassCode = "" + err := bh.Validate() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBHStandardEntryClassCode tests validating IATBatchHeader StandardEntryClassCode fieldInclusion +func TestIATBHStandardEntryClassCode(t *testing.T) { + testIATBHStandardEntryClassCode(t) +} + +// BenchmarkIATBHStandardEntryClassCode benchmarks validating IATBatchHeader StandardEntryClassCode fieldInclusion +func BenchmarkIATBHStandardEntryClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBHStandardEntryClassCode(b) + } +} + +// testIATBHCompanyEntryDescription validates IATBatchHeader CompanyEntryDescription fieldInclusion +func testIATBHCompanyEntryDescription(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.CompanyEntryDescription = "" + err := bh.Validate() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBHCompanyEntryDescription tests validating IATBatchHeader CompanyEntryDescription fieldInclusion +func TestIATBHCompanyEntryDescription(t *testing.T) { + testIATBHCompanyEntryDescription(t) +} + +// BenchmarkIATBHCompanyEntryDescription benchmarks validating IATBatchHeader CompanyEntryDescription fieldInclusion +func BenchmarkIATBHCompanyEntryDescription(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBHCompanyEntryDescription(b) + } +} + +// testIATBHISOOriginatingCurrencyCode validates IATBatchHeader ISOOriginatingCurrencyCode fieldInclusion +func testIATBHISOOriginatingCurrencyCode(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.ISOOriginatingCurrencyCode = "" + err := bh.Validate() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBHISOOriginatingCurrencyCode tests validating IATBatchHeader ISOOriginatingCurrencyCode fieldInclusion +func TestIATBHISOOriginatingCurrencyCode(t *testing.T) { + testIATBHISOOriginatingCurrencyCode(t) +} + +// BenchmarkIATBHISOOriginatingCurrencyCode benchmarks validating IATBatchHeader ISOOriginatingCurrencyCode fieldInclusion +func BenchmarkIATBHISOOriginatingCurrencyCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBHISOOriginatingCurrencyCode(b) + } +} + +// testIATBHISODestinationCurrencyCode validates IATBatchHeader ISODestinationCurrencyCode fieldInclusion +func testIATBHISODestinationCurrencyCode(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.ISODestinationCurrencyCode = "" + err := bh.Validate() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBHISODestinationCurrencyCode tests validating IATBatchHeader ISODestinationCurrencyCode fieldInclusion +func TestIATBHISODestinationCurrencyCode(t *testing.T) { + testIATBHISODestinationCurrencyCode(t) +} + +// BenchmarkIATBHISODestinationCurrencyCode benchmarks validating IATBatchHeader ISODestinationCurrencyCode fieldInclusion +func BenchmarkIATBHISODestinationCurrencyCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBHISODestinationCurrencyCode(b) + } +} + +// testIATBHODFIIdentification validates IATBatchHeader ODFIIdentification fieldInclusion +func testIATBHODFIIdentification(t testing.TB) { + bh := mockIATBatchHeaderFF() + bh.ODFIIdentification = "" + err := bh.Validate() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBHODFIIdentification tests validating IATBatchHeader ODFIIdentification fieldInclusion +func TestIATBHODFIIdentification(t *testing.T) { + testIATBHODFIIdentification(t) +} + +// BenchmarkIATBHODFIIdentification benchmarks validating IATBatchHeader ODFIIdentification fieldInclusion +func BenchmarkIATBHODFIIdentification(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBHODFIIdentification(b) + } +} diff --git a/iatBatch_test.go b/iatBatch_test.go new file mode 100644 index 000000000..5bdb1c2aa --- /dev/null +++ b/iatBatch_test.go @@ -0,0 +1,1996 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "encoding/json" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/moov-io/base" + "github.com/stretchr/testify/require" +) + +// mockIATBatch +func mockIATBatch(t testing.TB) IATBatch { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATBatchHeaderFF()) + mockBatch.AddEntry(mockIATEntryDetailWithAddendas()) + if err := mockBatch.build(); err != nil { + t.Fatal(err) + } + return mockBatch +} + +func mockIATEntryDetailWithAddendas() *IATEntryDetail { + ed := mockIATEntryDetail() + ed.Addenda10 = mockAddenda10() + ed.Addenda11 = mockAddenda11() + ed.Addenda12 = mockAddenda12() + ed.Addenda13 = mockAddenda13() + ed.Addenda14 = mockAddenda14() + ed.Addenda15 = mockAddenda15() + ed.Addenda16 = mockAddenda16() + return ed +} + +// mockIATBatchManyEntries +func mockIATBatchManyEntries(t testing.TB) IATBatch { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATBatchHeaderFF()) + + mockBatch.AddEntry(mockIATEntryDetail()) + + mockBatch.Entries[0].Addenda10 = mockAddenda10() + mockBatch.Entries[0].Addenda11 = mockAddenda11() + mockBatch.Entries[0].Addenda12 = mockAddenda12() + mockBatch.Entries[0].Addenda13 = mockAddenda13() + mockBatch.Entries[0].Addenda14 = mockAddenda14() + mockBatch.Entries[0].Addenda15 = mockAddenda15() + mockBatch.Entries[0].Addenda16 = mockAddenda16() + mockBatch.Entries[0].AddAddenda17(mockAddenda17()) + mockBatch.Entries[0].AddAddenda17(mockAddenda17B()) + mockBatch.Entries[0].AddAddenda18(mockAddenda18()) + mockBatch.Entries[0].AddAddenda18(mockAddenda18B()) + mockBatch.Entries[0].AddAddenda18(mockAddenda18C()) + mockBatch.Entries[0].AddAddenda18(mockAddenda18D()) + mockBatch.Entries[0].AddAddenda18(mockAddenda18E()) + + mockBatch.AddEntry(mockIATEntryDetail2()) + + mockBatch.Entries[1].Addenda10 = mockAddenda10() + mockBatch.Entries[1].Addenda11 = mockAddenda11() + mockBatch.Entries[1].Addenda12 = mockAddenda12() + mockBatch.Entries[1].Addenda13 = mockAddenda13() + mockBatch.Entries[1].Addenda14 = mockAddenda14() + mockBatch.Entries[1].Addenda15 = mockAddenda15() + mockBatch.Entries[1].Addenda16 = mockAddenda16() + mockBatch.Entries[1].AddAddenda17(mockAddenda17()) + mockBatch.Entries[1].AddAddenda17(mockAddenda17B()) + mockBatch.Entries[1].AddAddenda18(mockAddenda18()) + mockBatch.Entries[1].AddAddenda18(mockAddenda18B()) + mockBatch.Entries[1].AddAddenda18(mockAddenda18C()) + mockBatch.Entries[1].AddAddenda18(mockAddenda18D()) + mockBatch.Entries[1].AddAddenda18(mockAddenda18E()) + + if err := mockBatch.build(); err != nil { + t.Fatal(err) + } + return mockBatch +} + +// mockIATBatch +func mockInvalidIATBatch(t testing.TB) IATBatch { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATBatchHeaderFF()) + mockBatch.AddEntry(mockIATEntryDetail()) + mockBatch.Entries[0].Addenda10 = mockAddenda10() + mockBatch.Entries[0].Addenda11 = mockAddenda11() + mockBatch.Entries[0].Addenda12 = mockAddenda12() + mockBatch.Entries[0].Addenda13 = mockAddenda13() + mockBatch.Entries[0].Addenda14 = mockAddenda14() + mockBatch.Entries[0].Addenda15 = mockAddenda15() + mockBatch.Entries[0].Addenda16 = mockAddenda16() + mockBatch.Entries[0].AddAddenda17(mockInvalidAddenda17()) + if err := mockBatch.build(); err != nil { + t.Fatal(err) + } + return mockBatch +} + +func mockInvalidAddenda17() *Addenda17 { + addenda17 := NewAddenda17() + addenda17.PaymentRelatedInformation = "Transfer of money from one country to another" + addenda17.TypeCode = "02" + addenda17.SequenceNumber = 2 + addenda17.EntryDetailSequenceNumber = 0000002 + return addenda17 +} + +func mockIATAddenda99() *Addenda99 { + addenda99 := NewAddenda99() + addenda99.ReturnCode = "R07" + addenda99.OriginalTrace = "231380100000001" + addenda99.OriginalDFI = "12104288" + addenda99.IATPaymentAmount("0000100000") + addenda99.IATAddendaInformation("Authorization Revoked") + return addenda99 +} + +func mockIATAddenda98() *Addenda98 { + addenda98 := NewAddenda98() + addenda98.ChangeCode = "C01" + addenda98.OriginalTrace = "231380100000001" + addenda98.OriginalDFI = "12104288" + addenda98.CorrectedData = "89722-C3" + addenda98.TraceNumber = "121042880000001" + return addenda98 +} + +// TestMockIATBatch validates mockIATBatch +func TestMockIATBatch(t *testing.T) { + iatBatch := mockIATBatch(t) + if err := iatBatch.verify(); err != nil { + t.Error("mockIATBatch does not validate and will break other tests") + } +} + +// TestIATBatch__UnmarshalJSON reads an example File (with IAT Batches) and attempts to unmarshal it as JSON +func TestIATBatch__UnmarshalJSON(t *testing.T) { + // Make sure we don't panic with nil in the mix + var batch *IATBatch + if err := batch.UnmarshalJSON(nil); err != nil && !strings.Contains(err.Error(), "unexpected end of JSON input") { + t.Fatal(err) + } + + // Read file, convert to JSON + fd, err := os.Open(filepath.Join("test", "ach-iat-read", "iat-credit.ach")) + if err != nil { + t.Fatal(err) + } + f, err := NewReader(fd).Read() + if err != nil { + t.Fatal(err) + } + bs, err := json.Marshal(f) + if err != nil { + t.Fatal(err) + } + + // Read as JSON + file, err := FileFromJSON(bs) + if err != nil { + t.Fatal(err) + } + if file == nil { + t.Error("file == nil") + } +} + +// testIATBatchAddenda10Error validates IATBatch returns an error if Addenda10 is not included +func testIATBatchAddenda10Error(t testing.TB) { + iatBatch := mockIATBatch(t) + iatBatch.GetEntries()[0].Addenda10 = nil + err := iatBatch.verify() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddenda10Error tests validating IATBatch returns an error +// if Addenda10 is not included +func TestIATBatchAddenda10Error(t *testing.T) { + testIATBatchAddenda10Error(t) +} + +// BenchmarkIATBatchAddenda10Error benchmarks validating IATBatch returns an error +// if Addenda10 is not included +func BenchmarkIATBatchAddenda10Error(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchAddenda10Error(b) + } +} + +// testIATBatchAddenda11Error validates IATBatch returns an error if Addenda11 is not included +func testIATBatchAddenda11Error(t testing.TB) { + iatBatch := mockIATBatch(t) + iatBatch.GetEntries()[0].Addenda11 = nil + err := iatBatch.verify() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddenda11Error tests validating IATBatch returns an error +// if Addenda11 is not included +func TestIATBatchAddenda11Error(t *testing.T) { + testIATBatchAddenda11Error(t) +} + +// BenchmarkIATBatchAddenda11Error benchmarks validating IATBatch returns an error +// if Addenda11 is not included +func BenchmarkIATBatchAddenda11Error(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchAddenda11Error(b) + } +} + +// testIATBatchAddenda12Error validates IATBatch returns an error if Addenda12 is not included +func testIATBatchAddenda12Error(t testing.TB) { + iatBatch := mockIATBatch(t) + iatBatch.GetEntries()[0].Addenda12 = nil + err := iatBatch.verify() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddenda12Error tests validating IATBatch returns an error +// if Addenda12 is not included +func TestIATBatchAddenda12Error(t *testing.T) { + testIATBatchAddenda12Error(t) +} + +// BenchmarkIATBatchAddenda12Error benchmarks validating IATBatch returns an error +// if Addenda12 is not included +func BenchmarkIATBatchAddenda12Error(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchAddenda12Error(b) + } +} + +// testIATBatchAddenda13Error validates IATBatch returns an error if Addenda13 is not included +func testIATBatchAddenda13Error(t testing.TB) { + iatBatch := mockIATBatch(t) + iatBatch.GetEntries()[0].Addenda13 = nil + err := iatBatch.verify() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddenda13Error tests validating IATBatch returns an error +// if Addenda13 is not included +func TestIATBatchAddenda13Error(t *testing.T) { + testIATBatchAddenda13Error(t) +} + +// BenchmarkIATBatchAddenda13Error benchmarks validating IATBatch returns an error +// if Addenda13 is not included +func BenchmarkIATBatchAddenda13Error(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchAddenda13Error(b) + } +} + +// testIATBatchAddenda14Error validates IATBatch returns an error if Addenda14 is not included +func testIATBatchAddenda14Error(t testing.TB) { + iatBatch := mockIATBatch(t) + iatBatch.GetEntries()[0].Addenda14 = nil + err := iatBatch.verify() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddenda14Error tests validating IATBatch returns an error +// if Addenda14 is not included +func TestIATBatchAddenda14Error(t *testing.T) { + testIATBatchAddenda14Error(t) +} + +// BenchmarkIATBatchAddenda14Error benchmarks validating IATBatch returns an error +// if Addenda14 is not included +func BenchmarkIATBatchAddenda14Error(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchAddenda14Error(b) + } +} + +// testIATBatchAddenda15Error validates IATBatch returns an error if Addenda15 is not included +func testIATBatchAddenda15Error(t testing.TB) { + iatBatch := mockIATBatch(t) + iatBatch.GetEntries()[0].Addenda15 = nil + err := iatBatch.verify() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddenda15Error tests validating IATBatch returns an error +// if Addenda15 is not included +func TestIATBatchAddenda15Error(t *testing.T) { + testIATBatchAddenda15Error(t) +} + +// BenchmarkIATBatchAddenda15Error benchmarks validating IATBatch returns an error +// if Addenda15 is not included +func BenchmarkIATBatchAddenda15Error(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchAddenda15Error(b) + } +} + +// testIATBatchAddenda16Error validates IATBatch returns an error if Addenda16 is not included +func testIATBatchAddenda16Error(t testing.TB) { + iatBatch := mockIATBatch(t) + iatBatch.GetEntries()[0].Addenda16 = nil + err := iatBatch.verify() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddenda16Error tests validating IATBatch returns an error +// if Addenda16 is not included +func TestIATBatchAddenda16Error(t *testing.T) { + testIATBatchAddenda16Error(t) +} + +// BenchmarkIATBatchAddenda16Error benchmarks validating IATBatch returns an error +// if Addenda16 is not included +func BenchmarkIATBatchAddenda16Error(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchAddenda16Error(b) + } +} + +// testAddenda10EntryDetailSequenceNumber validates IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func testAddenda10EntryDetailSequenceNumber(t testing.TB) { + iatBatch := mockIATBatch(t) + iatBatch.GetEntries()[0].Addenda10.EntryDetailSequenceNumber = 00000005 + err := iatBatch.verify() + if !base.Match(err, NewErrBatchAddendaTraceNumber("0000005", "0000001")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda10EntryDetailSequenceNumber tests validating IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func TestAddenda10EntryDetailSequenceNumber(t *testing.T) { + testAddenda10EntryDetailSequenceNumber(t) +} + +// BenchmarkAddenda10EntryDetailSequenceNumber benchmarks validating IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func BenchmarkAddenda10EntryDetailSequenceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda10EntryDetailSequenceNumber(b) + } +} + +// testAddenda11EntryDetailSequenceNumber validates IATBatch returns an error if EntryDetailSequenceNumber +// is not valid +func testAddenda11EntryDetailSequenceNumber(t testing.TB) { + iatBatch := mockIATBatch(t) + iatBatch.GetEntries()[0].Addenda11.EntryDetailSequenceNumber = 00000005 + err := iatBatch.verify() + if !base.Match(err, NewErrBatchAddendaTraceNumber("0000005", "0000001")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda11EntryDetailSequenceNumber tests validating IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func TestAddenda11EntryDetailSequenceNumber(t *testing.T) { + testAddenda11EntryDetailSequenceNumber(t) +} + +// BenchmarkAddenda11EntryDetailSequenceNumber benchmarks validating IATBatch returns an error +// if EntryDetailSequenceNumber is not valid +func BenchmarkAddenda11EntryDetailSequenceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda11EntryDetailSequenceNumber(b) + } +} + +// testAddenda12EntryDetailSequenceNumber validates IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func testAddenda12EntryDetailSequenceNumber(t testing.TB) { + iatBatch := mockIATBatch(t) + iatBatch.GetEntries()[0].Addenda12.EntryDetailSequenceNumber = 00000005 + err := iatBatch.verify() + if !base.Match(err, NewErrBatchAddendaTraceNumber("0000005", "0000001")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda12EntryDetailSequenceNumber tests validating IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func TestAddenda12EntryDetailSequenceNumber(t *testing.T) { + testAddenda12EntryDetailSequenceNumber(t) +} + +// BenchmarkAddenda12EntryDetailSequenceNumber benchmarks validating IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func BenchmarkAddenda12EntryDetailSequenceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda12EntryDetailSequenceNumber(b) + } +} + +// testAddenda13EntryDetailSequenceNumber validates IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func testAddenda13EntryDetailSequenceNumber(t testing.TB) { + iatBatch := mockIATBatch(t) + iatBatch.GetEntries()[0].Addenda13.EntryDetailSequenceNumber = 00000005 + err := iatBatch.verify() + if !base.Match(err, NewErrBatchAddendaTraceNumber("0000005", "0000001")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda13EntryDetailSequenceNumber tests validating IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func TestAddenda13EntryDetailSequenceNumber(t *testing.T) { + testAddenda13EntryDetailSequenceNumber(t) +} + +// BenchmarkAddenda13EntryDetailSequenceNumber benchmarks validating IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func BenchmarkAddenda13EntryDetailSequenceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda13EntryDetailSequenceNumber(b) + } +} + +// testAddenda14EntryDetailSequenceNumber validates IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func testAddenda14EntryDetailSequenceNumber(t testing.TB) { + iatBatch := mockIATBatch(t) + iatBatch.GetEntries()[0].Addenda14.EntryDetailSequenceNumber = 00000005 + err := iatBatch.verify() + if !base.Match(err, NewErrBatchAddendaTraceNumber("0000005", "0000001")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda14EntryDetailSequenceNumber tests validating IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func TestAddenda14EntryDetailSequenceNumber(t *testing.T) { + testAddenda14EntryDetailSequenceNumber(t) +} + +// BenchmarkAddenda14EntryDetailSequenceNumber benchmarks validating IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func BenchmarkAddenda14EntryDetailSequenceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda14EntryDetailSequenceNumber(b) + } +} + +// testAddenda15EntryDetailSequenceNumber validates IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func testAddenda15EntryDetailSequenceNumber(t testing.TB) { + iatBatch := mockIATBatch(t) + iatBatch.GetEntries()[0].Addenda15.EntryDetailSequenceNumber = 00000005 + err := iatBatch.verify() + if !base.Match(err, NewErrBatchAddendaTraceNumber("0000005", "0000001")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda15EntryDetailSequenceNumber tests validating IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func TestAddenda15EntryDetailSequenceNumber(t *testing.T) { + testAddenda15EntryDetailSequenceNumber(t) +} + +// BenchmarkAddenda15EntryDetailSequenceNumber benchmarks validating IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func BenchmarkAddenda15EntryDetailSequenceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda15EntryDetailSequenceNumber(b) + } +} + +// testAddenda16EntryDetailSequenceNumber validates IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func testAddenda16EntryDetailSequenceNumber(t testing.TB) { + iatBatch := mockIATBatch(t) + iatBatch.GetEntries()[0].Addenda16.EntryDetailSequenceNumber = 00000005 + err := iatBatch.verify() + if !base.Match(err, NewErrBatchAddendaTraceNumber("0000005", "0000001")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddenda16EntryDetailSequenceNumber tests validating IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func TestAddenda16EntryDetailSequenceNumber(t *testing.T) { + testAddenda16EntryDetailSequenceNumber(t) +} + +// BenchmarkAddenda16EntryDetailSequenceNumber benchmarks validating IATBatch returns an error if +// EntryDetailSequenceNumber is not valid +func BenchmarkAddenda16EntryDetailSequenceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddenda16EntryDetailSequenceNumber(b) + } +} + +// testIATBatchNumberMismatch validates BatchNumber mismatch +func testIATBatchNumberMismatch(t testing.TB) { + mockBatch := mockIATBatch(t) + mockBatch.GetControl().BatchNumber = 2 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchHeaderControlEquality(1, 2)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchNumberMismatch tests validating BatchNumber mismatch +func TestIATBatchNumberMismatch(t *testing.T) { + testIATBatchNumberMismatch(t) +} + +// BenchmarkIATBatchNumberMismatch benchmarks validating BatchNumber mismatch +func BenchmarkIATBatchNumberMismatch(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchNumberMismatch(b) + } +} + +// testIATServiceClassCodeMismatch validates ServiceClassCode mismatch +func testIATServiceClassCodeMismatch(t testing.TB) { + mockBatch := mockIATBatch(t) + mockBatch.GetControl().ServiceClassCode = DebitsOnly + err := mockBatch.verify() + if !base.Match(err, NewErrBatchHeaderControlEquality(CreditsOnly, DebitsOnly)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATServiceClassCodeMismatch tests validating ServiceClassCode mismatch +func TestServiceClassCodeMismatch(t *testing.T) { + testIATServiceClassCodeMismatch(t) +} + +// BenchmarkIATServiceClassCoderMismatch benchmarks validating ServiceClassCode mismatch +func BenchmarkIATServiceClassCodeMismatch(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATServiceClassCodeMismatch(b) + } +} + +// testIATBatchCreditIsBatchAmount validates credit isBatchAmount +func testIATBatchCreditIsBatchAmount(t testing.TB) { + mockBatch := mockIATBatch(t) + e1 := mockBatch.GetEntries()[0] + e2 := mockIATEntryDetail() + e2.TransactionCode = CheckingCredit + e2.Amount = 5000 + // replace last 2 of TraceNumber + e2.TraceNumber = e1.TraceNumber[:13] + "10" + mockBatch.AddEntry(e2) + mockBatch.Entries[1].Addenda10 = mockAddenda10() + mockBatch.Entries[1].Addenda11 = mockAddenda11() + mockBatch.Entries[1].Addenda12 = mockAddenda12() + mockBatch.Entries[1].Addenda13 = mockAddenda13() + mockBatch.Entries[1].Addenda14 = mockAddenda14() + mockBatch.Entries[1].Addenda15 = mockAddenda15() + mockBatch.Entries[1].Addenda16 = mockAddenda16() + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + mockBatch.GetControl().TotalCreditEntryDollarAmount = 1000 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchCalculatedControlEquality(105000, 1000)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchCreditIsBatchAmount tests validating credit isBatchAmount +func TestIATBatchCreditIsBatchAmount(t *testing.T) { + testIATBatchCreditIsBatchAmount(t) +} + +// BenchmarkIATBatchCreditIsBatchAmount benchmarks validating credit isBatchAmount +func BenchmarkIATBatchCreditIsBatchAmount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchCreditIsBatchAmount(b) + } + +} + +// testIATBatchDebitIsBatchAmount validates debit isBatchAmount +func testIATBatchDebitIsBatchAmount(t testing.TB) { + mockBatch := mockIATBatch(t) + e1 := mockBatch.GetEntries()[0] + e1.TransactionCode = CheckingDebit + e2 := mockIATEntryDetail() + e2.TransactionCode = CheckingDebit + e2.Amount = 5000 + // replace last 2 of TraceNumber + e2.TraceNumber = e1.TraceNumber[:13] + "10" + mockBatch.AddEntry(e2) + mockBatch.Entries[1].Addenda10 = mockAddenda10() + mockBatch.Entries[1].Addenda11 = mockAddenda11() + mockBatch.Entries[1].Addenda12 = mockAddenda12() + mockBatch.Entries[1].Addenda13 = mockAddenda13() + mockBatch.Entries[1].Addenda14 = mockAddenda14() + mockBatch.Entries[1].Addenda15 = mockAddenda15() + mockBatch.Entries[1].Addenda16 = mockAddenda16() + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + mockBatch.GetControl().TotalDebitEntryDollarAmount = 1000 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchCalculatedControlEquality(105000, 1000)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchDebitIsBatchAmount tests validating debit isBatchAmount +func TestIATBatchDebitIsBatchAmount(t *testing.T) { + testIATBatchDebitIsBatchAmount(t) +} + +// BenchmarkIATBatchDebitIsBatchAmount benchmarks validating debit isBatchAmount +func BenchmarkIATBatchDebitIsBatchAmount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchDebitIsBatchAmount(b) + } + +} + +// testIATBatchFieldInclusion validates IATBatch FieldInclusion +func testIATBatchFieldInclusion(t testing.TB) { + mockBatch := mockIATBatch(t) + mockBatch2 := mockIATBatch(t) + mockBatch2.Header.ServiceClassCode = 4 + + err := mockBatch.verify() + // no errors expected + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } + err = mockBatch2.verify() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } + err = mockBatch2.build() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchFieldInclusion tests validating IATBatch FieldInclusion +func TestIATBatchFieldInclusion(t *testing.T) { + testIATBatchFieldInclusion(t) +} + +// BenchmarkIATBatchFieldInclusion benchmarks validating IATBatch FieldInclusion +func BenchmarkIATBatchFieldInclusion(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchFieldInclusion(b) + } + +} + +// testIATBatchBuild validates IATBatch build error +func testIATBatchBuild(t testing.TB) { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATBatchHeaderFF()) + + err := mockBatch.build() + if !base.Match(err, ErrBatchNoEntries) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchBuild tests validating IATBatch build error +func TestIATBatchBuild(t *testing.T) { + testIATBatchBuild(t) +} + +// BenchmarkIATBatchBuild benchmarks validating IATBatch build error +func BenchmarkIATBatchBuild(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchBuild(b) + } + +} + +// testIATODFIIdentificationMismatch validates ODFIIdentification mismatch +func testIATODFIIdentificationMismatch(t testing.TB) { + mockBatch := mockIATBatch(t) + mockBatch.GetControl().ODFIIdentification = "53158020" + err := mockBatch.verify() + if !base.Match(err, NewErrBatchHeaderControlEquality(23138010, 53158020)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATODFIIdentificationMismatch tests validating ODFIIdentification mismatch +func TestODFIIdentificationMismatch(t *testing.T) { + testIATODFIIdentificationMismatch(t) +} + +// BenchmarkIATODFIIdentificationMismatch benchmarks validating ODFIIdentification mismatch +func BenchmarkIATODFIIdentificationMismatch(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATODFIIdentificationMismatch(b) + } +} + +// testIATBatchAddendaRecordIndicator validates IATEntryDetail AddendaRecordIndicator +func testIATBatchAddendaRecordIndicator(t testing.TB) { + mockBatch := mockIATBatch(t) + mockBatch.GetEntries()[0].AddendaRecordIndicator = 2 + err := mockBatch.verify() + if !base.Match(err, ErrIATBatchAddendaIndicator) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddendaRecordIndicator tests validating IATEntryDetail AddendaRecordIndicator +func TestIATBatchAddendaRecordIndicator(t *testing.T) { + testIATBatchAddendaRecordIndicator(t) +} + +// BenchmarkIATBatchAddendaRecordIndicator benchmarks IATEntryDetail AddendaRecordIndicator +func BenchmarkIATBatchAddendaRecordIndicator(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchAddendaRecordIndicator(b) + } +} + +// testIATBatchInvalidTraceNumberODFI validates TraceNumberODFI +func testIATBatchInvalidTraceNumberODFI(t testing.TB) { + mockBatch := mockIATBatch(t) + mockBatch.GetEntries()[0].SetTraceNumber("9928272", 1) + err := mockBatch.verify() + if !base.Match(err, NewErrBatchTraceNumberNotODFI("23138010", "09928272")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchInvalidTraceNumberODFI tests validating TraceNumberODFI +func TestIATBatchInvalidTraceNumberODFI(t *testing.T) { + testIATBatchInvalidTraceNumberODFI(t) +} + +// BenchmarkIATBatchInvalidTraceNumberODFI benchmarks validating TraceNumberODFI +func BenchmarkIATBatchInvalidTraceNumberODFI(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchInvalidTraceNumberODFI(b) + } +} + +// testIATBatchControl validates BatchControl ODFIIdentification +func testIATBatchControl(t testing.TB) { + mockBatch := mockIATBatch(t) + mockBatch.Control.ODFIIdentification = "" + err := mockBatch.verify() + if !base.Match(err, NewErrBatchHeaderControlEquality("23138010", "")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchControl tests validating BatchControl ODFIIdentification +func TestIATBatchControl(t *testing.T) { + testIATBatchControl(t) +} + +// BenchmarkIATBatchControl benchmarks validating BatchControl ODFIIdentification +func BenchmarkIATBatchControl(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchControl(b) + } +} + +// testIATBatchEntryCountEquality validates IATBatch EntryAddendaCount +func testIATBatchEntryCountEquality(t testing.TB) { + mockBatch := mockIATBatch(t) + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + mockBatch.GetControl().EntryAddendaCount = 1 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchCalculatedControlEquality(8, 1)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchEntryCountEquality tests validating IATBatch EntryAddendaCount +func TestIATBatchEntryCountEquality(t *testing.T) { + testIATBatchEntryCountEquality(t) +} + +// BenchmarkIATBatchEntryCountEquality benchmarks validating IATBatch EntryAddendaCount +func BenchmarkIATBatchEntryCountEquality(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchEntryCountEquality(b) + } +} + +// testIATBatchisEntryHash validates IATBatch EntryHash +func testIATBatchisEntryHash(t testing.TB) { + mockBatch := mockIATBatch(t) + mockBatch.GetControl().EntryHash = 1 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchCalculatedControlEquality("0012104288", "1")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchisEntryHash tests validating IATBatch EntryHash +func TestIATBatchisEntryHash(t *testing.T) { + testIATBatchisEntryHash(t) +} + +// BenchmarkIATBatchisEntryHash benchmarks validating IATBatch EntryHash +func BenchmarkIATBatchisEntryHash(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchisEntryHash(b) + } +} + +// testIATBatchIsSequenceAscending validates sequence ascending +func testIATBatchIsSequenceAscending(t testing.TB) { + mockBatch := mockIATBatch(t) + e2 := mockIATEntryDetail() + e2.TraceNumber = "1" + mockBatch.AddEntry(e2) + mockBatch.Entries[1].Addenda10 = mockAddenda10() + mockBatch.Entries[1].Addenda11 = mockAddenda11() + mockBatch.Entries[1].Addenda12 = mockAddenda12() + mockBatch.Entries[1].Addenda13 = mockAddenda13() + mockBatch.Entries[1].Addenda14 = mockAddenda14() + mockBatch.Entries[1].Addenda15 = mockAddenda15() + mockBatch.Entries[1].Addenda16 = mockAddenda16() + mockBatch.GetControl().EntryAddendaCount = 16 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchAscending("231380100000001", "1")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchIsSequenceAscending tests validating sequence ascending +func TestIATBatchIsSequenceAscending(t *testing.T) { + testIATBatchIsSequenceAscending(t) +} + +// BenchmarkIATBatchIsSequenceAscending tests validating sequence ascending +func BenchmarkIATBatchIsSequenceAscending(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchIsSequenceAscending(b) + } +} + +// testIATBatchIsCategory validates category +func testIATBatchIsCategory(t testing.TB) { + mockBatch := mockIATBatchManyEntries(t) + mockBatch.GetEntries()[1].Category = CategoryReturn + + err := mockBatch.verify() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchIsCategory tests validating category +func TestIATBatchIsCategory(t *testing.T) { + testIATBatchIsCategory(t) +} + +// BenchmarkIATBatchIsCategory tests validating category +func BenchmarkIATBatchIsCategory(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchIsCategory(b) + } +} + +// testIATBatchCategory tests IATBatch Category +func testIATBatchCategory(t testing.TB) { + mockBatch := mockIATBatch(t) + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + if mockBatch.Category() != CategoryForward { + t.Errorf("No returns and Category is %s", mockBatch.Category()) + } +} + +// TestIATBatchCategory tests IATBatch Category +func TestIATBatchCategory(t *testing.T) { + testIATBatchCategory(t) +} + +// BenchmarkIATBatchCategory benchmarks IATBatch Category +func BenchmarkIATBatchCategory(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchCategory(b) + } +} + +// testIATBatchValidateEntry validates EntryDetail +func testIATBatchValidateEntry(t testing.TB) { + mockBatch := mockIATBatch(t) + mockBatch.GetEntries()[0].TransactionCode = 5 + + err := mockBatch.verify() + if !base.Match(err, ErrTransactionCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchValidateEntry tests validating Entry +func TestIATBatchValidateEntry(t *testing.T) { + testIATBatchValidateEntry(t) +} + +// BenchmarkIATBatchValidateEntry tests validating Entry +func BenchmarkIATBatchValidateEntry(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchValidateEntry(b) + } +} + +// testIATBatchValidateAddenda10 validates Addenda10 +func testIATBatchValidateAddenda10(t testing.TB) { + mockBatch := mockIATBatchManyEntries(t) + mockBatch.GetEntries()[1].Addenda10.TypeCode = "02" + + err := mockBatch.verify() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchValidateAddenda10 tests validating Addenda10 +func TestIATBatchValidateAddenda10(t *testing.T) { + testIATBatchValidateAddenda10(t) +} + +// BenchmarkIATBatchValidateAddenda10 tests validating Addenda10 +func BenchmarkIATBatchValidateAddenda10(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchValidateAddenda10(b) + } +} + +// testIATBatchValidateAddenda11 validates Addenda11 +func testIATBatchValidateAddenda11(t testing.TB) { + mockBatch := mockIATBatchManyEntries(t) + mockBatch.GetEntries()[1].Addenda11.TypeCode = "02" + + err := mockBatch.verify() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchValidateAddenda11 tests validating Addenda11 +func TestIATBatchValidateAddenda11(t *testing.T) { + testIATBatchValidateAddenda11(t) +} + +// BenchmarkIATBatchValidateAddenda11 tests validating Addenda11 +func BenchmarkIATBatchValidateAddenda11(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchValidateAddenda11(b) + } +} + +// testIATBatchValidateAddenda12 validates Addenda12 +func testIATBatchValidateAddenda12(t testing.TB) { + mockBatch := mockIATBatchManyEntries(t) + mockBatch.GetEntries()[1].Addenda12.TypeCode = "02" + + err := mockBatch.verify() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchValidateAddenda12 tests validating Addenda12 +func TestIATBatchValidateAddenda12(t *testing.T) { + testIATBatchValidateAddenda12(t) +} + +// BenchmarkIATBatchValidateAddenda12 tests validating Addenda12 +func BenchmarkIATBatchValidateAddenda12(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchValidateAddenda12(b) + } +} + +// testIATBatchValidateAddenda13 validates Addenda13 +func testIATBatchValidateAddenda13(t testing.TB) { + mockBatch := mockIATBatchManyEntries(t) + mockBatch.GetEntries()[1].Addenda13.TypeCode = "02" + + err := mockBatch.verify() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchValidateAddenda13 tests validating Addenda13 +func TestIATBatchValidateAddenda13(t *testing.T) { + testIATBatchValidateAddenda13(t) +} + +// BenchmarkIATBatchValidateAddenda13 tests validating Addenda13 +func BenchmarkIATBatchValidateAddenda13(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchValidateAddenda13(b) + } +} + +// testIATBatchValidateAddenda14 validates Addenda14 +func testIATBatchValidateAddenda14(t testing.TB) { + mockBatch := mockIATBatchManyEntries(t) + mockBatch.GetEntries()[1].Addenda14.TypeCode = "02" + + err := mockBatch.verify() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchValidateAddenda14 tests validating Addenda14 +func TestIATBatchValidateAddenda14(t *testing.T) { + testIATBatchValidateAddenda14(t) +} + +// BenchmarkIATBatchValidateAddenda14 tests validating Addenda14 +func BenchmarkIATBatchValidateAddenda14(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchValidateAddenda14(b) + } +} + +// testIATBatchValidateAddenda15 validates Addenda15 +func testIATBatchValidateAddenda15(t testing.TB) { + mockBatch := mockIATBatchManyEntries(t) + mockBatch.GetEntries()[1].Addenda15.TypeCode = "02" + + err := mockBatch.verify() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchValidateAddenda15 tests validating Addenda15 +func TestIATBatchValidateAddenda15(t *testing.T) { + testIATBatchValidateAddenda15(t) +} + +// BenchmarkIATBatchValidateAddenda15 tests validating Addenda15 +func BenchmarkIATBatchValidateAddenda15(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchValidateAddenda15(b) + } +} + +// testIATBatchValidateAddenda16 validates Addenda16 +func testIATBatchValidateAddenda16(t testing.TB) { + mockBatch := mockIATBatchManyEntries(t) + mockBatch.GetEntries()[1].Addenda16.TypeCode = "02" + + err := mockBatch.verify() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchValidateAddenda16 tests validating Addenda16 +func TestIATBatchValidateAddenda16(t *testing.T) { + testIATBatchValidateAddenda16(t) +} + +// BenchmarkIATBatchValidateAddenda16 tests validating Addenda16 +func BenchmarkIATBatchValidateAddenda16(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchValidateAddenda16(b) + } +} + +// testIATBatchValidateAddenda17 validates Addenda17 +func testIATBatchValidateAddenda17(t testing.TB) { + mockBatch := mockInvalidIATBatch(t) + + err := mockBatch.verify() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchValidateAddenda17 tests validating Addenda17 +func TestIATBatchValidateAddenda17(t *testing.T) { + testIATBatchValidateAddenda17(t) +} + +// BenchmarkIATBatchValidateAddenda17 tests validating Addenda17 +func BenchmarkIATBatchValidateAddenda17(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchValidateAddenda17(b) + } +} + +// testIATBatchCreateError validates IATBatch create error +func testIATBatchCreate(t testing.TB) { + file := NewFile().SetHeader(mockFileHeader()) + mockBatch := mockIATBatch(t) + mockBatch.GetHeader().ServiceClassCode = 7 + + err := mockBatch.Create() + if !base.Match(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } + + file.AddIATBatch(mockBatch) +} + +// TestIATBatchCreate tests validating IATBatch create error +func TestIATBatchCreate(t *testing.T) { + testIATBatchCreate(t) +} + +// BenchmarkIATBatchCreate benchmarks validating IATBatch create error +func BenchmarkIATBatchCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchCreate(b) + } + +} + +// testIATBatchValidate validates IATBatch validate error +func testIATBatchValidate(t testing.TB) { + file := NewFile().SetHeader(mockFileHeader()) + mockBatch := mockIATBatch(t) + mockBatch.GetHeader().ServiceClassCode = DebitsOnly + + err := mockBatch.verify() + if !base.Match(err, NewErrBatchHeaderControlEquality(DebitsOnly, CreditsOnly)) { + t.Errorf("%T: %s", err, err) + } + + file.AddIATBatch(mockBatch) +} + +// TestIATBatchValidate tests validating IATBatch validate error +func TestIATBatchValidate(t *testing.T) { + testIATBatchValidate(t) +} + +// BenchmarkIATBatchValidate benchmarks validating IATBatch validate error +func BenchmarkIATBatchValidate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchValidate(b) + } + +} + +// testIATBatchEntryAddendum validates IATBatch EntryAddendum error +func testIATBatchEntryAddendum(t testing.TB) { + file := NewFile().SetHeader(mockFileHeader()) + mockBatch := mockIATBatch(t) + mockBatch.Entries[0].AddAddenda17(mockAddenda17()) + mockBatch.Entries[0].AddAddenda17(mockAddenda17B()) + mockBatch.Entries[0].AddAddenda18(mockAddenda18()) + mockBatch.Entries[0].AddAddenda18(mockAddenda18B()) + mockBatch.Entries[0].AddAddenda18(mockAddenda18C()) + mockBatch.Entries[0].AddAddenda18(mockAddenda18D()) + mockBatch.Entries[0].AddAddenda18(mockAddenda18E()) + mockBatch.Entries[0].AddAddenda18(mockAddenda18F()) + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchAddendaCount(6, 5)) { + t.Errorf("%T: %s", err, err) + } + + file.AddIATBatch(mockBatch) +} + +// TestIATBatchEntryAddendum tests validating IATBatch EntryAddendum error +func TestIATBatchEntryAddendum(t *testing.T) { + testIATBatchEntryAddendum(t) +} + +// BenchmarkIATBatchEntryAddendum benchmarks validating IATBatch EntryAddendum error +func BenchmarkIATBatchEntryAddendum(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchEntryAddendum(b) + } +} + +// testIATBatchAddenda17EDSequenceNumber validates IATBatch Addenda17 Entry Detail Sequence Number error +func testIATBatchAddenda17EDSequenceNumber(t testing.TB) { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATBatchHeaderFF()) + mockBatch.AddEntry(mockIATEntryDetail()) + addenda17 := NewAddenda17() + addenda17.PaymentRelatedInformation = "This is an international payment" + addenda17.SequenceNumber = 1 + addenda17.EntryDetailSequenceNumber = 0000001 + addenda17B := NewAddenda17() + addenda17B.PaymentRelatedInformation = "Transfer of money from one country to another" + addenda17B.SequenceNumber = 2 + addenda17B.EntryDetailSequenceNumber = 0000001 + mockBatch.Entries[0].Addenda10 = mockAddenda10() + mockBatch.Entries[0].Addenda11 = mockAddenda11() + mockBatch.Entries[0].Addenda12 = mockAddenda12() + mockBatch.Entries[0].Addenda13 = mockAddenda13() + mockBatch.Entries[0].Addenda14 = mockAddenda14() + mockBatch.Entries[0].Addenda15 = mockAddenda15() + mockBatch.Entries[0].Addenda16 = mockAddenda16() + mockBatch.Entries[0].AddAddenda17(addenda17) + mockBatch.Entries[0].AddAddenda17(addenda17B) + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + addenda17B.SequenceNumber = 1 + addenda17B.EntryDetailSequenceNumber = 0000002 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchAddendaTraceNumber("0000002", "0000001")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddenda17EDSequenceNumber tests validating IATBatch Addenda17 Entry Detail Sequence Number error +func TestIATBatchAddenda17EDSequenceNumber(t *testing.T) { + testIATBatchAddenda17EDSequenceNumber(t) +} + +// BenchmarkIATBatchAddenda17EDSequenceNumber benchmarks validating IATBatch Addenda17 Entry Detail Sequence Number error +func BenchmarkIATBatchAddenda17EDSequenceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchAddenda17EDSequenceNumber(b) + } +} + +// testIATBatchAddenda17Sequence validates IATBatch Addenda17 Sequence Number error +func testIATBatchAddenda17Sequence(t testing.TB) { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATBatchHeaderFF()) + mockBatch.AddEntry(mockIATEntryDetail()) + addenda17 := NewAddenda17() + addenda17.PaymentRelatedInformation = "This is an international payment" + addenda17.SequenceNumber = 2 + addenda17.EntryDetailSequenceNumber = 0000001 + addenda17B := NewAddenda17() + addenda17B.PaymentRelatedInformation = "Transfer of money from one country to another" + addenda17B.SequenceNumber = 1 + addenda17B.EntryDetailSequenceNumber = 0000001 + mockBatch.Entries[0].Addenda10 = mockAddenda10() + mockBatch.Entries[0].Addenda11 = mockAddenda11() + mockBatch.Entries[0].Addenda12 = mockAddenda12() + mockBatch.Entries[0].Addenda13 = mockAddenda13() + mockBatch.Entries[0].Addenda14 = mockAddenda14() + mockBatch.Entries[0].Addenda15 = mockAddenda15() + mockBatch.Entries[0].Addenda16 = mockAddenda16() + mockBatch.Entries[0].AddAddenda17(addenda17) + mockBatch.Entries[0].AddAddenda17(addenda17B) + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + addenda17B.SequenceNumber = -1 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchAscending("-1", "1")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddenda17Sequence tests validating IATBatch Addenda17 Sequence Number error +func TestIATBatchAddenda17Sequence(t *testing.T) { + testIATBatchAddenda17Sequence(t) +} + +// BenchmarkIATBatchAddenda17Sequence benchmarks validating IATBatch Addenda17 Sequence Number error +func BenchmarkIATBatchAddenda17Sequence(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchAddenda17Sequence(b) + } +} + +// testIATBatchAddenda18EDSequenceNumber validates IATBatch Addenda18 Entry Detail Sequence Number error +func testIATBatchAddenda18EDSequenceNumber(t testing.TB) { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATBatchHeaderFF()) + mockBatch.AddEntry(mockIATEntryDetail()) + addenda17 := NewAddenda17() + addenda17.PaymentRelatedInformation = "This is an international payment" + addenda17.SequenceNumber = 1 + addenda17.EntryDetailSequenceNumber = 0000001 + addenda17B := NewAddenda17() + addenda17B.PaymentRelatedInformation = "Transfer of money from one country to another" + addenda17B.SequenceNumber = 2 + addenda17B.EntryDetailSequenceNumber = 0000001 + addenda18 := NewAddenda18() + addenda18.ForeignCorrespondentBankName = "Bank of Turkey" + addenda18.ForeignCorrespondentBankIDNumberQualifier = "01" + addenda18.ForeignCorrespondentBankIDNumber = "12312345678910" + addenda18.ForeignCorrespondentBankBranchCountryCode = "TR" + addenda18.SequenceNumber = 1 + addenda18.EntryDetailSequenceNumber = 0000001 + addenda18B := NewAddenda18() + addenda18B.ForeignCorrespondentBankName = "Bank of United Kingdom" + addenda18B.ForeignCorrespondentBankIDNumberQualifier = "01" + addenda18B.ForeignCorrespondentBankIDNumber = "1234567890123456789012345678901234" + addenda18B.ForeignCorrespondentBankBranchCountryCode = "GB" + addenda18B.SequenceNumber = 2 + addenda18B.EntryDetailSequenceNumber = 0000001 + mockBatch.Entries[0].Addenda10 = mockAddenda10() + mockBatch.Entries[0].Addenda11 = mockAddenda11() + mockBatch.Entries[0].Addenda12 = mockAddenda12() + mockBatch.Entries[0].Addenda13 = mockAddenda13() + mockBatch.Entries[0].Addenda14 = mockAddenda14() + mockBatch.Entries[0].Addenda15 = mockAddenda15() + mockBatch.Entries[0].Addenda16 = mockAddenda16() + mockBatch.Entries[0].AddAddenda17(addenda17) + mockBatch.Entries[0].AddAddenda17(addenda17B) + mockBatch.Entries[0].AddAddenda18(addenda18) + mockBatch.Entries[0].AddAddenda18(addenda18B) + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + addenda18B.SequenceNumber = 1 + addenda18B.EntryDetailSequenceNumber = 0000002 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchAddendaTraceNumber("0000002", "0000001")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddenda18EDSequenceNumber tests validating IATBatch Addenda18 Entry Detail Sequence Number error +func TestIATBatchAddenda18EDSequenceNumber(t *testing.T) { + testIATBatchAddenda18EDSequenceNumber(t) +} + +// BenchmarkIATBatchAddenda18EDSequenceNumber benchmarks validating IATBatch Addenda18 Entry Detail Sequence Number error +func BenchmarkIATBatchAddenda18EDSequenceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchAddenda18EDSequenceNumber(b) + } +} + +// testIATBatchAddenda18Sequence validates IATBatch Addenda18 Sequence Number error +func testIATBatchAddenda18Sequence(t testing.TB) { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATBatchHeaderFF()) + mockBatch.AddEntry(mockIATEntryDetail()) + addenda17 := NewAddenda17() + addenda17.PaymentRelatedInformation = "This is an international payment" + addenda17.SequenceNumber = 1 + addenda17.EntryDetailSequenceNumber = 0000001 + addenda17B := NewAddenda17() + addenda17B.PaymentRelatedInformation = "Transfer of money from one country to another" + addenda17B.SequenceNumber = 2 + addenda17B.EntryDetailSequenceNumber = 0000001 + addenda18 := NewAddenda18() + addenda18.ForeignCorrespondentBankName = "Bank of Turkey" + addenda18.ForeignCorrespondentBankIDNumberQualifier = "01" + addenda18.ForeignCorrespondentBankIDNumber = "12312345678910" + addenda18.ForeignCorrespondentBankBranchCountryCode = "TR" + addenda18.SequenceNumber = 1 + addenda18.EntryDetailSequenceNumber = 0000001 + addenda18B := NewAddenda18() + addenda18B.ForeignCorrespondentBankName = "Bank of United Kingdom" + addenda18B.ForeignCorrespondentBankIDNumberQualifier = "01" + addenda18B.ForeignCorrespondentBankIDNumber = "1234567890123456789012345678901234" + addenda18B.ForeignCorrespondentBankBranchCountryCode = "GB" + addenda18B.SequenceNumber = 2 + addenda18B.EntryDetailSequenceNumber = 0000001 + mockBatch.Entries[0].Addenda10 = mockAddenda10() + mockBatch.Entries[0].Addenda11 = mockAddenda11() + mockBatch.Entries[0].Addenda12 = mockAddenda12() + mockBatch.Entries[0].Addenda13 = mockAddenda13() + mockBatch.Entries[0].Addenda14 = mockAddenda14() + mockBatch.Entries[0].Addenda15 = mockAddenda15() + mockBatch.Entries[0].Addenda16 = mockAddenda16() + mockBatch.Entries[0].AddAddenda17(addenda17) + mockBatch.Entries[0].AddAddenda17(addenda17B) + mockBatch.Entries[0].AddAddenda18(addenda18) + mockBatch.Entries[0].AddAddenda18(addenda18B) + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + addenda18B.SequenceNumber = -1 + err := mockBatch.verify() + if !base.Match(err, NewErrBatchAscending("-1", "1")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddenda18Sequence tests validating IATBatch Addenda18 Sequence Number error +func TestIATBatchAddenda18Sequence(t *testing.T) { + testIATBatchAddenda18Sequence(t) +} + +// BenchmarkIATBatchAddenda18Sequence benchmarks validating IATBatch Addenda18 Sequence Number error +func BenchmarkIATBatchAddenda18Sequence(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchAddenda18Sequence(b) + } +} + +// testIATNoEntry validates error for no entries +func testIATNoEntry(t testing.TB) { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATBatchHeaderFF()) + err := mockBatch.verify() + if !base.Match(err, ErrBatchNoEntries) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATNoEntry tests validating error for no entries +func TestIATNoEntry(t *testing.T) { + testIATNoEntry(t) +} + +// BenchmarkIATNoEntry benchmarks validating error for no entries +func BenchmarkIATNoEntry(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATNoEntry(b) + } +} + +// testIATBatchAddendumTypeCode validates IATBatch Addendum TypeCode +func testIATBatchAddendumTypeCode(t testing.TB) { + mockBatch := mockIATBatch(t) + mockBatch.GetEntries()[0].AddAddenda17(mockAddenda17()) + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + err := mockBatch.Validate() + // no error expected + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddendumTypeCode tests validating IATBatch Addendum TypeCode +func TestIATBatchAddendumTypeCode(t *testing.T) { + testIATBatchAddendumTypeCode(t) +} + +// BenchmarkIATBatchAddendumTypeCode benchmarks validating IATBatch Addendum TypeCode +func BenchmarkIATBatchAddendumTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchAddendumTypeCode(b) + } + +} + +// testIATBatchAddenda17Count validates IATBatch Addenda17 Count +func testIATBatchAddenda17Count(t testing.TB) { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATBatchHeaderFF()) + mockBatch.AddEntry(mockIATEntryDetail()) + addenda17 := NewAddenda17() + addenda17.PaymentRelatedInformation = "This is an international payment" + addenda17.SequenceNumber = 1 + addenda17.EntryDetailSequenceNumber = 0000001 + addenda17B := NewAddenda17() + addenda17B.PaymentRelatedInformation = "Transfer of money from one country to another" + addenda17B.SequenceNumber = 2 + addenda17B.EntryDetailSequenceNumber = 0000001 + addenda17C := NewAddenda17() + addenda17C.PaymentRelatedInformation = "Send money Internationally" + addenda17C.SequenceNumber = 3 + addenda17C.EntryDetailSequenceNumber = 0000001 + mockBatch.Entries[0].Addenda10 = mockAddenda10() + mockBatch.Entries[0].Addenda11 = mockAddenda11() + mockBatch.Entries[0].Addenda12 = mockAddenda12() + mockBatch.Entries[0].Addenda13 = mockAddenda13() + mockBatch.Entries[0].Addenda14 = mockAddenda14() + mockBatch.Entries[0].Addenda15 = mockAddenda15() + mockBatch.Entries[0].Addenda16 = mockAddenda16() + mockBatch.Entries[0].AddAddenda17(addenda17) + mockBatch.Entries[0].AddAddenda17(addenda17B) + mockBatch.Entries[0].AddAddenda17(addenda17C) + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchAddendaCount(3, 2)) { + t.Errorf("%T: %s", err, err) + } + +} + +// TestIATBatchAddenda17Count tests validating IATBatch Addenda17 Count +func TestIATBatchAddenda17Count(t *testing.T) { + testIATBatchAddenda17Count(t) +} + +// BenchmarkIATBatchAddenda17Count benchmarks validating IATBatch Addenda17 Count +func BenchmarkIATBatchAddenda17Count(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchAddenda17Count(b) + } +} + +// testIATBatchAddenda18Count validates IATBatch Addenda18 Count +func testIATBatchAddenda18Count(t testing.TB) { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATBatchHeaderFF()) + mockBatch.AddEntry(mockIATEntryDetail()) + mockBatch.Entries[0].Addenda10 = mockAddenda10() + mockBatch.Entries[0].Addenda11 = mockAddenda11() + mockBatch.Entries[0].Addenda12 = mockAddenda12() + mockBatch.Entries[0].Addenda13 = mockAddenda13() + mockBatch.Entries[0].Addenda14 = mockAddenda14() + mockBatch.Entries[0].Addenda15 = mockAddenda15() + mockBatch.Entries[0].Addenda16 = mockAddenda16() + mockBatch.Entries[0].AddAddenda17(mockAddenda17()) + mockBatch.Entries[0].AddAddenda18(mockAddenda18()) + mockBatch.Entries[0].AddAddenda18(mockAddenda18B()) + mockBatch.Entries[0].AddAddenda18(mockAddenda18C()) + mockBatch.Entries[0].AddAddenda18(mockAddenda18D()) + mockBatch.Entries[0].AddAddenda18(mockAddenda18E()) + + addenda18F := NewAddenda18() + addenda18F.ForeignCorrespondentBankName = "Russian Federation Bank" + addenda18F.ForeignCorrespondentBankIDNumberQualifier = "01" + addenda18F.ForeignCorrespondentBankIDNumber = "123123456789943874" + addenda18F.ForeignCorrespondentBankBranchCountryCode = "RU" + addenda18F.SequenceNumber = 6 + addenda18F.EntryDetailSequenceNumber = 0000001 + + mockBatch.Entries[0].AddAddenda18(mockAddenda18F()) + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchAddendaCount(6, 5)) { + t.Errorf("%T: %s", err, err) + } + +} + +// TestIATBatchAddenda18Count tests validating IATBatch Addenda18 Count +func TestIATBatchAddenda18Count(t *testing.T) { + testIATBatchAddenda18Count(t) +} + +// BenchmarkIATBatchAddenda18Count benchmarks validating IATBatch Addenda18 Count +func BenchmarkIATBatchAddenda18Count(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchAddenda18Count(b) + } +} + +// testIATBatchBuildAddendaError validates IATBatch build Addenda error +func testIATBatchBuildAddendaError(t testing.TB) { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATBatchHeaderFF()) + mockBatch.AddEntry(mockIATEntryDetail()) + + err := mockBatch.build() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchBuildAddendaError tests validating IATBatch build Addenda error +func TestIATBatchBuildAddendaError(t *testing.T) { + testIATBatchBuildAddendaError(t) +} + +// BenchmarkIATBatchBuildAddendaError benchmarks validating IATBatch build Addenda error +func BenchmarkIATBatchBuildAddendaError(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchBuildAddendaError(b) + } + +} + +// testIATBatchBHODFI validates IATBatchHeader ODFI error +func testIATBatchBHODFI(t testing.TB) { + mockBatch := mockIATBatch(t) + mockBatch.GetEntries()[0].SetTraceNumber("39387337", 1) + + err := mockBatch.verify() + if !base.Match(err, NewErrBatchTraceNumberNotODFI("23138010", "39387337")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchBHODFI tests validating IATBatchHeader ODFI error +func TestIATBatchBHODFI(t *testing.T) { + testIATBatchBHODFI(t) +} + +// BenchmarkIATBatchBHODFI benchmarks validating IATBatchHeader ODFI error +func BenchmarkIATBatchBHODFI(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchBHODFI(b) + } + +} + +// testIATBatchAddenda99Count validates IATBatch Addenda99 Count +func testIATBatchAddenda99Count(t testing.TB) { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATReturnBatchHeaderFF()) + mockBatch.AddEntry(mockIATEntryDetail()) + mockBatch.GetEntries()[0].Category = CategoryReturn + mockBatch.Entries[0].Addenda10 = mockAddenda10() + mockBatch.Entries[0].Addenda11 = mockAddenda11() + mockBatch.Entries[0].Addenda12 = mockAddenda12() + mockBatch.Entries[0].Addenda13 = mockAddenda13() + mockBatch.Entries[0].Addenda14 = mockAddenda14() + mockBatch.Entries[0].Addenda15 = mockAddenda15() + mockBatch.Entries[0].Addenda16 = mockAddenda16() + mockBatch.Entries[0].AddAddenda17(mockAddenda17()) + mockBatch.Entries[0].Addenda99 = mockIATAddenda99() + mockBatch.category = CategoryReturn + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + err := mockBatch.Validate() + // TODO: are we expecting there to be no errors here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } + +} + +// TestIATBatchAddenda99Count tests validating IATBatch Addenda99 Count +func TestIATBatchAddenda99Count(t *testing.T) { + testIATBatchAddenda99Count(t) +} + +// BenchmarkIATBatchAddenda99Count benchmarks validating IATBatch Addenda99 Count +func BenchmarkIATBatchAddenda99Count(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATBatchAddenda99Count(b) + } +} + +// TestIATBatchAddenda98TotalCount validates IATBatch Addenda98 TotalCount +func TestIATBatchAddenda98TotalCount(t *testing.T) { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATNOCBatchHeaderFF()) + mockBatch.AddEntry(mockIATEntryDetail()) + mockBatch.GetEntries()[0].TransactionCode = CheckingReturnNOCCredit + mockBatch.GetEntries()[0].AddendaRecords = 2 + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.Entries[0].Addenda98 = mockIATAddenda98() + mockBatch.category = CategoryNOC + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + err := mockBatch.Validate() + // TODO: are we expecting there to be no errors here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddenda98Nil validates IATBatch Addenda98 is not nil +func TestIATBatchAddenda98Nil(t *testing.T) { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATNOCBatchHeaderFF()) + mockBatch.AddEntry(mockIATEntryDetail()) + mockBatch.GetEntries()[0].TransactionCode = CheckingReturnNOCCredit + mockBatch.GetEntries()[0].AddendaRecords = 2 + mockBatch.GetEntries()[0].Category = CategoryNOC + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + err := mockBatch.Validate() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddenda98RecordType validates IATBatch Addenda98 RecordType is valid +func TestIATBatchAddenda98RecordType(t *testing.T) { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATNOCBatchHeaderFF()) + mockBatch.AddEntry(mockIATEntryDetail()) + mockBatch.GetEntries()[0].TransactionCode = CheckingReturnNOCCredit + mockBatch.GetEntries()[0].AddendaRecords = 2 + addenda98 := mockAddenda98() + addenda98.TypeCode = "00" + mockBatch.GetEntries()[0].Addenda98 = addenda98 + mockBatch.GetEntries()[0].Category = CategoryNOC + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + err := mockBatch.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddenda99RecordType validates IATBatch Addenda99 RecordType is valid +func TestIATBatchAddenda99RecordType(t *testing.T) { + mockBatch := mockIATBatch(t) + mockBatch.SetHeader(mockIATReturnBatchHeaderFF()) + mockBatch.GetEntries()[0].AddendaRecords = 1 + addenda99 := mockAddenda99() + addenda99.TypeCode = "00" + mockBatch.GetEntries()[0].Addenda99 = addenda99 + mockBatch.GetEntries()[0].Category = CategoryReturn + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + err := mockBatch.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddenda18RecordType validates IATBatch Addenda18 RecordType is valid +func TestIATBatchAddenda18RecordType(t *testing.T) { + mockBatch := mockIATBatch(t) + mockBatch.SetHeader(mockIATReturnBatchHeaderFF()) + addenda18 := mockAddenda18() + addenda18.TypeCode = "00" + mockBatch.GetEntries()[0].AddAddenda18(addenda18) + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + err := mockBatch.Validate() + if !base.Match(err, ErrAddendaTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddenda98TransactionCode validates IATBatch Transaction Code Count +func TestIATBatchAddenda98TransactionCode(t *testing.T) { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATNOCBatchHeaderFF()) + mockBatch.AddEntry(mockIATEntryDetail()) + mockBatch.GetEntries()[0].TransactionCode = CheckingReturnNOCCredit + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.Entries[0].Addenda98 = mockIATAddenda98() + mockBatch.category = CategoryNOC + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + err := mockBatch.Validate() + // TODO: are we expecting there to be no errors here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATBatchAddenda98IATIndicator validates IATBatch Transaction Code Count +func TestIATBatchAddenda98IATIndicator(t *testing.T) { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATNOCBatchHeaderFF()) + mockBatch.GetHeader().IATIndicator = "B" + mockBatch.AddEntry(mockIATEntryDetail()) + mockBatch.GetEntries()[0].TransactionCode = CheckingReturnNOCCredit + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.Entries[0].Addenda98 = mockIATAddenda98() + mockBatch.category = CategoryNOC + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchIATNOC("B", "IATCOR")) { + t.Errorf("%T: %s", err, err) + } +} + +func TestIATBatchAddenda98SECCode(t *testing.T) { + mockBatch := IATBatch{} + mockBatch.SetHeader(mockIATNOCBatchHeaderFF()) + mockBatch.GetHeader().StandardEntryClassCode = IAT + mockBatch.AddEntry(mockIATEntryDetail()) + mockBatch.GetEntries()[0].TransactionCode = CheckingReturnNOCCredit + mockBatch.GetEntries()[0].Category = CategoryNOC + mockBatch.Entries[0].Addenda98 = mockIATAddenda98() + mockBatch.category = CategoryNOC + + if err := mockBatch.build(); err != nil { + t.Errorf("%T: %s", err, err) + } + + err := mockBatch.Validate() + if !base.Match(err, NewErrBatchIATNOC("IAT", "COR")) { + t.Errorf("%T: %s", err, err) + } +} + +// TestMockISODestinationCountryCode validates mockIATBatch +func TestMockISODestinationCountryCode(t *testing.T) { + iatBatch := mockIATBatch(t) + iatBatch.Header.ISODestinationCountryCode = "®©" + err := iatBatch.Validate() + if !base.Match(err, ErrValidISO3166) { + t.Errorf("%T: %s", err, err) + } + +} + +// TestMockISOOriginatingCurrencyCode validates mockIATBatch +func TestMockISOOriginatingCurrencyCode(t *testing.T) { + iatBatch := mockIATBatch(t) + iatBatch.Header.ISOOriginatingCurrencyCode = "®©" + err := iatBatch.Validate() + if !base.Match(err, ErrValidISO4217) { + t.Errorf("%T: %s", err, err) + } + +} + +// TestMockISODestinationCurrencyCode validates mockIATBatch +func TestMockISODestinationCurrencyCode(t *testing.T) { + iatBatch := mockIATBatch(t) + iatBatch.Header.ISODestinationCurrencyCode = "®©" + err := iatBatch.Validate() + if !base.Match(err, ErrValidISO4217) { + t.Errorf("%T: %s", err, err) + } +} + +// TestParseRuneCountIATBatchHeader tests parsing an invalid RuneCount in IATBatchHeader +func TestParseRuneCountIATBatchHeader(t *testing.T) { + line := "5220 FF3 US123456789 IATTRADEPAYMTCADUSD010101" + r := NewReader(strings.NewReader(line)) + r.line = line + err := r.parseIATBatchHeader() + if !base.Match(err, ErrFieldInclusion) { + t.Errorf("%T: %s", err, err) + } +} + +func TestIATBatch_calculateEntryHash(t *testing.T) { + b := &IATBatch{} + b.SetHeader(mockIATBatchHeaderFF()) + + ed1 := mockIATEntryDetailWithAddendas() + ed1.RDFIIdentification = "05600507" + b.AddEntry(ed1) + + ed2 := mockIATEntryDetailWithAddendas() + ed2.RDFIIdentification = "05140225" + b.AddEntry(ed2) + + ed3 := mockIATEntryDetailWithAddendas() + ed3.RDFIIdentification = "11400065" + b.AddEntry(ed3) + + require.NoError(t, b.build()) + + hash := b.calculateEntryHash() + require.Equal(t, 22140797, hash) +} + +func TestDiscussion1077(t *testing.T) { + b := IATBatch{} + b.SetHeader(mockIATBatchHeaderFF()) + + ed1 := mockIATEntryDetailWithAddendas() + ed1.RDFIIdentification = "101201863" + b.AddEntry(ed1) + + ed2 := mockIATEntryDetailWithAddendas() + ed2.RDFIIdentification = "274973183" + b.AddEntry(ed2) + + ed3 := mockIATEntryDetailWithAddendas() + ed3.RDFIIdentification = "111900659" + b.AddEntry(ed3) + + ed4 := mockIATEntryDetailWithAddendas() + ed4.RDFIIdentification = "101918101" + b.AddEntry(ed4) + + ed5 := mockIATEntryDetailWithAddendas() + ed5.RDFIIdentification = "044000037" + b.AddEntry(ed5) + + require.NoError(t, b.build()) + require.Equal(t, 63399382, b.calculateEntryHash()) +} diff --git a/iatEntryDetail.go b/iatEntryDetail.go new file mode 100644 index 000000000..ca4cc3b9c --- /dev/null +++ b/iatEntryDetail.go @@ -0,0 +1,301 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "fmt" + "strconv" + "strings" + "unicode/utf8" +) + +// IATEntryDetail contains the actual transaction data for an individual entry. +// Fields include those designating the entry as a deposit (credit) or +// withdrawal (debit), the transit routing number for the entry recipient's financial +// institution, the account number (left justify,no zero fill), name, and dollar amount. +type IATEntryDetail struct { + // ID is a client defined string used as a reference to this record. + ID string `json:"id"` + // TransactionCode if the receivers account is: + // Credit (deposit) to checking account '22' + // Prenote for credit to checking account '23' + // Debit (withdrawal) to checking account '27' + // Prenote for debit to checking account '28' + // Credit to savings account '32' + // Prenote for credit to savings account '33' + // Debit to savings account '37' + // Prenote for debit to savings account '38' + TransactionCode int `json:"transactionCode"` + // RDFIIdentification is the RDFI's routing number without the last digit. + // Receiving Depository Financial Institution + RDFIIdentification string `json:"RDFIIdentification"` + // CheckDigit the last digit of the RDFI's routing number + CheckDigit string `json:"checkDigit"` + // AddendaRecords is the number of Addenda Records + AddendaRecords int `json:"addendaRecords"` + // Amount Number of cents you are debiting/crediting this account + Amount int `json:"amount"` + // DFIAccountNumber is the receiver's bank account number you are crediting/debiting. + // It important to note that this is an alphanumeric field, so its space padded, no zero padded + DFIAccountNumber string `json:"DFIAccountNumber"` + // OFACScreeningIndicator - Leave blank + OFACScreeningIndicator string `json:"OFACScreeningIndicator"` + // SecondaryOFACScreeningIndicator - Leave blank + SecondaryOFACScreeningIndicator string `json:"secondaryOFACScreeningIndicator"` + // AddendaRecordIndicator indicates the existence of an Addenda Record. + // A value of "1" indicates that one or more addenda records follow, + // and "0" means no such record is present. + AddendaRecordIndicator int `json:"addendaRecordIndicator"` + // TraceNumber assigned by the ODFI in ascending sequence, is included in each + // Entry Detail Record, Corporate Entry Detail Record, and addenda Record. + // Trace Numbers uniquely identify each entry within a batch in an ACH input file. + // In association with the Batch Number, transmission (File Creation) Date, + // and File ID Modifier, the Trace Number uniquely identifies an entry within a given file. + // For addenda Records, the Trace Number will be identical to the Trace Number + // in the associated Entry Detail Record, since the Trace Number is associated + // with an entry or item rather than a physical record. + // + // Use TraceNumberField for a properly formatted string representation. + TraceNumber string `json:"traceNumber,omitempty"` + // Addenda10 is mandatory for IAT entries + // + // The Addenda10 Record identifies the Receiver of the transaction and the dollar amount of + // the payment. + Addenda10 *Addenda10 `json:"addenda10"` + // Addenda11 is mandatory for IAT entries + // + // The Addenda11 record identifies key information related to the Originator of + // the entry. + Addenda11 *Addenda11 `json:"addenda11"` + // Addenda12 is mandatory for IAT entries + // + // The Addenda12 record identifies key information related to the Originator of + // the entry. + Addenda12 *Addenda12 `json:"addenda12"` + // Addenda13 is mandatory for IAT entries + // + // The Addenda13 contains information related to the financial institution originating the entry. + // For inbound IAT entries, the Fourth Addenda Record must contain information to identify the + // foreign financial institution that is providing the funding and payment instruction for + // the IAT entry. + Addenda13 *Addenda13 `json:"addenda13"` + // Addenda14 is mandatory for IAT entries + // + // The Addenda14 identifies the Receiving financial institution holding the Receiver's account. + Addenda14 *Addenda14 `json:"addenda14"` + // Addenda15 is mandatory for IAT entries + // + // The Addenda15 record identifies key information related to the Receiver. + Addenda15 *Addenda15 `json:"addenda15"` + // Addenda16 is mandatory for IAt entries + // + // Addenda16 record identifies additional key information related to the Receiver. + Addenda16 *Addenda16 `json:"addenda16"` + // Addenda17 is optional for IAT entries + // + // This is an optional Addenda Record used to provide payment-related data. There i a maximum of up to two of these + // Addenda Records with each IAT entry. + Addenda17 []*Addenda17 `json:"addenda17,omitempty"` + // Addenda18 is optional for IAT entries + // + // This optional addenda record is used to provide information on each Foreign Correspondent Bank involved in the + // processing of the IAT entry. If no Foreign Correspondent Bank is involved,the record should not be included. + // A maximum of five Addenda18 records may be included with each IAT entry. + Addenda18 []*Addenda18 `json:"addenda18,omitempty"` + // Addenda98 for user with NOC + Addenda98 *Addenda98 `json:"addenda98,omitempty"` + // Addenda99 for use with Returns + Addenda99 *Addenda99 `json:"addenda99,omitempty"` + // Category defines if the entry is a Forward, Return, or NOC + Category string `json:"category,omitempty"` + // validator is composed for data validation + validator + // converters is composed for ACH to golang Converters + converters +} + +// NewIATEntryDetail returns a new IATEntryDetail with default values for non exported fields +func NewIATEntryDetail() *IATEntryDetail { + iatEd := &IATEntryDetail{ + Category: CategoryForward, + AddendaRecordIndicator: 1, + } + return iatEd +} + +// Parse takes the input record string and parses the EntryDetail values +// +// Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity. +func (iatEd *IATEntryDetail) Parse(record string) { + if utf8.RuneCountInString(record) != 94 { + return + } + + // 1-1 Always "6" + // 2-3 is checking credit 22 debit 27 savings credit 32 debit 37 + iatEd.TransactionCode = iatEd.parseNumField(record[1:3]) + // 4-11 the RDFI's routing number without the last digit. + iatEd.RDFIIdentification = iatEd.parseStringField(record[3:11]) + // 12-12 The last digit of the RDFI's routing number + iatEd.CheckDigit = iatEd.parseStringField(record[11:12]) + // 13-16 Number of addenda records + iatEd.AddendaRecords = iatEd.parseNumField(record[12:16]) + // 17-29 reserved - Leave blank + // 30-39 Number of cents you are debiting/crediting this account + iatEd.Amount = iatEd.parseNumField(record[29:39]) + // 40-74 The foreign receiver's account number you are crediting/debiting + iatEd.DFIAccountNumber = record[39:74] + // 75-76 reserved Leave blank + // 77 OFACScreeningIndicator + iatEd.OFACScreeningIndicator = " " + // 78-78 Secondary SecondaryOFACScreeningIndicator + iatEd.SecondaryOFACScreeningIndicator = " " + // 79-79 1 if addenda exists 0 if it does not + //iatEd.AddendaRecordIndicator = 1 + iatEd.AddendaRecordIndicator = iatEd.parseNumField(record[78:79]) + // 80-94 An internal identification (alphanumeric) that you use to uniquely identify + // this Entry Detail Record This number should be unique to the transaction and will help identify the transaction in case of an inquiry + iatEd.TraceNumber = strings.TrimSpace(record[79:94]) +} + +// String writes the EntryDetail struct to a 94 character string. +func (iatEd *IATEntryDetail) String() string { + var buf strings.Builder + buf.Grow(94) + buf.WriteString(entryDetailPos) + buf.WriteString(fmt.Sprintf("%v", iatEd.TransactionCode)) + buf.WriteString(iatEd.RDFIIdentificationField()) + buf.WriteString(iatEd.CheckDigit) + buf.WriteString(iatEd.AddendaRecordsField()) + buf.WriteString(" ") + buf.WriteString(iatEd.AmountField()) + buf.WriteString(iatEd.DFIAccountNumberField()) + buf.WriteString(" ") + buf.WriteString(iatEd.OFACScreeningIndicatorField()) + buf.WriteString(iatEd.SecondaryOFACScreeningIndicatorField()) + buf.WriteString(fmt.Sprintf("%v", iatEd.AddendaRecordIndicator)) + buf.WriteString(iatEd.TraceNumberField()) + return buf.String() +} + +// Validate performs NACHA format rule checks on the record and returns an error if not Validated +// The first error encountered is returned and stops that parsing. +func (iatEd *IATEntryDetail) Validate() error { + if err := iatEd.fieldInclusion(); err != nil { + return err + } + if err := iatEd.isTransactionCode(iatEd.TransactionCode); err != nil { + return fieldError("TransactionCode", err, strconv.Itoa(iatEd.TransactionCode)) + } + if err := iatEd.isAlphanumeric(iatEd.DFIAccountNumber); err != nil { + return fieldError("DFIAccountNumber", err, iatEd.DFIAccountNumber) + } + // CheckDigit calculations + calculated := iatEd.CalculateCheckDigit(iatEd.RDFIIdentificationField()) + + edCheckDigit, err := strconv.Atoi(iatEd.CheckDigit) + if err != nil { + return fieldError("CheckDigit", err, iatEd.CheckDigit) + } + if calculated != edCheckDigit { + return fieldError("RDFIIdentification", NewErrValidCheckDigit(calculated), iatEd.CheckDigit) + } + return nil +} + +// fieldInclusion validate mandatory fields are not default values. If fields are +// invalid the ACH transfer will be returned. +func (iatEd *IATEntryDetail) fieldInclusion() error { + if iatEd.TransactionCode == 0 { + return fieldError("TransactionCode", ErrConstructor, strconv.Itoa(iatEd.TransactionCode)) + } + if iatEd.RDFIIdentification == "" { + return fieldError("RDFIIdentification", ErrConstructor, iatEd.RDFIIdentificationField()) + } + if iatEd.AddendaRecords == 0 { + return fieldError("AddendaRecords", ErrConstructor, strconv.Itoa(iatEd.AddendaRecords)) + } + if iatEd.DFIAccountNumber == "" { + return fieldError("DFIAccountNumber", ErrConstructor, iatEd.DFIAccountNumber) + } + if iatEd.AddendaRecordIndicator == 0 { + return fieldError("AddendaRecordIndicator", ErrConstructor, strconv.Itoa(iatEd.AddendaRecordIndicator)) + } + if iatEd.TraceNumber == "" { + return fieldError("TraceNumber", ErrConstructor, iatEd.TraceNumberField()) + } + return nil +} + +// SetRDFI takes the 9 digit RDFI account number and separates it for RDFIIdentification and CheckDigit +func (iatEd *IATEntryDetail) SetRDFI(rdfi string) *IATEntryDetail { + s := iatEd.stringField(rdfi, 9) + iatEd.RDFIIdentification = iatEd.parseStringField(s[:8]) + iatEd.CheckDigit = iatEd.parseStringField(s[8:9]) + return iatEd +} + +// SetTraceNumber takes first 8 digits of ODFI and concatenates a sequence number onto the TraceNumber +func (iatEd *IATEntryDetail) SetTraceNumber(ODFIIdentification string, seq int) { + iatEd.TraceNumber = iatEd.stringField(ODFIIdentification, 8) + iatEd.numericField(seq, 7) +} + +// RDFIIdentificationField get the rdfiIdentification with zero padding +func (iatEd *IATEntryDetail) RDFIIdentificationField() string { + return iatEd.stringField(iatEd.RDFIIdentification, 8) +} + +// AddendaRecordsField returns a zero padded AddendaRecords string +func (iatEd *IATEntryDetail) AddendaRecordsField() string { + return iatEd.numericField(iatEd.AddendaRecords, 4) +} + +// AmountField returns a zero padded string of amount +func (iatEd *IATEntryDetail) AmountField() string { + return iatEd.numericField(iatEd.Amount, 10) +} + +// DFIAccountNumberField gets the DFIAccountNumber with space padding +func (iatEd *IATEntryDetail) DFIAccountNumberField() string { + return iatEd.alphaField(iatEd.DFIAccountNumber, 35) +} + +// OFACScreeningIndicatorField gets the OFACScreeningIndicator +func (iatEd *IATEntryDetail) OFACScreeningIndicatorField() string { + return iatEd.alphaField(iatEd.OFACScreeningIndicator, 1) +} + +// SecondaryOFACScreeningIndicatorField gets the SecondaryOFACScreeningIndicator +func (iatEd *IATEntryDetail) SecondaryOFACScreeningIndicatorField() string { + return iatEd.alphaField(iatEd.SecondaryOFACScreeningIndicator, 1) +} + +// TraceNumberField returns a zero padded TraceNumber string +func (iatEd *IATEntryDetail) TraceNumberField() string { + return iatEd.stringField(iatEd.TraceNumber, 15) +} + +// AddAddenda17 appends an Addenda17 to the IATEntryDetail +func (iatEd *IATEntryDetail) AddAddenda17(addenda17 *Addenda17) { + iatEd.Addenda17 = append(iatEd.Addenda17, addenda17) +} + +// AddAddenda18 appends an Addenda18 to the IATEntryDetail +func (iatEd *IATEntryDetail) AddAddenda18(addenda18 *Addenda18) { + iatEd.Addenda18 = append(iatEd.Addenda18, addenda18) +} diff --git a/iatEntryDetail_test.go b/iatEntryDetail_test.go new file mode 100644 index 000000000..0c71de5f0 --- /dev/null +++ b/iatEntryDetail_test.go @@ -0,0 +1,432 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "strconv" + "strings" + "testing" + + "github.com/moov-io/base" +) + +// mockIATEntryDetail creates an IAT EntryDetail +func mockIATEntryDetail() *IATEntryDetail { + entry := NewIATEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("121042882") + entry.AddendaRecords = 007 + entry.DFIAccountNumber = "123456789" + entry.Amount = 100000 // 1000.00 + entry.SetTraceNumber(mockIATBatchHeaderFF().ODFIIdentification, 1) + entry.Category = CategoryForward + return entry +} + +// mockIATEntryDetail2 creates an EntryDetail +func mockIATEntryDetail2() *IATEntryDetail { + entry := NewIATEntryDetail() + entry.TransactionCode = CheckingCredit + entry.SetRDFI("121042882") + entry.AddendaRecords = 007 + entry.DFIAccountNumber = "123456789" + entry.Amount = 200000 // 2000.00 + entry.SetTraceNumber(mockIATBatchHeaderFF().ODFIIdentification, 2) + entry.Category = CategoryForward + return entry +} + +// testMockIATEntryDetail validates an IATEntryDetail record +func testMockIATEntryDetail(t testing.TB) { + entry := mockIATEntryDetail() + if err := entry.Validate(); err != nil { + t.Error("mockEntryDetail does not validate and will break other tests") + } + if entry.TransactionCode != CheckingCredit { + t.Error("TransactionCode dependent default value has changed") + } + if entry.RDFIIdentification != "12104288" { + t.Error("RDFIIdentification dependent default value has changed") + } + if entry.AddendaRecords != 7 { + t.Error("AddendaRecords default dependent value has changed") + } + if entry.DFIAccountNumber != "123456789" { + t.Error("DFIAccountNumber dependent default value has changed") + } + if entry.Amount != 100000 { + t.Error("Amount dependent default value has changed") + } + if entry.TraceNumber != "231380100000001" { + t.Errorf("TraceNumber dependent default value has changed %v", entry.TraceNumber) + } +} + +// TestMockIATEntryDetail tests validating an IATEntryDetail record +func TestMockIATEntryDetail(t *testing.T) { + testMockIATEntryDetail(t) +} + +// BenchmarkMockIATEntryDetail benchmarks validating an IATEntryDetail record +func BenchmarkIATMockEntryDetail(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testMockIATEntryDetail(b) + } +} + +// testParseIATEntryDetail parses a known IATEntryDetail record string. +func testParseIATEntryDetail(t testing.TB) { + var line = "6221210428820007 000010000012345678901234567890123456789012345 1231380100000001" + r := NewReader(strings.NewReader(line)) + r.addIATCurrentBatch(NewIATBatch(mockIATBatchHeaderFF())) + r.IATCurrentBatch.SetHeader(mockIATBatchHeaderFF()) + r.line = line + if err := r.parseIATEntryDetail(); err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.IATCurrentBatch.GetEntries()[0] + + if record.TransactionCode != CheckingCredit { + t.Errorf("TransactionCode Expected '22' got: %v", record.TransactionCode) + } + if record.RDFIIdentificationField() != "12104288" { + t.Errorf("RDFIIdentification Expected '12104288' got: '%v'", record.RDFIIdentificationField()) + } + + if record.AddendaRecordsField() != "0007" { + t.Errorf("addendaRecords Expected '0007' got: %v", record.AddendaRecords) + } + if record.CheckDigit != "2" { + t.Errorf("CheckDigit Expected '2' got: %v", record.CheckDigit) + } + if record.AmountField() != "0000100000" { + t.Errorf("Amount Expected '0000100000' got: %v", record.AmountField()) + } + if record.DFIAccountNumberField() != "12345678901234567890123456789012345" { + t.Errorf("DfiAccountNumber Expected '12345678901234567890123456789012345' got: %v", record.DFIAccountNumberField()) + } + if record.AddendaRecordIndicator != 1 { + t.Errorf("AddendaRecordIndicator Expected '0' got: %v", record.AddendaRecordIndicator) + } + if record.TraceNumberField() != "231380100000001" { + t.Errorf("TraceNumber Expected '231380100000001' got: %v", record.TraceNumberField()) + } +} + +// TestParseIATEntryDetail tests parsing a known IATEntryDetail record string. +func TestParseIATEntryDetail(t *testing.T) { + testParseIATEntryDetail(t) +} + +// BenchmarkParseIATEntryDetail benchmarks parsing a known IATEntryDetail record string. +func BenchmarkParseIATEntryDetail(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testParseIATEntryDetail(b) + } +} + +// testIATEDString validates that a known parsed entry +// detail can be returned to a string of the same value +func testIATEDString(t testing.TB) { + var line = "6221210428820007 000010000012345678901234567890123456789012345 1231380100000001" + r := NewReader(strings.NewReader(line)) + r.addIATCurrentBatch(NewIATBatch(mockIATBatchHeaderFF())) + r.IATCurrentBatch.SetHeader(mockIATBatchHeaderFF()) + r.line = line + if err := r.parseIATEntryDetail(); err != nil { + t.Errorf("%T: %s", err, err) + } + record := r.IATCurrentBatch.GetEntries()[0] + + if record.String() != line { + t.Errorf("Strings do not match") + } +} + +// TestIATEDString tests validating that a known parsed entry +// detail can be returned to a string of the same value +func TestIATEDString(t *testing.T) { + testIATEDString(t) +} + +// BenchmarkIATEDString benchmarks validating that a known parsed entry +// detail can be returned to a string of the same value +func BenchmarkIATEDString(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATEDString(b) + } +} + +// testIATEDInvalidTransactionCode validates error for IATEntryDetail invalid TransactionCode +func testIATEDInvalidTransactionCode(t testing.TB) { + iatEd := mockIATEntryDetail() + iatEd.TransactionCode = 77 + err := iatEd.Validate() + if !base.Match(err, ErrTransactionCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATEDInvalidTransactionCode tests validating error for IATEntryDetail invalid TransactionCode +func TestIATEDInvalidTransactionCode(t *testing.T) { + testIATEDInvalidTransactionCode(t) +} + +// BenchmarkIATEDTransactionCode benchmarks validating error for IATEntryDetail invalid TransactionCode +func BenchmarkIATEDInvalidTransactionCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATEDInvalidTransactionCode(b) + } +} + +// testEDIATDFIAccountNumberAlphaNumeric validates company identification is alphanumeric +func testEDIATDFIAccountNumberAlphaNumeric(t testing.TB) { + ed := mockIATEntryDetail() + ed.DFIAccountNumber = "®" + err := ed.Validate() + if !base.Match(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestEDIATDFIAccountNumberAlphaNumeric tests validating company identification is alphanumeric +func TestEDIATDFIAccountNumberAlphaNumeric(t *testing.T) { + testEDIATDFIAccountNumberAlphaNumeric(t) +} + +// BenchmarkEDIATDFIAccountNumberAlphaNumeric benchmarks validating company identification is alphanumeric +func BenchmarkEDIATDFIAccountNumberAlphaNumeric(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDIATDFIAccountNumberAlphaNumeric(b) + } +} + +// testEDIATisCheckDigit validates check digit +func testEDIATisCheckDigit(t testing.TB) { + ed := mockIATEntryDetail() + ed.CheckDigit = "1" + err := ed.Validate() + if !base.Match(err, NewErrValidCheckDigit(7)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestEDIATisCheckDigit tests validating check digit +func TestEDIATisCheckDigit(t *testing.T) { + testEDIATisCheckDigit(t) +} + +// BenchmarkEDIATisCheckDigit benchmarks validating check digit +func BenchmarkEDIATisCheckDigit(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDIATisCheckDigit(b) + } +} + +// testEDIATSetRDFII validates setting RDFI +func testEDIATSetRDFI(t testing.TB) { + ed := NewIATEntryDetail() + ed.SetRDFI("810866774") + if ed.RDFIIdentification != "81086677" { + t.Error("RDFI identification") + } + if ed.CheckDigit != "4" { + t.Error("Unexpected check digit") + } +} + +// TestEDIATSetRDFI tests validating setting RDFI +func TestEDIATSetRDFI(t *testing.T) { + testEDIATSetRDFI(t) +} + +// BenchmarkEDIATSetRDFI benchmarks validating setting RDFI +func BenchmarkEDIATSetRDFI(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEDIATSetRDFI(b) + } +} + +// testValidateEDIATCheckDigit validates CheckDigit error +func testValidateEDIATCheckDigit(t testing.TB) { + ed := mockIATEntryDetail() + ed.CheckDigit = "XYZ" + err := ed.Validate() + if !base.Match(err, &strconv.NumError{}) { + t.Errorf("%T: %s", err, err) + } +} + +// TestValidateEDIATCheckDigit tests validating validates CheckDigit error +func TestValidateEDIATCheckDigit(t *testing.T) { + testValidateEDIATCheckDigit(t) +} + +// BenchmarkValidateEDIATCheckDigit benchmarks validating CheckDigit error +func BenchmarkValidateEDIATCheckDigit(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testValidateEDIATCheckDigit(b) + } +} + +// testIATEDTransactionCode validates IATEntryDetail TransactionCode fieldInclusion +func testIATEDTransactionCode(t testing.TB) { + iatEd := mockIATEntryDetail() + iatEd.TransactionCode = 0 + err := iatEd.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATEDTransactionCode tests validating IATEntryDetail TransactionCode fieldInclusion +func TestIATEDTransactionCode(t *testing.T) { + testIATEDTransactionCode(t) +} + +// BenchmarkIATEDTransactionCode benchmarks validating IATEntryDetail TransactionCode fieldInclusion +func BenchmarkIATEDTransactionCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATEDTransactionCode(b) + } +} + +// testIATEDRDFIIdentification validates IATEntryDetail RDFIIdentification fieldInclusion +func testIATEDRDFIIdentification(t testing.TB) { + iatEd := mockIATEntryDetail() + iatEd.RDFIIdentification = "" + err := iatEd.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATEDRDFIIdentification tests validating IATEntryDetail RDFIIdentification fieldInclusion +func TestIATEDRDFIIdentification(t *testing.T) { + testIATEDRDFIIdentification(t) +} + +// BenchmarkIATEDRDFIIdentification benchmarks validating IATEntryDetail RDFIIdentification fieldInclusion +func BenchmarkIATEDRDFIIdentification(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATEDRDFIIdentification(b) + } +} + +// testIATEDAddendaRecords validates IATEntryDetail AddendaRecords fieldInclusion +func testIATEDAddendaRecords(t testing.TB) { + iatEd := mockIATEntryDetail() + iatEd.AddendaRecords = 0 + err := iatEd.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATEDAddendaRecords tests validating IATEntryDetail AddendaRecords fieldInclusion +func TestIATEDAddendaRecords(t *testing.T) { + testIATEDAddendaRecords(t) +} + +// BenchmarkIATEDAddendaRecords benchmarks validating IATEntryDetail AddendaRecords fieldInclusion +func BenchmarkIATEDAddendaRecords(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATEDAddendaRecords(b) + } +} + +// testIATEDDFIAccountNumber validates IATEntryDetail DFIAccountNumber fieldInclusion +func testIATEDDFIAccountNumber(t testing.TB) { + iatEd := mockIATEntryDetail() + iatEd.DFIAccountNumber = "" + err := iatEd.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATEDDFIAccountNumber tests validating IATEntryDetail DFIAccountNumber fieldInclusion +func TestIATEDDFIAccountNumber(t *testing.T) { + testIATEDDFIAccountNumber(t) +} + +// BenchmarkIATEDDFIAccountNumber benchmarks validating IATEntryDetail DFIAccountNumber fieldInclusion +func BenchmarkIATEDDFIAccountNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATEDDFIAccountNumber(b) + } +} + +// testIATEDTraceNumber validates IATEntryDetail TraceNumber fieldInclusion +func testIATEDTraceNumber(t testing.TB) { + iatEd := mockIATEntryDetail() + iatEd.TraceNumber = "0" + err := iatEd.Validate() + // TODO: are we expecting there to be no errors here? + if !base.Match(err, nil) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATEDTraceNumber tests validating IATEntryDetail TraceNumber fieldInclusion +func TestIATEDTraceNumber(t *testing.T) { + testIATEDTraceNumber(t) +} + +// BenchmarkIATEDTraceNumber benchmarks validating IATEntryDetail TraceNumber fieldInclusion +func BenchmarkIATEDTraceNumber(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATEDTraceNumber(b) + } +} + +// testIATEDAddendaRecordIndicator validates IATEntryDetail AddendaIndicator fieldInclusion +func testIATEDAddendaRecordIndicator(t testing.TB) { + iatEd := mockIATEntryDetail() + iatEd.AddendaRecordIndicator = 0 + err := iatEd.Validate() + if !base.Match(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATEDAddendaRecordIndicator tests validating IATEntryDetail AddendaRecordIndicator fieldInclusion +func TestIATEDAddendaRecordIndicator(t *testing.T) { + testIATEDAddendaRecordIndicator(t) +} + +// BenchmarkIATEDAddendaRecordIndicator benchmarks validating IATEntryDetail AddendaRecordIndicator fieldInclusion +func BenchmarkIATEDAddendaRecordIndicator(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATEDAddendaRecordIndicator(b) + } +} diff --git a/internal/iso3166/iso3166.go b/internal/iso3166/iso3166.go new file mode 100644 index 000000000..cd327890e --- /dev/null +++ b/internal/iso3166/iso3166.go @@ -0,0 +1,271 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Generated on 2021-03-23T11:08:15Z by adam, any modifications will be overwritten +package iso3166 + +var countryCodes = map[string]bool{ + "AF": true, // Afghanistan + "AX": true, // Åland Islands + "AL": true, // Albania + "DZ": true, // Algeria + "AS": true, // American Samoa + "AD": true, // Andorra + "AO": true, // Angola + "AI": true, // Anguilla + "AQ": true, // Antarctica + "AG": true, // Antigua and Barbuda + "AR": true, // Argentina + "AM": true, // Armenia + "AW": true, // Aruba + "AU": true, // Australia + "AT": true, // Austria + "AZ": true, // Azerbaijan + "BS": true, // Bahamas + "BH": true, // Bahrain + "BD": true, // Bangladesh + "BB": true, // Barbados + "BY": true, // Belarus + "BE": true, // Belgium + "BZ": true, // Belize + "BJ": true, // Benin + "BM": true, // Bermuda + "BT": true, // Bhutan + "BO": true, // Bolivia, Plurinational State of + "BQ": true, // Bonaire, Sint Eustatius and Saba + "BA": true, // Bosnia and Herzegovina + "BW": true, // Botswana + "BV": true, // Bouvet Island + "BR": true, // Brazil + "IO": true, // British Indian Ocean Territory + "BN": true, // Brunei Darussalam + "BG": true, // Bulgaria + "BF": true, // Burkina Faso + "BI": true, // Burundi + "KH": true, // Cambodia + "CM": true, // Cameroon + "CA": true, // Canada + "CV": true, // Cape Verde + "KY": true, // Cayman Islands + "CF": true, // Central African Republic + "TD": true, // Chad + "CL": true, // Chile + "CN": true, // China + "CX": true, // Christmas Island + "CC": true, // Cocos (Keeling) Islands + "CO": true, // Colombia + "KM": true, // Comoros + "CG": true, // Congo + "CD": true, // Congo, the Democratic Republic of the + "CK": true, // Cook Islands + "CR": true, // Costa Rica + "CI": true, // Côte d'Ivoire + "HR": true, // Croatia + "CU": true, // Cuba + "CW": true, // Curaçao + "CY": true, // Cyprus + "CZ": true, // Czech Republic + "DK": true, // Denmark + "DJ": true, // Djibouti + "DM": true, // Dominica + "DO": true, // Dominican Republic + "EC": true, // Ecuador + "EG": true, // Egypt + "SV": true, // El Salvador + "GQ": true, // Equatorial Guinea + "ER": true, // Eritrea + "EE": true, // Estonia + "ET": true, // Ethiopia + "FK": true, // Falkland Islands (Malvinas) + "FO": true, // Faroe Islands + "FJ": true, // Fiji + "FI": true, // Finland + "FR": true, // France + "GF": true, // French Guiana + "PF": true, // French Polynesia + "TF": true, // French Southern Territories + "GA": true, // Gabon + "GM": true, // Gambia + "GE": true, // Georgia + "DE": true, // Germany + "GH": true, // Ghana + "GI": true, // Gibraltar + "GR": true, // Greece + "GL": true, // Greenland + "GD": true, // Grenada + "GP": true, // Guadeloupe + "GU": true, // Guam + "GT": true, // Guatemala + "GG": true, // Guernsey + "GN": true, // Guinea + "GW": true, // Guinea-Bissau + "GY": true, // Guyana + "HT": true, // Haiti + "HM": true, // Heard Island and McDonald Islands + "VA": true, // Holy See (Vatican City State) + "HN": true, // Honduras + "HK": true, // Hong Kong + "HU": true, // Hungary + "IS": true, // Iceland + "IN": true, // India + "ID": true, // Indonesia + "IR": true, // Iran, Islamic Republic of + "IQ": true, // Iraq + "IE": true, // Ireland + "IM": true, // Isle of Man + "IL": true, // Israel + "IT": true, // Italy + "JM": true, // Jamaica + "JP": true, // Japan + "JE": true, // Jersey + "JO": true, // Jordan + "KZ": true, // Kazakhstan + "KE": true, // Kenya + "KI": true, // Kiribati + "KP": true, // Korea, Democratic People's Republic of + "KR": true, // Korea, Republic of + "KW": true, // Kuwait + "KG": true, // Kyrgyzstan + "LA": true, // Lao People's Democratic Republic + "LV": true, // Latvia + "LB": true, // Lebanon + "LS": true, // Lesotho + "LR": true, // Liberia + "LY": true, // Libya + "LI": true, // Liechtenstein + "LT": true, // Lithuania + "LU": true, // Luxembourg + "MO": true, // Macao + "MK": true, // Macedonia, the Former Yugoslav Republic of + "MG": true, // Madagascar + "MW": true, // Malawi + "MY": true, // Malaysia + "MV": true, // Maldives + "ML": true, // Mali + "MT": true, // Malta + "MH": true, // Marshall Islands + "MQ": true, // Martinique + "MR": true, // Mauritania + "MU": true, // Mauritius + "YT": true, // Mayotte + "MX": true, // Mexico + "FM": true, // Micronesia, Federated States of + "MD": true, // Moldova, Republic of + "MC": true, // Monaco + "MN": true, // Mongolia + "ME": true, // Montenegro + "MS": true, // Montserrat + "MA": true, // Morocco + "MZ": true, // Mozambique + "MM": true, // Myanmar + "NA": true, // Namibia + "NR": true, // Nauru + "NP": true, // Nepal + "NL": true, // Netherlands + "NC": true, // New Caledonia + "NZ": true, // New Zealand + "NI": true, // Nicaragua + "NE": true, // Niger + "NG": true, // Nigeria + "NU": true, // Niue + "NF": true, // Norfolk Island + "MP": true, // Northern Mariana Islands + "NO": true, // Norway + "OM": true, // Oman + "PK": true, // Pakistan + "PW": true, // Palau + "PS": true, // Palestine, State of + "PA": true, // Panama + "PG": true, // Papua New Guinea + "PY": true, // Paraguay + "PE": true, // Peru + "PH": true, // Philippines + "PN": true, // Pitcairn + "PL": true, // Poland + "PT": true, // Portugal + "PR": true, // Puerto Rico + "QA": true, // Qatar + "RE": true, // Réunion + "RO": true, // Romania + "RU": true, // Russian Federation + "RW": true, // Rwanda + "BL": true, // Saint Barthélemy + "SH": true, // Saint Helena, Ascension and Tristan da Cunha + "KN": true, // Saint Kitts and Nevis + "LC": true, // Saint Lucia + "MF": true, // Saint Martin (French part) + "PM": true, // Saint Pierre and Miquelon + "VC": true, // Saint Vincent and the Grenadines + "WS": true, // Samoa + "SM": true, // San Marino + "ST": true, // Sao Tome and Principe + "SA": true, // Saudi Arabia + "SN": true, // Senegal + "RS": true, // Serbia + "SC": true, // Seychelles + "SL": true, // Sierra Leone + "SG": true, // Singapore + "SX": true, // Sint Maarten (Dutch part) + "SK": true, // Slovakia + "SI": true, // Slovenia + "SB": true, // Solomon Islands + "SO": true, // Somalia + "ZA": true, // South Africa + "GS": true, // South Georgia and the South Sandwich Islands + "SS": true, // South Sudan + "ES": true, // Spain + "LK": true, // Sri Lanka + "SD": true, // Sudan + "SR": true, // Suriname + "SJ": true, // Svalbard and Jan Mayen + "SZ": true, // Swaziland + "SE": true, // Sweden + "CH": true, // Switzerland + "SY": true, // Syrian Arab Republic + "TW": true, // Taiwan, Province of China + "TJ": true, // Tajikistan + "TZ": true, // Tanzania, United Republic of + "TH": true, // Thailand + "TL": true, // Timor-Leste + "TG": true, // Togo + "TK": true, // Tokelau + "TO": true, // Tonga + "TT": true, // Trinidad and Tobago + "TN": true, // Tunisia + "TR": true, // Turkey + "TM": true, // Turkmenistan + "TC": true, // Turks and Caicos Islands + "TV": true, // Tuvalu + "UG": true, // Uganda + "UA": true, // Ukraine + "AE": true, // United Arab Emirates + "GB": true, // United Kingdom + "US": true, // United States + "UM": true, // United States Minor Outlying Islands + "UY": true, // Uruguay + "UZ": true, // Uzbekistan + "VU": true, // Vanuatu + "VE": true, // Venezuela, Bolivarian Republic of + "VN": true, // Viet Nam + "VG": true, // Virgin Islands, British + "VI": true, // Virgin Islands, U.S. + "WF": true, // Wallis and Futuna + "EH": true, // Western Sahara + "YE": true, // Yemen + "ZM": true, // Zambia + "ZW": true, // Zimbabwe +} diff --git a/internal/iso3166/iso3166_gen.go b/internal/iso3166/iso3166_gen.go new file mode 100644 index 000000000..79037e2fb --- /dev/null +++ b/internal/iso3166/iso3166_gen.go @@ -0,0 +1,119 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//go:build ignore +// +build ignore + +// Generates iso3166.go. +// +// This file grabs the ISO 3166-1-alpha2 codes and writes them +// into source code so we don't rely on any external files (zip, +// json, etc). +// +// The data is pulled from datahub.io as the ISO.org site only offers +// XML. +// +// https://datahub.io/core/country-list#data +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "go/format" + "log" + "net/http" + "os" + "os/user" + "path/filepath" + "runtime" + "time" +) + +var ( + // From https://datahub.io/core/country-list#data + downloadUrl = "https://datahub.io/core/country-list/r/data.json" + outputFilename = filepath.Join("internal", "iso3166", "iso3166.go") +) + +// [{"Code": "AF", "Name": "Afghanistan"}, ...] +type country struct { + Code string `json:"Code"` + Name string `json:"Name"` +} + +func main() { + when := time.Now().Format("2006-01-02T03:04:05Z") + who, err := user.Current() + if err != nil { + log.Fatalf("Unable to get user on %s", runtime.GOOS) + } + + // Write copyright header + var buf bytes.Buffer + fmt.Fprintf(&buf, `// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Generated on %s by %s, any modifications will be overwritten +package iso3166 +`, when, who.Username) + + // Download certs + resp, err := http.Get(downloadUrl) + if err != nil { + log.Fatalf("error while downloading %s: %v", downloadUrl, err) + } + defer resp.Body.Close() + + var countries []country + if err := json.NewDecoder(resp.Body).Decode(&countries); err != nil { + log.Fatalf("error while parsing country response: %v", err) + } + + // Write countries to source code + fmt.Fprintln(&buf, "var countryCodes = map[string]bool{") + for i := range countries { + fmt.Fprintf(&buf, fmt.Sprintf(`"%s": true, // %s`+"\n", countries[i].Code, countries[i].Name)) + } + fmt.Fprintln(&buf, "}") + + // format source code and write file + out, err := format.Source(buf.Bytes()) + if err != nil { + fmt.Println(buf.String()) + log.Fatalf("error formatting output code, err=%v", err) + } + + err = os.WriteFile(outputFilename, out, 0644) + if err != nil { + log.Fatalf("error writing file, err=%v", err) + } +} diff --git a/internal/iso3166/validate.go b/internal/iso3166/validate.go new file mode 100644 index 000000000..9d8c36f6c --- /dev/null +++ b/internal/iso3166/validate.go @@ -0,0 +1,29 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package iso3166 + +import ( + "strings" +) + +// Valid returns successful if code is a valid ISO 3166-1-alpha-2 +// country code. Example: US +func Valid(code string) bool { + _, ok := countryCodes[strings.ToUpper(code)] + return ok +} diff --git a/internal/iso3166/validate_test.go b/internal/iso3166/validate_test.go new file mode 100644 index 000000000..f2c075776 --- /dev/null +++ b/internal/iso3166/validate_test.go @@ -0,0 +1,40 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package iso3166 + +import ( + "testing" +) + +func TestValidate(t *testing.T) { + if !Valid("US") { + t.Error("expected valid") + } + + if !Valid("SS") { + t.Error("expected valid") + } + + if Valid("") { + t.Errorf("invalid") + } + + if Valid("QZ") { + t.Errorf("invalid") + } +} diff --git a/internal/iso4217/iso4217.go b/internal/iso4217/iso4217.go new file mode 100644 index 000000000..93218452c --- /dev/null +++ b/internal/iso4217/iso4217.go @@ -0,0 +1,324 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Generated on 2021-03-23T11:08:18Z by adam, any modifications will be overwritten +package iso4217 + +var currencyCodes = map[string]bool{ + "AFN": true, // Afghani + "EUR": true, // Euro + "ALL": true, // Lek + "DZD": true, // Algerian Dinar + "USD": true, // US Dollar + "AOA": true, // Kwanza + "XCD": true, // East Caribbean Dollar + "ARS": true, // Argentine Peso + "AMD": true, // Armenian Dram + "AWG": true, // Aruban Florin + "AUD": true, // Australian Dollar + "AZN": true, // Azerbaijan Manat + "BSD": true, // Bahamian Dollar + "BHD": true, // Bahraini Dinar + "BDT": true, // Taka + "BBD": true, // Barbados Dollar + "BYN": true, // Belarusian Ruble + "BZD": true, // Belize Dollar + "XOF": true, // CFA Franc BCEAO + "BMD": true, // Bermudian Dollar + "INR": true, // Indian Rupee + "BTN": true, // Ngultrum + "BOB": true, // Boliviano + "BOV": true, // Mvdol + "BAM": true, // Convertible Mark + "BWP": true, // Pula + "NOK": true, // Norwegian Krone + "BRL": true, // Brazilian Real + "BND": true, // Brunei Dollar + "BGN": true, // Bulgarian Lev + "BIF": true, // Burundi Franc + "CVE": true, // Cabo Verde Escudo + "KHR": true, // Riel + "XAF": true, // CFA Franc BEAC + "CAD": true, // Canadian Dollar + "KYD": true, // Cayman Islands Dollar + "CLP": true, // Chilean Peso + "CLF": true, // Unidad de Fomento + "CNY": true, // Yuan Renminbi + "COP": true, // Colombian Peso + "COU": true, // Unidad de Valor Real + "KMF": true, // Comorian Franc + "CDF": true, // Congolese Franc + "NZD": true, // New Zealand Dollar + "CRC": true, // Costa Rican Colon + "HRK": true, // Kuna + "CUP": true, // Cuban Peso + "CUC": true, // Peso Convertible + "ANG": true, // Netherlands Antillean Guilder + "CZK": true, // Czech Koruna + "DKK": true, // Danish Krone + "DJF": true, // Djibouti Franc + "DOP": true, // Dominican Peso + "EGP": true, // Egyptian Pound + "SVC": true, // El Salvador Colon + "ERN": true, // Nakfa + "SZL": true, // Lilangeni + "ETB": true, // Ethiopian Birr + "FKP": true, // Falkland Islands Pound + "FJD": true, // Fiji Dollar + "XPF": true, // CFP Franc + "GMD": true, // Dalasi + "GEL": true, // Lari + "GHS": true, // Ghana Cedi + "GIP": true, // Gibraltar Pound + "GTQ": true, // Quetzal + "GBP": true, // Pound Sterling + "GNF": true, // Guinean Franc + "GYD": true, // Guyana Dollar + "HTG": true, // Gourde + "HNL": true, // Lempira + "HKD": true, // Hong Kong Dollar + "HUF": true, // Forint + "ISK": true, // Iceland Krona + "IDR": true, // Rupiah + "XDR": true, // SDR (Special Drawing Right) + "IRR": true, // Iranian Rial + "IQD": true, // Iraqi Dinar + "ILS": true, // New Israeli Sheqel + "JMD": true, // Jamaican Dollar + "JPY": true, // Yen + "JOD": true, // Jordanian Dinar + "KZT": true, // Tenge + "KES": true, // Kenyan Shilling + "KPW": true, // North Korean Won + "KRW": true, // Won + "KWD": true, // Kuwaiti Dinar + "KGS": true, // Som + "LAK": true, // Lao Kip + "LBP": true, // Lebanese Pound + "LSL": true, // Loti + "ZAR": true, // Rand + "LRD": true, // Liberian Dollar + "LYD": true, // Libyan Dinar + "CHF": true, // Swiss Franc + "MOP": true, // Pataca + "MKD": true, // Denar + "MGA": true, // Malagasy Ariary + "MWK": true, // Malawi Kwacha + "MYR": true, // Malaysian Ringgit + "MVR": true, // Rufiyaa + "MRU": true, // Ouguiya + "MUR": true, // Mauritius Rupee + "XUA": true, // ADB Unit of Account + "MXN": true, // Mexican Peso + "MXV": true, // Mexican Unidad de Inversion (UDI) + "MDL": true, // Moldovan Leu + "MNT": true, // Tugrik + "MAD": true, // Moroccan Dirham + "MZN": true, // Mozambique Metical + "MMK": true, // Kyat + "NAD": true, // Namibia Dollar + "NPR": true, // Nepalese Rupee + "NIO": true, // Cordoba Oro + "NGN": true, // Naira + "OMR": true, // Rial Omani + "PKR": true, // Pakistan Rupee + "PAB": true, // Balboa + "PGK": true, // Kina + "PYG": true, // Guarani + "PEN": true, // Sol + "PHP": true, // Philippine Peso + "PLN": true, // Zloty + "QAR": true, // Qatari Rial + "RON": true, // Romanian Leu + "RUB": true, // Russian Ruble + "RWF": true, // Rwanda Franc + "SHP": true, // Saint Helena Pound + "WST": true, // Tala + "STN": true, // Dobra + "SAR": true, // Saudi Riyal + "RSD": true, // Serbian Dinar + "SCR": true, // Seychelles Rupee + "SLL": true, // Leone + "SGD": true, // Singapore Dollar + "XSU": true, // Sucre + "SBD": true, // Solomon Islands Dollar + "SOS": true, // Somali Shilling + "SSP": true, // South Sudanese Pound + "LKR": true, // Sri Lanka Rupee + "SDG": true, // Sudanese Pound + "SRD": true, // Surinam Dollar + "SEK": true, // Swedish Krona + "CHE": true, // WIR Euro + "CHW": true, // WIR Franc + "SYP": true, // Syrian Pound + "TWD": true, // New Taiwan Dollar + "TJS": true, // Somoni + "TZS": true, // Tanzanian Shilling + "THB": true, // Baht + "TOP": true, // Pa'anga + "TTD": true, // Trinidad and Tobago Dollar + "TND": true, // Tunisian Dinar + "TRY": true, // Turkish Lira + "TMT": true, // Turkmenistan New Manat + "UGX": true, // Uganda Shilling + "UAH": true, // Hryvnia + "AED": true, // UAE Dirham + "USN": true, // US Dollar (Next day) + "UYU": true, // Peso Uruguayo + "UYI": true, // Uruguay Peso en Unidades Indexadas (UI) + "UYW": true, // Unidad Previsional + "UZS": true, // Uzbekistan Sum + "VUV": true, // Vatu + "VES": true, // Bolívar Soberano + "VND": true, // Dong + "YER": true, // Yemeni Rial + "ZMW": true, // Zambian Kwacha + "ZWL": true, // Zimbabwe Dollar + "XBA": true, // Bond Markets Unit European Composite Unit (EURCO) + "XBB": true, // Bond Markets Unit European Monetary Unit (E.M.U.-6) + "XBC": true, // Bond Markets Unit European Unit of Account 9 (E.U.A.-9) + "XBD": true, // Bond Markets Unit European Unit of Account 17 (E.U.A.-17) + "XTS": true, // Codes specifically reserved for testing purposes + "XXX": true, // The codes assigned for transactions where no currency is involved + "XAU": true, // Gold + "XPD": true, // Palladium + "XPT": true, // Platinum + "XAG": true, // Silver + "AFA": true, // Afghani + "FIM": true, // Markka + "ALK": true, // Old Lek + "ADP": true, // Andorran Peseta + "ESP": true, // Spanish Peseta + "FRF": true, // French Franc + "AOK": true, // Kwanza + "AON": true, // New Kwanza + "AOR": true, // Kwanza Reajustado + "ARA": true, // Austral + "ARP": true, // Peso Argentino + "ARY": true, // Peso + "RUR": true, // Russian Ruble + "ATS": true, // Schilling + "AYM": true, // Azerbaijan Manat + "AZM": true, // Azerbaijanian Manat + "BYB": true, // Belarusian Ruble + "BYR": true, // Belarusian Ruble + "BEC": true, // Convertible Franc + "BEF": true, // Belgian Franc + "BEL": true, // Financial Franc + "BOP": true, // Peso boliviano + "BAD": true, // Dinar + "BRB": true, // Cruzeiro + "BRC": true, // Cruzado + "BRE": true, // Cruzeiro + "BRN": true, // New Cruzado + "BRR": true, // Cruzeiro Real + "BGJ": true, // Lev A/52 + "BGK": true, // Lev A/62 + "BGL": true, // Lev + "BUK": true, // Kyat + "HRD": true, // Croatian Dinar + "CYP": true, // Cyprus Pound + "CSJ": true, // Krona A/53 + "CSK": true, // Koruna + "ECS": true, // Sucre + "ECV": true, // Unidad de Valor Constante (UVC) + "GQE": true, // Ekwele + "EEK": true, // Kroon + "XEU": true, // European Currency Unit (E.C.U) + "GEK": true, // Georgian Coupon + "DDM": true, // Mark der DDR + "DEM": true, // Deutsche Mark + "GHC": true, // Cedi + "GHP": true, // Ghana Cedi + "GRD": true, // Drachma + "GNE": true, // Syli + "GNS": true, // Syli + "GWE": true, // Guinea Escudo + "GWP": true, // Guinea-Bissau Peso + "ITL": true, // Italian Lira + "ISJ": true, // Old Krona + "IEP": true, // Irish Pound + "ILP": true, // Pound + "ILR": true, // Old Shekel + "LAJ": true, // Pathet Lao Kip + "LVL": true, // Latvian Lats + "LVR": true, // Latvian Ruble + "LSM": true, // Loti + "ZAL": true, // Financial Rand + "LTL": true, // Lithuanian Litas + "LTT": true, // Talonas + "LUC": true, // Luxembourg Convertible Franc + "LUF": true, // Luxembourg Franc + "LUL": true, // Luxembourg Financial Franc + "MGF": true, // Malagasy Franc + "MVQ": true, // Maldive Rupee + "MLF": true, // Mali Franc + "MTL": true, // Maltese Lira + "MTP": true, // Maltese Pound + "MRO": true, // Ouguiya + "MXP": true, // Mexican Peso + "MZE": true, // Mozambique Escudo + "MZM": true, // Mozambique Metical + "NLG": true, // Netherlands Guilder + "NIC": true, // Cordoba + "PEH": true, // Sol + "PEI": true, // Inti + "PES": true, // Sol + "PLZ": true, // Zloty + "PTE": true, // Portuguese Escudo + "ROK": true, // Leu A/52 + "ROL": true, // Old Leu + "STD": true, // Dobra + "CSD": true, // Serbian Dinar + "SKK": true, // Slovak Koruna + "SIT": true, // Tolar + "RHD": true, // Rhodesian Dollar + "ESA": true, // Spanish Peseta + "ESB": true, // "A" Account (convertible Peseta Account) + "SDD": true, // Sudanese Dinar + "SDP": true, // Sudanese Pound + "SRG": true, // Surinam Guilder + "CHC": true, // WIR Franc (for electronic) + "TJR": true, // Tajik Ruble + "TPE": true, // Timor Escudo + "TRL": true, // Old Turkish Lira + "TMM": true, // Turkmenistan Manat + "UGS": true, // Uganda Shilling + "UGW": true, // Old Shilling + "UAK": true, // Karbovanet + "SUR": true, // Rouble + "USS": true, // US Dollar (Same day) + "UYN": true, // Old Uruguay Peso + "UYP": true, // Uruguayan Peso + "VEB": true, // Bolivar + "VEF": true, // Bolivar Fuerte + "VNC": true, // Old Dong + "YDD": true, // Yemeni Dinar + "YUD": true, // New Yugoslavian Dinar + "YUM": true, // New Dinar + "YUN": true, // Yugoslavian Dinar + "ZRN": true, // New Zaire + "ZRZ": true, // Zaire + "ZMK": true, // Zambian Kwacha + "ZWC": true, // Rhodesian Dollar + "ZWD": true, // Zimbabwe Dollar (old) + "ZWN": true, // Zimbabwe Dollar (new) + "ZWR": true, // Zimbabwe Dollar + "XFO": true, // Gold-Franc + "XRE": true, // RINET Funds Code + "XFU": true, // UIC-Franc +} diff --git a/internal/iso4217/iso4217_gen.go b/internal/iso4217/iso4217_gen.go new file mode 100644 index 000000000..5cd51a2b7 --- /dev/null +++ b/internal/iso4217/iso4217_gen.go @@ -0,0 +1,133 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//go:build ignore +// +build ignore + +// Generates iso4217.go. +// +// This file grabs the ISO 4217 currency codes and writes them +// into source code so we don't rely on any external files (zip, +// json, etc). +// +// The data is pulled from datahub.io as the ISO.org site only offers +// XML. +// +// https://datahub.io/core/currency-codes +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "go/format" + "log" + "net/http" + "os" + "os/user" + "path/filepath" + "runtime" + "strings" + "time" +) + +var ( + downloadUrl = "https://datahub.io/core/currency-codes/r/codes-all.json" + outputFilename = filepath.Join("internal", "iso4217", "iso4217.go") + + windowsQuoteReplacer = strings.NewReplacer(`’`, `'`, `’`, `'`, `“`, `"`, `”`, `"`) +) + +// {"AlphabeticCode": "AFN", "Currency": "Afghani", ... } +type currency struct { + Code string `json:"AlphabeticCode"` + Name string `json:"Currency"` +} + +func main() { + when := time.Now().Format("2006-01-02T03:04:05Z") + who, err := user.Current() + if err != nil { + log.Fatalf("Unable to get user on %s", runtime.GOOS) + } + + // Write copyright header + var buf bytes.Buffer + fmt.Fprintf(&buf, `// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Generated on %s by %s, any modifications will be overwritten +package iso4217 +`, when, who.Username) + + // Download certs + resp, err := http.Get(downloadUrl) + if err != nil { + log.Fatalf("error while downloading %s: %v", downloadUrl, err) + } + defer resp.Body.Close() + + var currencies []currency + if err := json.NewDecoder(resp.Body).Decode(¤cies); err != nil { + log.Fatalf("error while parsing currency response: %v", err) + } + + // The JSON file contains duplicates so we need to dedup them.. + cs := make(map[string]bool, 150) + + // Write countries to source code + fmt.Fprintln(&buf, "var currencyCodes = map[string]bool{") + for i := range currencies { + code, name := currencies[i].Code, currencies[i].Name + if code == "" || name == "" { + fmt.Printf("SKIPPING: code=%q currency=%q\n", code, name) + continue + } + name = windowsQuoteReplacer.Replace(name) + if _, exists := cs[code]; !exists { + cs[code] = true // mark as seen + fmt.Fprintf(&buf, fmt.Sprintf(`"%s": true, // %s`+"\n", code, name)) + } + } + fmt.Fprintln(&buf, "}") + + // format source code and write file + out, err := format.Source(buf.Bytes()) + if err != nil { + fmt.Println(buf.String()) + log.Fatalf("error formatting output code, err=%v", err) + } + + err = os.WriteFile(outputFilename, out, 0644) + if err != nil { + log.Fatalf("error writing file, err=%v", err) + } +} diff --git a/internal/iso4217/validate.go b/internal/iso4217/validate.go new file mode 100644 index 000000000..13dc7ba8d --- /dev/null +++ b/internal/iso4217/validate.go @@ -0,0 +1,29 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package iso4217 + +import ( + "strings" +) + +// Valid returns successful if code is a valid ISO 4217 +// currency code. Example: USD +func Valid(code string) bool { + _, ok := currencyCodes[strings.ToUpper(code)] + return ok +} diff --git a/internal/iso4217/validate_test.go b/internal/iso4217/validate_test.go new file mode 100644 index 000000000..1ba959396 --- /dev/null +++ b/internal/iso4217/validate_test.go @@ -0,0 +1,40 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package iso4217 + +import ( + "testing" +) + +func TestValidate(t *testing.T) { + if !Valid("USD") { + t.Error("expected valid") + } + + if !Valid("eur") { + t.Error("expected valid") + } + + if Valid("") { + t.Errorf("invalid") + } + + if Valid("QZA") { + t.Errorf("invalid") + } +} diff --git a/internal/usabbrev/usabbrev.go b/internal/usabbrev/usabbrev.go new file mode 100644 index 000000000..2f7829fe6 --- /dev/null +++ b/internal/usabbrev/usabbrev.go @@ -0,0 +1,94 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package usabbrev + +import ( + "strings" +) + +var ( + // usStateAndTerritoryAbbreviations is a mapping of all commonly accepted + // two-letter abbreviations. + // Source: https://www.hud.gov/sites/documents/22211X2CHCH.PDF + usStateAndTerritoryAbbreviations = map[string]bool{ + "AK": true, // Alaska + "AL": true, // Alabama + "AR": true, // Arkansas + "AS": true, // American Samoa + "AZ": true, // Arizona + "CA": true, // California + "CO": true, // Colorado + "CT": true, // Connecticut + "DC": true, // District of Columbia + "DE": true, // Delaware + "FL": true, // Florida + // "FT": true, // Foreign (for CTS use) + "GA": true, // Georgia + "GU": true, // Guam + "HI": true, // Hawaii + "IA": true, // Iowa + "ID": true, // Idaho + "IL": true, // Illinois + "IN": true, // Indiana + "KS": true, // Kansas + "KY": true, // Kentucky + "LA": true, // Louisiana + "MA": true, // Massachusetts + "MD": true, // Maryland + "ME": true, // Maine + "MI": true, // Michigan + "MN": true, // Minnesota + "MO": true, // Missouri + "MP": true, // Northern Marianas Islands + "MS": true, // Mississippi + "MT": true, // Montana + "NC": true, // North Carolina + "ND": true, // North Dakota + "NE": true, // Nebraska + "NH": true, // New Hampshire + "NJ": true, // New Jersey + "NM": true, // New Mexico + "NV": true, // Nevada + "NY": true, // New York + "OH": true, // Ohio + "OK": true, // Oklahoma + "OR": true, // Oregon + "PA": true, // Pennsylvania + "PR": true, // Puerto Rico + "RI": true, // Rhode Island + "SC": true, // South Carolina + "SD": true, // South Dakota + "TN": true, // Tennessee + "TX": true, // Texas + "UT": true, // Utah + "VA": true, // Virginia + "VI": true, // Virgin Islands + "VT": true, // Vermont + "WA": true, // Washington + "WI": true, // Wisconsin + "WV": true, // West Virginia + "WY": true, // Wyoming + } +) + +// Valid returns true if code is a valid abbreviation for a United States territory or state. +// Example: UT (Utah) or GU (Guam) +func Valid(code string) bool { + _, ok := usStateAndTerritoryAbbreviations[strings.ToUpper(code)] + return ok +} diff --git a/internal/usabbrev/usabbrev_test.go b/internal/usabbrev/usabbrev_test.go new file mode 100644 index 000000000..ad24b607f --- /dev/null +++ b/internal/usabbrev/usabbrev_test.go @@ -0,0 +1,34 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package usabbrev + +import ( + "testing" +) + +func TestUSAbbrev(t *testing.T) { + if !Valid("WA") { + t.Error("WA (Washington) is a sate") + } + if !Valid("GU") { + t.Error("GU (Guam) is a territory") + } + if Valid("XX") { + t.Error("XX is not a valid US state or territory") + } +} diff --git a/makefile b/makefile new file mode 100644 index 000000000..38814d50d --- /dev/null +++ b/makefile @@ -0,0 +1,138 @@ +PLATFORM=$(shell uname -s | tr '[:upper:]' '[:lower:]') +VERSION := $(shell grep -Eo '(v[0-9]+[\.][0-9]+[\.][0-9]+([-a-zA-Z0-9]*)?)' version.go) + +.PHONY: build generate docker release + +build: + go fmt ./... + @mkdir -p ./bin/ + go build github.com/moov-io/ach + go build -o bin/examples-http github.com/moov-io/ach/examples/http + CGO_ENABLED=0 go build -o ./bin/server github.com/moov-io/ach/cmd/server + +build-webui: + cp $(shell go env GOROOT)/misc/wasm/wasm_exec.js ./cmd/webui/assets/wasm_exec.js + GOOS=js GOARCH=wasm go build -o ./cmd/webui/assets/ach.wasm github.com/moov-io/ach/cmd/webui/ach/ + CGO_ENABLED=0 go build -o ./bin/webui ./cmd/webui + +generate: clean + @go run internal/iso3166/iso3166_gen.go + @go run internal/iso4217/iso4217_gen.go + +clean: + @rm -rf ./bin/ ./tmp/ coverage.txt misspell* staticcheck lint-project.sh + +.PHONY: check +check: +ifeq ($(OS),Windows_NT) + @echo "Skipping checks on Windows, currently unsupported." +else + @wget -O lint-project.sh https://raw.githubusercontent.com/moov-io/infra/master/go/lint-project.sh + @chmod +x ./lint-project.sh + GOOS=js GOARCH=wasm GOCYCLO_LIMIT=26 COVER_THRESHOLD=90.0 time ./lint-project.sh +endif + +check-openapi: + docker run \ + -v ${PWD}/openapi.yaml:/projects/openapi.yaml \ + wework/speccy lint --verbose /projects/openapi.yaml + +.PHONY: client +client: +ifeq ($(OS),Windows_NT) + @echo "Please generate client on macOS or Linux, currently unsupported on windows." +else +# Versions from https://github.com/OpenAPITools/openapi-generator/releases + @chmod +x ./openapi-generator + @rm -rf ./client + OPENAPI_GENERATOR_VERSION=5.1.1 ./openapi-generator generate --package-name client -i ./openapi.yaml -g go -o ./client + rm -f ./client/go.mod ./client/go.sum + go fmt ./... + go build github.com/moov-io/ach/client + go test ./client +endif + +dist: clean generate build +ifeq ($(OS),Windows_NT) + CGO_ENABLED=1 GOOS=windows go build -o bin/achcli.exe github.com/moov-io/ach/cmd/achcli + CGO_ENABLED=1 GOOS=windows go build -o bin/ach.exe github.com/moov-io/ach/cmd/server +else + CGO_ENABLED=0 GOOS=$(PLATFORM) go build -o bin/achcli-$(PLATFORM)-amd64 github.com/moov-io/ach/cmd/achcli + CGO_ENABLED=0 GOOS=$(PLATFORM) go build -o bin/ach-$(PLATFORM)-amd64 github.com/moov-io/ach/cmd/server +endif + +docker: clean docker-hub docker-openshift docker-fuzz docker-webui + +docker-hub: + docker build --pull -t moov/ach:$(VERSION) -f Dockerfile . + docker tag moov/ach:$(VERSION) moov/ach:latest + +docker-openshift: + docker build --pull -t quay.io/moov/ach:$(VERSION) -f Dockerfile-openshift --build-arg VERSION=$(VERSION) . + docker tag quay.io/moov/ach:$(VERSION) quay.io/moov/ach:latest + +docker-fuzz: + docker build --pull -t moov/achfuzz:$(VERSION) . -f Dockerfile-fuzz + docker tag moov/achfuzz:$(VERSION) moov/achfuzz:latest + +docker-webui: + docker build --pull -t moov/ach-webui:$(VERSION) -f Dockerfile-webui . + docker tag moov/ach-webui:$(VERSION) moov/ach-webui:latest + +.PHONY: clean-integration test-integration + +clean-integration: + docker-compose kill + docker-compose rm -v -f + +test-integration: clean-integration + docker-compose up -d + sleep 5 + curl -v http://localhost:8080/files + +release: docker generate AUTHORS + go test ./... + git tag -f $(VERSION) + +release-push: + docker push moov/ach:$(VERSION) + docker push moov/ach:latest + docker push moov/achfuzz:$(VERSION) + docker push moov/ach-webui:$(VERSION) + +quay-push: + docker push quay.io/moov/ach:$(VERSION) + docker push quay.io/moov/ach:latest + +.PHONY: cover-test cover-web +cover-test: + go test -coverprofile=cover.out ./... +cover-web: + go tool cover -html=cover.out + +# From https://github.com/genuinetools/img +.PHONY: AUTHORS +AUTHORS: + @$(file >$@,# This file lists all individuals having contributed content to the repository.) + @$(file >>$@,# For how it is generated, see `make AUTHORS`.) + @echo "$(shell git log --format='\n%aN <%aE>' | LC_ALL=C.UTF-8 sort -uf)" >> $@ + +.PHONY: tagged-release +tagged-release: + @./tagged-release.sh $(VERSION) + +.PHONY: fuzz +fuzz: + docker run moov/achfuzz:latest + +.PHONY: legal +legal: +ifeq ($(OS),Linux) + @wget -q -nc https://github.com/elastic/go-licenser/releases/download/v0.3.0/go-licenser_0.3.0_Linux_x86_64.tar.gz + @tar xf go-licenser_0.3.0_Linux_x86_64.tar.gz +else + @wget -q -nc https://github.com/elastic/go-licenser/releases/download/v0.3.0/go-licenser_0.3.0_Darwin_x86_64.tar.gz + @tar xf go-licenser_0.3.0_Darwin_x86_64.tar.gz +endif + ./go-licenser -license ASL2 -licensor 'The Moov Authors' -notice + @git checkout README.md LICENSE diff --git a/merge.go b/merge.go new file mode 100644 index 000000000..17470523b --- /dev/null +++ b/merge.go @@ -0,0 +1,295 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "time" +) + +const NACHAFileLineLimit = 10000 + +// MergeFiles is a helper function for consolidating an array of ACH Files into as few files +// as possible. This is useful for optimizing cost and network efficiency. +// +// This operation will override batch numbers in each file to ensure they do not collide. +// The ascending batch numbers will start at 1. +// +// Duplicate TraceNumbers will not be allowed in the same file. Multiple files will be created. +// +// Per NACHA rules files must remain under 10,000 lines (when rendered in their ASCII encoding) +// +// File Batches can only be merged if they are unique and routed to and from the same ABA routing numbers. +func MergeFiles(files []*File) ([]*File, error) { + return mergeFilesHelper(files, Conditions{ + MaxLines: NACHAFileLineLimit, + }) +} + +type Conditions struct { + // MaxLines will limit each merged files line count. + MaxLines int `json:"maxLines"` + + // MaxDollarAmount will limit each merged file's total dollar amount. + MaxDollarAmount int64 `json:"maxDollarAmount"` +} + +func MergeFilesWith(files []*File, conditions Conditions) ([]*File, error) { + return mergeFilesHelper(files, conditions) +} + +func mergeFilesHelper(files []*File, conditions Conditions) ([]*File, error) { + fs := &mergableFiles{infiles: files} + for i := range fs.infiles { + if fs.infiles[i] == nil { + continue // skip nil Files + } + outf := fs.findOutfile(fs.infiles[i]) + for j := range fs.infiles[i].Batches { + batchExistsInMerged := false + for k := range outf.Batches { + if fs.infiles[i].Batches[j].Equal(outf.Batches[k]) { + batchExistsInMerged = true + + batch, err := mergeEntries(outf.Batches[k], fs.infiles[i].Batches[j]) + if err != nil { + return nil, err + } + outf.Batches[k] = batch + break + } + } + if !batchExistsInMerged { + outf.AddBatch(fs.infiles[i].Batches[j]) + if err := outf.Create(); err != nil { + return nil, err + } + + fileTooLong := (conditions.MaxLines > 0 && (lineCount(outf) > conditions.MaxLines)) + fileTooExpensive := (conditions.MaxDollarAmount > 0 && (dollarAmount(outf) > conditions.MaxDollarAmount)) + + // Split into a new file if needed + if fileTooLong || fileTooExpensive { + // In the event of a file with one batch and one entry just keep it + if len(outf.Batches) > 1 || len(outf.Batches[0].GetEntries()) > 1 { + outf.RemoveBatch(fs.infiles[i].Batches[j]) + if err := outf.Create(); err != nil { // rebalance ACH file after removing the Batch + return nil, err + } + f := *outf + fs.locMaxed = append(fs.locMaxed, &f) + } + + outf = fs.swapLocMaxedFile(outf) // replace output file with the one we just created + + outf.AddBatch(fs.infiles[i].Batches[j]) + if err := outf.Create(); err != nil { + return nil, err + } + } + } + } + } + + // Return LOC-maxed files and merged files + out := append(fs.locMaxed, fs.outfiles...) + + // Override batch numbers to ensure they don't collide + for _, f := range out { + for i, b := range f.Batches { + b.GetHeader().BatchNumber = i + 1 + b.GetControl().BatchNumber = i + 1 + // Tabulate each Batch after combining them + if err := b.Create(); err != nil { + return out, err + } + } + // Tabulate the files before returning them + if err := f.Create(); err != nil { + return out, err + } + } + return out, nil +} + +type mergableFiles struct { + infiles []*File + outfiles []*File + locMaxed []*File +} + +// swapLocMaxedFile replaces an ACH file that is over the Nacha line limit with an empty file containing +// a matching FileHeader record. This allows future iterations inside of MergeFiles to append +func (fs *mergableFiles) swapLocMaxedFile(f *File) *File { + now := time.Now() + + // remove the current file from outfiles + for i := range fs.outfiles { + if fs.outfiles[i].Header.ImmediateDestination == f.Header.ImmediateDestination && + fs.outfiles[i].Header.ImmediateOrigin == f.Header.ImmediateOrigin { + // found a matching file, so remove it from fs.outfiles + fs.outfiles = append(fs.outfiles[:i], fs.outfiles[i+1:]...) + goto next + } + } +next: + out := NewFile() + out.Header = f.Header + out.Header.FileCreationDate = now.Format("060102") // YYMMDD + out.Header.FileCreationTime = now.Format("1504") // HHmm + out.SetValidation(f.validateOpts) + out.Create() + fs.outfiles = append(fs.outfiles, out) // add the new outfile + + return out +} + +// findOutfile optionally returns a File from fs.files if the FileHeaders match. +// This is done because we append batches into files to minimize the count of output files. +// +// findOutfile will return the existing file (stored in outfiles) if no matching file exists. +func (fs *mergableFiles) findOutfile(f *File) *File { + var lookup func(int) *File + lookup = func(start int) *File { + // To allow recursive lookups we need to memorize the current index so deeper calls + // will bypass files with conflicting trace numbers. + for i := start; i < len(fs.outfiles); i++ { + if fs.outfiles[i].Header.ImmediateDestination == f.Header.ImmediateDestination && + fs.outfiles[i].Header.ImmediateOrigin == f.Header.ImmediateOrigin { + + // found a matching file, so verify the TraceNumber isn't alreay inside + for inB := range f.Batches { + inEntries := f.Batches[inB].GetEntries() + for inE := range inEntries { + // Compare against outfiles + for outB := range fs.outfiles[i].Batches { + outEntries := fs.outfiles[i].Batches[outB].GetEntries() + for outE := range outEntries { + // If any of our incoming trace numbers match the existing merged file + // return the entire file as separate. This keeps partially overlapping + // batches self-contained. + if inEntries[inE].TraceNumber == outEntries[outE].TraceNumber { + return lookup(i + 1) + } + } + } + } + } + + // No conflicting TraceNumber was found, so return current merge file + return fs.outfiles[i] + } + } + // Record a newly mergable File/FileHeader we can use in future merge attempts + outf := NewFile() + outf.Header = f.Header + outf.SetValidation(f.validateOpts) + outf.Control = f.Control + fs.outfiles = append(fs.outfiles, outf) + return outf + } + return lookup(0) +} + +func mergeEntries(b1, b2 Batcher) (Batcher, error) { + b, _ := NewBatch(b1.GetHeader()) + entries := sortEntriesByTraceNumber(append(b1.GetEntries(), b2.GetEntries()...)) + for i := range entries { + b.AddEntry(entries[i]) + } + b.SetControl(b1.GetControl()) + if err := b.Create(); err != nil { + return nil, err + } + return b, nil +} + +func lineCount(f *File) int { + lines := 2 // FileHeader, FileControl + for i := range f.Batches { + lines += 2 // BatchHeader, BatchControl + entries := f.Batches[i].GetEntries() + for j := range entries { + lines++ + if entries[j].Addenda02 != nil { + lines++ + } + lines += len(entries[j].Addenda05) + if entries[j].Addenda98 != nil { + lines++ + } + if entries[j].Addenda99 != nil { + lines++ + } + if entries[j].Addenda99Dishonored != nil { + lines++ + } + if entries[j].Addenda99Contested != nil { + lines++ + } + } + } + for i := range f.IATBatches { + lines += 2 // IATBatchHeader, BatchControl + for j := range f.IATBatches[i].Entries { + lines++ + if f.IATBatches[i].Entries[j].Addenda10 != nil { + lines++ + } + if f.IATBatches[i].Entries[j].Addenda11 != nil { + lines++ + } + if f.IATBatches[i].Entries[j].Addenda12 != nil { + lines++ + } + if f.IATBatches[i].Entries[j].Addenda13 != nil { + lines++ + } + if f.IATBatches[i].Entries[j].Addenda14 != nil { + lines++ + } + if f.IATBatches[i].Entries[j].Addenda15 != nil { + lines++ + } + if f.IATBatches[i].Entries[j].Addenda16 != nil { + lines++ + } + + lines += len(f.IATBatches[i].Entries[j].Addenda17) + lines += len(f.IATBatches[i].Entries[j].Addenda18) + + if f.IATBatches[i].Entries[j].Addenda98 != nil { + lines++ + } + if f.IATBatches[i].Entries[j].Addenda99 != nil { + lines++ + } + } + } + return lines +} + +func dollarAmount(f *File) int64 { + var total int64 + for i := range f.Batches { + entries := f.Batches[i].GetEntries() + for j := range entries { + total += int64(entries[j].Amount) + } + } + return total +} diff --git a/merge_test.go b/merge_test.go new file mode 100644 index 000000000..b8f5df6af --- /dev/null +++ b/merge_test.go @@ -0,0 +1,462 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "crypto/rand" + "fmt" + "math/big" + "path/filepath" + "strconv" + "testing" + + "github.com/stretchr/testify/require" +) + +func filesAreEqual(f1, f2 *File) error { + // File Header + if f1.Header.ImmediateOrigin != f2.Header.ImmediateOrigin { + return fmt.Errorf("f1.Header.ImmediateOrigin=%s vs f2.Header.ImmediateOrigin=%s", f1.Header.ImmediateOrigin, f2.Header.ImmediateOrigin) + } + if f1.Header.ImmediateDestination != f2.Header.ImmediateDestination { + return fmt.Errorf("f1.Header.ImmediateDestination=%s vs f2.Header.ImmediateDestination=%s", f1.Header.ImmediateDestination, f2.Header.ImmediateDestination) + } + + // Batches + if len(f1.Batches) != len(f2.Batches) { + return fmt.Errorf("len(f1.Batches)=%d vs len(f2.Batches)=%d", len(f1.Batches), len(f2.Batches)) + } + for i := range f1.Batches { + for j := range f2.Batches { + if f1.Batches[i].Equal(f2.Batches[j]) { + goto next + } + } + return fmt.Errorf("unable to find batch in f2: %v", f1.Batches[i]) + next: + // check the next batch + } + + // IATBatches + if len(f1.IATBatches) != len(f2.IATBatches) { + return fmt.Errorf("len(f1.IATBatches)=%d vs len(f2.IATBatches)=%d", len(f1.IATBatches), len(f2.IATBatches)) + } + + // File Control + if f1.Control.EntryAddendaCount != f2.Control.EntryAddendaCount { + return fmt.Errorf("f1.Control.EntryAddendaCount=%d vs f2.Control.EntryAddendaCount=%d", f1.Control.EntryAddendaCount, f2.Control.EntryAddendaCount) + } + if f1.Control.TotalDebitEntryDollarAmountInFile != f2.Control.TotalDebitEntryDollarAmountInFile { + return fmt.Errorf("f1.Control.TotalDebitEntryDollarAmountInFile=%d vs f2.Control.TotalDebitEntryDollarAmountInFile=%d", f1.Control.TotalDebitEntryDollarAmountInFile, f2.Control.TotalDebitEntryDollarAmountInFile) + } + if f1.Control.TotalCreditEntryDollarAmountInFile != f2.Control.TotalCreditEntryDollarAmountInFile { + return fmt.Errorf("f1.Control.TotalCreditEntryDollarAmountInFile=%d vs f2.Control.TotalCreditEntryDollarAmountInFile=%d", f1.Control.TotalCreditEntryDollarAmountInFile, f2.Control.TotalCreditEntryDollarAmountInFile) + } + + return nil +} + +func TestMergeFiles__filesAreEqual(t *testing.T) { + file, err := readACHFilepath(filepath.Join("test", "testdata", "ppd-debit.ach")) + if err != nil { + t.Fatal(err) + } + + // compare a file against itself + if err := filesAreEqual(file, file); err != nil { + t.Fatalf("same file: %v", err) + } + + // break the equality + f2 := *file + f2.Header.ImmediateOrigin = "12" + if err := filesAreEqual(file, &f2); err == nil { + t.Fatal("expected error") + } +} + +func TestMergeFiles__identity(t *testing.T) { + file, err := readACHFilepath(filepath.Join("test", "testdata", "ppd-debit.ach")) + if err != nil { + t.Fatal(err) + } + + out, err := MergeFiles([]*File{file}) + if err != nil { + t.Fatal(err) + } + + if len(out) != 1 { + t.Errorf("got %d merged ACH files", len(out)) + } + + if err := filesAreEqual(file, out[0]); err != nil { + t.Errorf("unequal files:%v", err) + } + + for _, f := range out { + if err := f.Validate(); err != nil { + t.Fatalf("invalid file: %v", err) + } + } + +} + +func TestMergeFiles__together(t *testing.T) { + f1, err := readACHFilepath(filepath.Join("test", "testdata", "ppd-debit.ach")) + if err != nil { + t.Fatal(err) + } + f2, err := readACHFilepath(filepath.Join("test", "testdata", "web-debit.ach")) + if err != nil { + t.Fatal(err) + } + f2.Header = f1.Header // replace Header so they're merged into one file + + if len(f1.Batches) != 1 || len(f2.Batches) != 3 { + t.Errorf("did batch counts change? f1:%d f2:%d", len(f1.Batches), len(f2.Batches)) + } + + out, err := MergeFiles([]*File{f1, f2}) + if err != nil { + t.Fatal(err) + } + + if len(out) != 1 { + t.Errorf("got %d merged ACH files", len(out)) + } + if len(out[0].Batches) != 4 { + t.Errorf("got %d batches", len(out[0].Batches)) + } + + for _, f := range out { + if err := f.Validate(); err != nil { + t.Fatalf("invalid file: %v", err) + } + } + +} + +func TestMergeFiles__apart(t *testing.T) { + f1, err := readACHFilepath(filepath.Join("test", "testdata", "ppd-debit.ach")) + if err != nil { + t.Fatal(err) + } + f2, err := readACHFilepath(filepath.Join("test", "testdata", "web-debit.ach")) + if err != nil { + t.Fatal(err) + } + + out, err := MergeFiles([]*File{f1, f2}) + if err != nil { + t.Fatal(err) + } + + if len(out) != 2 { + t.Errorf("got %d merged ACH files", len(out)) + } + if len(out[0].Batches) != 1 { + t.Errorf("got %d batches", len(out[0].Batches)) + } + if len(out[1].Batches) != 3 { + t.Errorf("got %d batches", len(out[1].Batches)) + } + + for _, f := range out { + if err := f.Validate(); err != nil { + t.Fatalf("invalid file: %v", err) + } + } +} + +func BenchmarkMergeFiles__lineCount(b *testing.B) { + newACHFile := func() *File { + // Nacha files have a max of 10,000 lines and a batch is + // a header, entries, and control. + batches, err := rand.Int(rand.Reader, big.NewInt(3000)) + if err != nil { + b.Fatal(err) + } + + file := NewFile() + file.SetHeader(mockFileHeader()) + file.Control = mockFileControl() + + for i := 0; i < int(batches.Int64()+1); i++ { + file.AddBatch(mockBatchPPD()) + } + if err := file.Create(); err != nil { + b.Fatal(err) + } + return file + } + + for i := 0; i < b.N; i++ { + b.StopTimer() // pause timer so we can init our ACH file + file := newACHFile() + b.StartTimer() // resume benchmark + + // Count lines in our file + lineCount(file) + } +} + +func TestMergeFiles__lineCount(t *testing.T) { + file, err := readACHFilepath(filepath.Join("test", "testdata", "ppd-debit.ach")) + if err != nil { + t.Fatal(err) + } + if err := file.Create(); err != nil { + t.Fatal(err) + } + + if n := lineCount(file); n != 5 { + // We've optimized small file line counts to bypass writing out the file + // into plain text as it's costly. + t.Errorf("did we change optimizations? n=%d", n) + } + + // Add 100 batches to file and get a real line count + populateFileWithMockBatches(t, 100, file) + + if err := file.Create(); err != nil { + t.Fatal(err) + } + if n := lineCount(file); n != 305 { + t.Errorf("unexpected line count of %d", n) + } + + // Remove BatchCount and still properly count lines + file.Control.BatchCount = 0 + if n := lineCount(file); n != 305 { + t.Errorf("unexpected error n=%d", n) + } +} + +// TestMergeFiles__splitFiles generates a file over the 10k line limit and attempts to merge +// another file into it only to come away with two files after merging. +func TestMergeFiles__splitFiles(t *testing.T) { + file, err := readACHFilepath(filepath.Join("test", "testdata", "ppd-debit.ach")) + if err != nil { + t.Fatal(err) + } + file.Control = NewFileControl() + if err := file.Create(); err != nil { + t.Fatal(err) + } + if len(file.Batches) != 1 { + t.Fatalf("unexpected batch count of %d", len(file.Batches)) + } + + // Add a bunch of batches so it's over the line limit + // somewhere between 3-4k Batches exceed the 10k line limit + populateFileWithMockBatches(t, 4000, file) + + if err := file.Create(); err != nil { + t.Fatal(err) + } + + // Read another file to merge + f2, err := readACHFilepath(filepath.Join("test", "testdata", "web-debit.ach")) + if err != nil { + t.Fatal(err) + } + f2.Header = file.Header // replace Header so they're merged into one file + if err := f2.Create(); err != nil { + t.Fatal(err) + } + + // read a third file + f3, err := readACHFilepath(filepath.Join("test", "testdata", "20110805A.ach")) + if err != nil { + t.Fatal(err) + } + f3.Header = file.Header // replace Header so they're merged into one file + if err := f3.Create(); err != nil { + t.Fatal(err) + } + + traceNumbersBefore := countTraceNumbers(file, f2, f3) + + out, err := MergeFiles([]*File{file, f2, f3}) + if err != nil || len(out) != 1 { + t.Fatalf("got %d files, error=%v", len(out), err) + } + if n := len(out[0].Batches); n != 2006 { + t.Fatalf("out[0] has %d batches", n) + } + + traceNumbersAfter := countTraceNumbers(out...) + if traceNumbersBefore != traceNumbersAfter { + t.Fatalf("found %d of %d trace numbers", traceNumbersBefore, traceNumbersAfter) + } + + for _, f := range out { + if err := f.Validate(); err != nil { + t.Fatalf("invalid file: %v", err) + } + min, err := f.FlattenBatches() + if err != nil { + t.Fatal(err) + } + if err := min.Validate(); err != nil { + t.Fatal(err) + } + } +} + +func TestMergeFiles__dollarAmount(t *testing.T) { + file, err := readACHFilepath(filepath.Join("test", "testdata", "ppd-debit.ach")) + require.NoError(t, err) + require.NoError(t, file.Create()) + + if n := lineCount(file); n != 5 { + // We've optimized small file line counts to bypass writing out the file + // into plain text as it's costly. + t.Errorf("did we change optimizations? n=%d", n) + } + + // Add 100 batches to file and get a real line count + populateFileWithMockBatches(t, 100, file) + + // Verify our file's contents + require.NoError(t, file.Create()) + require.Equal(t, 305, lineCount(file)) + require.Equal(t, 101, countTraceNumbers(file)) + + mergedFiles, err := MergeFilesWith([]*File{file}, Conditions{ + MaxDollarAmount: 1000000, // $10,000.00 + }) + require.NoError(t, err) + require.Len(t, mergedFiles, 51) + require.Equal(t, 101, countTraceNumbers(mergedFiles...)) + + for i := range mergedFiles { + // With our static cases each file has one Batch + require.Equal(t, 1, len(mergedFiles[i].Batches)) + + entryCount := len(mergedFiles[i].Batches[0].GetEntries()) + if i == 0 { + require.Equal(t, 1, entryCount) + } else { + require.Equal(t, 2, entryCount) + } + } +} + +func TestMergeFiles__dollarAmount2(t *testing.T) { + file, err := readACHFilepath(filepath.Join("test", "testdata", "ppd-debit.ach")) + require.NoError(t, err) + require.NoError(t, file.Create()) + + if n := lineCount(file); n != 5 { + // We've optimized small file line counts to bypass writing out the file + // into plain text as it's costly. + t.Errorf("did we change optimizations? n=%d", n) + } + + // Add 100 batches to file and get a real line count + populateFileWithMockBatches(t, 100, file) + + // Verify our file's contents + require.NoError(t, file.Create()) + require.Equal(t, 305, lineCount(file)) + require.Equal(t, 101, countTraceNumbers(file)) + + mergedFiles, err := MergeFilesWith([]*File{file}, Conditions{ + MaxDollarAmount: 33_000_000_00, + }) + require.NoError(t, err) + require.Len(t, mergedFiles, 3) + require.Equal(t, 101, countTraceNumbers(mergedFiles...)) + + for i := range mergedFiles { + // With our static cases each file has one Batch + require.Equal(t, 17, len(mergedFiles[i].Batches)) + + entryCount := len(mergedFiles[i].Batches[0].GetEntries()) + if i == 0 { + require.Equal(t, 1, entryCount) + } else { + require.Equal(t, 2, entryCount) + } + } +} + +func countTraceNumbers(files ...*File) int { + var total int + for f := range files { + for b := range files[f].Batches { + total += len(files[f].Batches[b].GetEntries()) + } + } + return total +} + +func TestMergeFiles__invalid(t *testing.T) { + f1, err := readACHFilepath(filepath.Join("test", "testdata", "ppd-debit.ach")) + if err != nil { + t.Fatal(err) + } + f1.Header.ImmediateOrigin = "0000000000" // make file invalid + + f2, err := readACHFilepath(filepath.Join("test", "testdata", "web-debit.ach")) + if err != nil { + t.Fatal(err) + } + f2.Header = f1.Header + + out, err := MergeFiles([]*File{f1, f2}) + if len(out) != 0 || err == nil { + t.Errorf("expected error: len(out)=%d error=%v", len(out), err) + } +} + +func populateFileWithMockBatches(t *testing.T, numBatches int, file *File) { + lastBatchIdx := len(file.Batches) - 1 + var startSeq = file.Batches[lastBatchIdx].GetHeader().BatchNumber + 1 + var entryDetail = file.Batches[0].GetEntries()[0] + + for i := startSeq; i < (numBatches + startSeq); i++ { + header := mockBatchHeader() + header.StandardEntryClassCode = "PPD" + header.ServiceClassCode = 225 + header.CompanyName = "Example Company" + header.CompanyIdentification = "132465" + header.CompanyEntryDescription = "Example Description" + header.ODFIIdentification = "12104288" + batch, err := NewBatch(header) + if err != nil { + t.Fatal(err) + } + + ed := *entryDetail + n, _ := strconv.Atoi(ed.TraceNumber) + ed.TraceNumber = strconv.Itoa(n + i + 1e5) + batch.AddEntry(&ed) + + batch.GetHeader().BatchNumber = i + batch.GetControl().BatchNumber = i + + if err := batch.Create(); err != nil { + t.Fatal(err) + } + + file.AddBatch(batch) + } +} diff --git a/openapi-generator b/openapi-generator new file mode 100755 index 000000000..77c5887ac --- /dev/null +++ b/openapi-generator @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# +# From https://github.com/OpenAPITools/openapi-generator +# +#### +# Save as openapi-generator-cli on your PATH. chmod u+x. Enjoy. +# +# This script will query github on every invocation to pull the latest released version +# of openapi-generator. +# +# If you want repeatable executions, you can explicitly set a version via +# OPENAPI_GENERATOR_VERSION +# e.g. (in Bash) +# export OPENAPI_GENERATOR_VERSION=3.1.0 +# openapi-generator-cli.sh +# or +# OPENAPI_GENERATOR_VERSION=3.1.0 openapi-generator-cli.sh +# +# This is also helpful, for example, if you want want to evaluate a SNAPSHOT version. +# +# NOTE: Jars are downloaded on demand from maven into the same directory as this script +# for every 'latest' version pulled from github. Consider putting this under its own directory. +#### +set -o pipefail + +for cmd in {mvn,python,curl}; do + if ! command -v ${cmd} > /dev/null; then + >&2 echo "This script requires '${cmd}' to be installed." + exit 1 + fi +done + +function latest.tag { + local uri="https://api.github.com/repos/${1}/tags" + curl -s ${uri} | python -c "import sys, json; print json.load(sys.stdin)[0]['name'][1:]" +} + +ghrepo=openapitools/openapi-generator +groupid=org.openapitools +artifactid=openapi-generator-cli +ver=${OPENAPI_GENERATOR_VERSION:-$(latest.tag $ghrepo)} + +jar=${artifactid}-${ver}.jar +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +if [ ! -f ${DIR}/${jar} ]; then + repo="central::default::https://repo1.maven.org/maven2/" + if [[ ${ver} =~ ^.*-SNAPSHOT$ ]]; then + repo="central::default::https://oss.sonatype.org/content/repositories/snapshots" + fi + mvn org.apache.maven.plugins:maven-dependency-plugin:2.9:get \ + -DremoteRepositories=${repo} \ + -Dartifact=${groupid}:${artifactid}:${ver} \ + -Dtransitive=false \ + -Ddest=${DIR}/${jar} +fi + +java -ea \ + ${JAVA_OPTS} \ + -Xms512M \ + -Xmx1024M \ + -server \ + -jar ${DIR}/${jar} "$@" \ No newline at end of file diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 000000000..482134c71 --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,2278 @@ +openapi: 3.0.2 +info: + description: Moov ACH ([Automated Clearing House](https://en.wikipedia.org/wiki/Automated_Clearing_House)) implements an HTTP API for creating, parsing, and validating ACH files. ACH is the primary method of electronic money movement throughout the United States. + version: v1 + title: ACH API + contact: + url: https://github.com/moov-io/ach + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + +servers: + - url: http://localhost:8080 + description: Local development + +tags: + - name: 'ACH Files' + description: | + File contains the structures of an ACH File. It contains one and only one File Header and File Control with at least one Batch. + Batch objects within Files hold the Batch Header and Batch Control and all Entry Records and Addenda Records for the Batch. + +paths: + /ping: + get: + tags: ['ACH Files'] + summary: Ping ACH service + description: Check if the ACH service is running. + operationId: ping + responses: + '200': + description: Service is running properly + /files: + get: + tags: ['ACH Files'] + summary: List Files + description: List all ACH Files created with the ACH service. These Files are not persisted through multiple runs of the service. + operationId: getFiles + parameters: + - name: X-Request-ID + in: header + description: Optional Request ID allows application developer to trace requests through the system's logs + example: "rs4f9915" + schema: + type: string + responses: + '200': + description: A list of File objects + headers: + X-Total-Count: + description: The total number of Originators + schema: + type: integer + content: + application/json: + schema: + $ref: '#/components/schemas/Files' + /files/{fileID}: + post: + tags: ['ACH Files'] + summary: Create File + description: Create a new File object from either the plaintext or JSON representation. + operationId: createFile + parameters: + - name: X-Request-ID + in: header + description: Optional Request ID allows application developer to trace requests through the system's logs + example: "rs4f9915" + schema: + type: string + - name: X-Idempotency-Key + in: header + description: Idempotent key in the header which expires after 24 hours. These strings should contain enough entropy to not collide with each other in your requests. + example: "a4f88150" + required: false + schema: + type: string + - name: fileID + in: path + description: File ID + required: true + schema: + type: string + example: "3f2d23ee214" + - name: requireABAOrigin + in: query + description: Optional parameter to configure ImmediateOrigin validation + schema: + type: boolean + - name: bypassOrigin + in: query + description: Optional parameter to configure ImmediateOrigin validation + schema: + type: boolean + - name: bypassDestination + in: query + description: Optional parameter to configure ImmediateDestination validation + schema: + type: boolean + - name: customTraceNumbers + in: query + description: Optional parameter to configure ImmediateDestination validation + schema: + type: boolean + - name: allowZeroBatches + in: query + description: Optional parameter to configure ImmediateDestination validation + schema: + type: boolean + - name: allowMissingFileHeader + in: query + description: Optional parameter to configure ImmediateDestination validation + schema: + type: boolean + - name: allowMissingFileControl + in: query + description: Optional parameter to configure ImmediateDestination validation + schema: + type: boolean + - name: bypassCompanyIdentificationMatch + in: query + description: Optional parameter to configure ImmediateDestination validation + schema: + type: boolean + - name: customReturnCodes + in: query + description: Optional parameter to configure ImmediateDestination validation + schema: + type: boolean + - name: unequalServiceClassCode + in: query + description: Optional parameter to configure ImmediateDestination validation + schema: + type: boolean + requestBody: + description: Content of the ACH file (in json or raw text) + required: true + content: + text/plain: + schema: + description: A plaintext ACH file + type: string + example: | + 101 23138010401210428821906240000A094101Federal Reserve Bank My Bank Name + 5225Name on Account 121042882 PPDREG.SALARY 190625 1121042880000001 + 62723138010412345678 0100000000 Receiver Account Name 0121042880000001 + 82250000010023138010000100000000000000000000121042882 121042880000001 + 9000001000001000000010023138010000100000000000000000000 + 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 + 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 + 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 + 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 + 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 + application/json: + schema: + $ref: '#/components/schemas/CreateFile' + responses: + '200': + description: A JSON object containing a new File + headers: + Location: + description: The location of the new resource + schema: + type: string + format: uri + content: + application/json: + schema: + $ref: '#/components/schemas/CreateFileResponse' + '400': + description: "Invalid File Header Object" + content: + application/json: + schema: + $ref: 'https://raw.githubusercontent.com/moov-io/base/master/api/common.yaml#/components/schemas/Error' + get: + tags: ['ACH Files'] + summary: Retrieve File + description: Get the details of an existing File using the unique File identifier that was returned upon creation. + operationId: getFileByID + parameters: + - name: X-Request-ID + in: header + description: Optional Request ID allows application developer to trace requests through the system's logs + example: "rs4f9915" + schema: + type: string + - name: fileID + in: path + description: File ID + required: true + schema: + type: string + example: "3f2d23ee214" + responses: + '200': + description: A File object for the supplied ID + content: + application/json: + schema: + $ref: '#/components/schemas/File' + '404': + description: A resource with the specified ID was not found + delete: + tags: ['ACH Files'] + summary: Delete File + description: Permanently deletes a File and associated Batches. It cannot be undone. + operationId: deleteACHFile + parameters: + - name: fileID + in: path + description: File ID + required: true + schema: + type: string + example: "3f2d23ee214" + - name: X-Request-ID + in: header + description: Optional Request ID allows application developer to trace requests through the system's logs + example: "rs4f9915" + schema: + type: string + responses: + '200': + description: Permanently deleted File. + '404': + description: A File with the specified ID was not found. + /files/{fileID}/build: + get: + tags: ['ACH Files'] + summary: Build File + description: | + Assembles the existing File (batches and controls) records, computes sequence numbers and totals. Returns JSON formatted file. + operationId: buildFile + parameters: + - name: X-Request-ID + in: header + description: Optional Request ID allows application developer to trace requests through the system's logs + example: "rs4f9915" + schema: + type: string + - name: fileID + in: path + description: File ID + required: true + schema: + type: string + example: "3f2d23ee214" + responses: + '200': + description: File built successfully without errors. + content: + text/plain: + schema: + $ref: '#/components/schemas/File' + /files/{fileID}/contents: + get: + tags: ['ACH Files'] + summary: Get File Contents + description: | + Assembles the existing File (batches and controls) records, computes sequence numbers and totals. Returns plaintext file. + operationId: getFileContents + parameters: + - name: X-Request-ID + in: header + description: Optional Request ID allows application developer to trace requests through the system's logs + example: "rs4f9915" + schema: + type: string + - name: fileID + in: path + description: File ID + required: true + schema: + type: string + example: "3f2d23ee214" + responses: + '200': + description: File built successfully without errors. + content: + text/plain: + schema: + $ref: '#/components/schemas/RawFile' + /files/{fileID}/validate: + get: + tags: ['ACH Files'] + summary: Validate File + description: Validates the existing File. You need only supply the unique File identifier that was returned upon creation. + operationId: checkFile + parameters: + - name: X-Request-ID + in: header + description: Optional Request ID allows application developer to trace requests through the system's logs + example: "rs4f9915" + schema: + type: string + - name: fileID + in: path + description: File ID + required: true + schema: + type: string + example: "3f2d23ee214" + responses: + '200': + description: File validated successfully without errors. + content: + application/json: + schema: + $ref: 'https://raw.githubusercontent.com/moov-io/base/master/api/common.yaml#/components/schemas/Error' + '400': + description: Validation failed. Check response for errors + post: + tags: ['ACH Files'] + summary: Validate File (Custom) + description: Validates the existing File. You need only supply the unique File identifier that was returned upon creation. + operationId: validateFile + parameters: + - name: X-Request-ID + in: header + description: Optional Request ID allows application developer to trace requests through the system's logs + example: "rs4f9915" + schema: + type: string + - name: fileID + in: path + description: File ID + required: true + schema: + type: string + example: "3f2d23ee214" + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/ValidateOpts' + responses: + '200': + description: File validated successfully without errors. + content: + application/json: + schema: + $ref: 'https://raw.githubusercontent.com/moov-io/base/master/api/common.yaml#/components/schemas/Error' + '400': + description: Validation failed. Check response for errors + /files/{fileID}/segment: + post: + tags: ['ACH Files'] + summary: Segment FileID + description: Split one FileID into two. One with only debits and one with only credits. + operationId: segmentFileID + parameters: + - name: X-Request-ID + in: header + description: Optional Request ID allows application developer to trace requests through the system's logs + example: "rs4f9915" + schema: + type: string + - name: X-Idempotency-Key + in: header + description: Idempotent key in the header which expires after 24 hours. These strings should contain enough entropy to not collide with each other in your requests. + example: "a4f88150" + required: false + schema: + type: string + - name: fileID + in: path + description: File ID + required: true + schema: + type: string + example: "3f2d23ee214" + requestBody: + description: Optional configuration for segmenting files + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/SegmentFileConfiguration' + responses: + '200': + description: An ID of each new ACH file + content: + application/json: + schema: + $ref: '#/components/schemas/SegmentedFiles' + '400': + description: See error in response body + content: + application/json: + schema: + $ref: 'https://raw.githubusercontent.com/moov-io/base/master/api/common.yaml#/components/schemas/Error' + '404': + description: A resource with the specified ID was not found + /files/{fileID}/flatten: + post: + tags: ['ACH Files'] + summary: Flatten Batches + description: Consolidate Batches and Entries into the minimum number of Batches needed. + operationId: flattenFile + parameters: + - name: X-Request-ID + in: header + description: Optional Request ID allows application developer to trace requests through the system's logs + example: "rs4f9915" + schema: + type: string + - name: X-Idempotency-Key + in: header + description: Idempotent key in the header which expires after 24 hours. These strings should contain enough entropy to not collide with each other in your requests. + example: "a4f88150" + required: false + schema: + type: string + - name: fileID + in: path + description: File ID + required: true + schema: + type: string + example: "3f2d23ee214" + responses: + '200': + description: An ID of the new ACH file + content: + application/json: + schema: + $ref: '#/components/schemas/FlattenFileResponse' + '400': + description: See error in response body + content: + application/json: + schema: + $ref: 'https://raw.githubusercontent.com/moov-io/base/master/api/common.yaml#/components/schemas/Error' + '404': + description: A resource with the specified ID was not found + /files/{fileID}/batches: + get: + tags: ['ACH Files'] + summary: Get Batches + description: Get the Batches on a File. + operationId: getFileBatches + parameters: + - name: X-Request-ID + in: header + description: Optional Request ID allows application developer to trace requests through the system's logs + example: "rs4f9915" + schema: + type: string + - name: fileID + in: path + description: File ID + required: true + schema: + type: string + example: "3f2d23ee214" + responses: + '200': + description: A list of Batch objects + headers: + X-Total-Count: + description: The total number of Batches on the File. + schema: + type: integer + content: + application/json: + schema: + $ref: '#/components/schemas/Batches' + post: + tags: ['ACH Files'] + summary: Append Batch to File + description: Append a Batch record to the specified File. + operationId: addBatchToFile + parameters: + - name: X-Request-ID + in: header + description: Optional Request ID allows application developer to trace requests through the system's logs + example: "rs4f9915" + schema: + type: string + - name: X-Idempotency-Key + in: header + description: Idempotent key in the header which expires after 24 hours. These strings should contain enough entropy to not collide with each other in your requests. + example: "a4f88150" + required: false + schema: + type: string + - name: fileID + in: path + description: File ID + required: true + schema: + type: string + example: "3f2d23ee214" + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Batch' + responses: + '200': + description: Batch added to File + '400': + description: See error in response body + content: + application/json: + schema: + $ref: 'https://raw.githubusercontent.com/moov-io/base/master/api/common.yaml#/components/schemas/Error' + '404': + description: A resource with the specified ID was not found + /files/{fileID}/batches/{batchID}: + get: + tags: ['ACH Files'] + summary: Get Batch + description: Get a specific Batch on a File. + operationId: getFileBatch + parameters: + - name: X-Request-ID + in: header + description: Optional Request ID allows application developer to trace requests through the system's logs + example: "rs4f9915" + schema: + type: string + - name: fileID + in: path + description: File ID + required: true + schema: + type: string + example: "3f2d23ee214" + - name: batchID + in: path + description: Batch ID + required: true + schema: + type: string + example: "45758063" + responses: + '200': + description: Batch object + content: + application/json: + schema: + $ref: '#/components/schemas/Batch' + '404': + description: Batch or File not found + delete: + tags: ['ACH Files'] + summary: Delete Batch + description: Delete a Batch from a File. + operationId: deleteFileBatch + parameters: + - name: X-Request-ID + in: header + description: Optional Request ID allows application developer to trace requests through the system's logs + example: "rs4f9915" + schema: + type: string + - name: fileID + in: path + description: File ID + required: true + schema: + type: string + example: "3f2d23ee214" + - name: batchID + in: path + description: Batch ID + required: true + schema: + type: string + example: "45758063" + responses: + '200': + description: Batch deleted + '404': + description: Batch or File not found + /segment: + post: + tags: ['ACH Files'] + summary: Segment File + description: Split one File into two. One with only debits and one with only credits. + operationId: segmentFile + parameters: + - name: X-Request-ID + in: header + description: Optional Request ID allows application developer to trace requests through the system's logs + example: "rs4f9915" + schema: + type: string + - name: X-Idempotency-Key + in: header + description: Idempotent key in the header which expires after 24 hours. These strings should contain enough entropy to not collide with each other in your requests. + example: "a4f88150" + required: false + schema: + type: string + requestBody: + description: ACH file (in Nacha or JSON formatting) along with optional segment configuration + required: false + content: + text/plain: + schema: + description: A plaintext ACH file + type: string + example: | + 101 23138010401210428821906240000A094101Federal Reserve Bank My Bank Name + 5225Name on Account 121042882 PPDREG.SALARY 190625 1121042880000001 + 62723138010412345678 0100000000 Receiver Account Name 0121042880000001 + 82250000010023138010000100000000000000000000121042882 121042880000001 + 9000001000001000000010023138010000100000000000000000000 + 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 + 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 + 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 + 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 + 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 + application/json: + schema: + $ref: '#/components/schemas/SegmentFile' + responses: + '200': + description: An ID of each new ACH file + content: + application/json: + schema: + $ref: '#/components/schemas/SegmentedFiles' + '400': + description: See error in response body + content: + application/json: + schema: + $ref: 'https://raw.githubusercontent.com/moov-io/base/master/api/common.yaml#/components/schemas/Error' + '404': + description: A resource with the specified ID was not found + +components: + schemas: + CreateFile: + properties: + ID: + type: string + description: File ID + example: "3f2d23ee214" + fileHeader: + $ref: '#/components/schemas/FileHeader' + batches: + type: array + items: + $ref: '#/components/schemas/Batch' + IATBatches: + type: array + items: + $ref: '#/components/schemas/IATBatch' + fileControl: + $ref: '#/components/schemas/FileControl' + advEntryDetails: + type: array + items: + $ref: '#/components/schemas/ADVEntryDetail' + advBatchControl: + $ref: '#/components/schemas/ADVBatchControl' + required: + - fileHeader + CreateFileResponse: + properties: + ID: + type: string + description: File ID + example: "1e522dc8" + file: + $ref: '#/components/schemas/File' + error: + type: string + description: An error message describing the problem intended for humans. + example: Validation error(s) present. + FlattenFileResponse: + properties: + ID: + type: string + description: File ID + example: "1e522dc8" + file: + $ref: '#/components/schemas/File' + error: + type: string + description: An error message describing the problem intended for humans. + example: Validation error(s) present. + File: + properties: + ID: + type: string + description: File ID + example: "3f2d23ee214" + fileHeader: + $ref: '#/components/schemas/FileHeader' + batches: + type: array + items: + $ref: '#/components/schemas/Batch' + IATBatches: + type: array + items: + $ref: '#/components/schemas/IATBatch' + fileControl: + $ref: '#/components/schemas/FileControl' + NotificationOfChange: + type: array + items: + $ref: '#/components/schemas/Batch' + nullable: true + ReturnEntries: + type: array + items: + $ref: '#/components/schemas/Batch' + nullable: true + fileADVControl: + $ref: '#/components/schemas/ADVFileControl' + required: + - ID + - fileHeader + - fileControl + FileHeader: + properties: + ID: + description: File ID + type: string + example: "d1e26288" + immediateOrigin: + type: string + description: Contains the Routing Number of the ACH Operator or sending point that is sending the file. + minLength: 9 + maxLength: 10 + example: "999912342" + immediateOriginName: + type: string + description: The name of the ACH Operator or sending point that is sending the file. + maxLength: 23 + example: "My Bank Name" + immediateDestination: + type: string + maxLength: 10 + minLength: 9 + example: "691000134" + description: Contains the Routing Number of the ACH Operator or receiving point to which the file is being sent. + immediateDestinationName: + type: string + description: The name of the ACH Operator or receiving point to which the file is being sent. + maxLength: 23 + example: "Federal Reserve Bank" + fileCreationTime: + description: 'The File Creation Time is the time when the file was prepared by an ODFI. (Format HHmm - H=Hour, m=Minute)' + type: string + format: "HHmm" + example: "1504" + fileCreationDate: + description: 'The File Creation Date is the date when the file was prepared by an ODFI. (Format YYMMDD - Y=Year, M=Month, D=Day)' + type: string + format: "YYMMDD" + example: "190102" + fileIDModifier: + type: string + description: Incremented value for each file for RDFIs. + example: "0" + referenceCode: + type: string + description: Reserved field for information pertinent to the Originator. + required: + - immediateOrigin + - immediateOriginName + - immediateDestination + - immediateDestinationName + - fileCreationTime + - fileCreationDate + FileControl: + properties: + ID: + description: File ID + type: string + example: "d1e26288" + batchCount: + description: Count of Batches in the File + type: integer + minimum: 1 + example: 1 + blockCount: + description: | + Total number of records in the file (include all headers and trailer) divided by 10 (This number must be evenly divisible by 10. If not, additional records consisting of all 9's are added to the file after the initial '9' record to fill out the block 10.) + type: integer + example: 1 + entryAddendaCount: + description: Total detail and addenda records in the file + type: integer + minimum: 1 + example: 1 + entryHash: + description: Calculated in the same manner as the batch total but includes total from entire file + type: integer + example: 0 + totalDebit: + description: Accumulated Batch debit totals within the file. + type: integer + example: 100 + totalCredit: + description: Accumulated Batch credit totals within the file. + type: integer + example: 20 + required: + - ID + - batchCount + - blockCount + - entryAddendaCount + - entryHash + - totalDebit + - totalCredit + RawFile: + type: string + description: Plaintext ACH file + example: "101 222380104 1210428821805100000A094101Citadel Bank Name" + Files: + type: array + items: + $ref: '#/components/schemas/File' + Batch: + properties: + batchHeader: + $ref: '#/components/schemas/BatchHeader' + entryDetails: + type: array + items: + $ref: '#/components/schemas/EntryDetail' + minLength: 1 + batchControl: + $ref: '#/components/schemas/BatchControl' + advEntryDetails: + type: array + items: + $ref: '#/components/schemas/ADVEntryDetail' + advBatchControl: + $ref: '#/components/schemas/ADVBatchControl' + required: + - batchHeader + - entryDetails + - batchControl + BatchHeader: + required: + - serviceClassCode + - standardEntryClassCode + - companyName + - companyIdentification + - ODFIIdentification + - batchNumber + properties: + ID: + type: string + description: A client-defined ID used as a reference to this batch + example: "913b5742" + serviceClassCode: + type: integer + description: Service Class Code - Mixed Debits and Credits '200', ACH Credits Only '220', or ACH Debits Only '225' + example: 220 + companyName: + type: string + description: Company originating the entries in the batch + example: Acme Corp + companyDiscretionaryData: + type: string + description: The 9 digit FEIN number (proceeded by a predetermined alpha or numeric character) of the entity in the company name field + example: "123456789" + companyIdentification: + description: | + Alphanumeric code used to identify an Originator. The Company Identification Field must be included on all prenotification records and on each entry initiated pursuant to such prenotification. The Company ID may begin with the ANSI one-digit Identification Code Designator (ICD), followed by the identification number. + Possible ICDs are the IRS Employer Identification Number (EIN) "1", Data Universal Numbering Systems (DUNS) "3", or User Assigned Number "9". + type: string + example: "121042882" + standardEntryClassCode: + type: string + description: Identifies the payment type (product) found within an ACH batch using a 3-character code. + example: "PPD" + companyEntryDescription: + type: string + description: | + A description of the entries contained in the batch. + The Originator establishes the value of this field to provide a description of the purpose of the entry to be displayed back to the receiver. For example, "GAS BILL," "REG. SALARY," "INS. PREM,", "SOC. SEC.," "DTC," "TRADE PAY," "PURCHASE," etc. + This field must contain the word "REVERSAL" (left justified) when the batch contains reversing entries. + This field must contain the word "RECLAIM" (left justified) when the batch contains reclamation entries. + This field must contain the word "NONSETTLED" (left justified) when the batch contains entries which could not settle. + example: "PURCHASE" + companyDescriptiveDate: + type: string + description: | + The Originator establishes this field as the date it would like to see displayed to the receiver for descriptive purposes. This field is never used to control timing of any computer or manual operation. It is solely for descriptive purposes. The RDFI should not assume any specific format. + example: "SD1300" + effectiveEntryDate: + description: Date on which the entries are to settle. (Format YYMMDD - Y=Year, M=Month, D=Day) + type: string + format: "YYMMDD" + example: "190102" + originatorStatusCode: + type: integer + description: | + ODFI initiating the Entry. | + 0 - ADV File prepared by an ACH Operator. | + 1 - This code identifies the Originator as a depository financial institution. | + 2 - This code identifies the Originator as a Federal Government entity or agency. + example: 1 + ODFIIdentification: + description: First 8 digits of the originating DFI transit routing number + type: string + example: "12345678" + batchNumber: + type: integer + description: | + BatchNumber is assigned in ascending sequence to each batch by the ODFI or its Sending Point in a given file of entries. Since the batch number in the Batch Header Record and the Batch Control Record is the same, the ascending sequence number should be assigned by batch and not by record. + example: 0 + settlementDate: + type: string + description: The date the entries actually settled (this is inserted by the ACH operator) + format: "YYMMDD" + example: "190102" + BatchControl: + properties: + ID: + description: Batch ID + type: string + example: "62d8f0cd" + serviceClassCode: + description: Same as ServiceClassCode in BatchHeaderRecord + type: integer + example: 220 + entryAddendaCount: + description: EntryAddendaCount is a tally of each Entry Detail Record and each Addenda Record processed, within either the batch or file as appropriate. + type: integer + example: 1 + entryHash: + description: | + Validate the Receiving DFI Identification in each Entry Detail Record is hashed to provide a check against inadvertent alteration of data contents due to hardware failure or program error. + In this context the Entry Hash is the sum of the corresponding fields in the Entry Detail Records on the file. + type: integer + example: 0 + totalDebit: + description: Contains accumulated Entry debit totals within the batch. + type: integer + example: 100 + totalCredit: + description: Contains accumulated Entry credit totals within the batch. + type: integer + example: 100 + companyIdentification: + description: | + Alphanumeric code used to identify an Originator. The Company Identification Field must be included on all prenotification records and on each entry initiated pursuant to such prenotification. The Company ID may begin with the ANSI one-digit Identification Code Designator (ICD), followed by the identification number. + Possible ICDs are the IRS Employer Identification Number (EIN) "1", Data Universal Numbering Systems (DUNS) "3", and User Assigned Number "9". + type: string + example: "121042882" + messageAuthentication: + description: MAC is an eight character code derived from a special key used in conjunction with the DES algorithm. The purpose of the MAC is to validate the authenticity of ACH entries. The DES algorithm and key message standards must be in accordance with standards adopted by the American National Standards Institute. The remaining eleven characters of this field are blank. + type: string + example: "3fe106cf" + ODFIIdentification: + description: The routing number is used to identify the DFI originating entries within a given branch. + type: string + example: "123456789" + batchNumber: + type: integer + description: BatchNumber is assigned in ascending sequence to each batch by the ODFI or its Sending Point in a given file of entries. Since the batch number in the Batch Header Record and the Batch Control Record is the same, the ascending sequence number should be assigned by batch and not by record. + example: 0 + required: + - serviceClassCode + - entryAddendaCount + - entryHash + - totalDebit + - totalCredit + - companyIdentification + - ODFIIdentification + - batchNumber + Batches: + type: array + items: + $ref: '#/components/schemas/Batch' + EntryDetail: + required: + - id + - transactionCode + - RDFIIdentification + - checkDigit + - DFIAccountNumber + - amount + - individualName + properties: + ID: + type: string + description: Entry Detail ID + example: "842a2261" + transactionCode: + type: integer + description: | + Based on transaction type: + 22 - Credit (deposit) to checking account | + 23 - Prenote for credit to checking account | + 27 - Debit (withdrawal) to checking account | + 28 - Prenote for debit to checking account | + 32 - Credit to savings account | + 33 - Prenote for credit to savings account | + 37 - Debit to savings account | + 38 - Prenote for debit to savings account + example: 22 + RDFIIdentification: + type: string + description: RDFI's routing number without the last digit. + example: "12345678" + checkDigit: + type: string + description: Last digit in RDFI routing number. + example: "0" + DFIAccountNumber: + type: string + description: | + The receiver's bank account number you are crediting/debiting. It important to note that this is an alphanumeric field, so it's space padded, not zero padded + example: "181141847" + amount: + type: integer + description: Number of cents you are debiting/crediting this account + example: 1235 + identificationNumber: + type: string + description: Internal identification (alphanumeric) that you use to uniquely identify this Entry Detail Record + example: "8aa786" + individualName: + type: string + description: The name of the receiver, usually the name on the bank account + example: Taylor Swift + discretionaryData: + type: string + description: | + DiscretionaryData allows ODFIs to include codes, of significance only to them, to enable specialized handling of the entry. There will be no standardized interpretation for the value of this field. It can either be a single two-character code, or two distinct one-character codes, according to the needs of the ODFI and/or Originator involved. This field must be returned intact for any returned entry. + WEB uses the Discretionary Data Field as the Payment Type Code. + example: "AB" + addendaRecordIndicator: + type: integer + description: | + AddendaRecordIndicator indicates the existence of an Addenda Record. A value of "1" indicates that one or more addenda records follow, and "0" means no such record is present. + example: 1 + traceNumber: + type: string + description: | + TraceNumber assigned by the ODFI in ascending sequence, is included in each Entry Detail Record, Corporate Entry Detail Record, and Addenda Record. + Trace Numbers uniquely identify each entry within a batch in an ACH input file. In association with the Batch Number, transmission (File Creation) Date, and File ID Modifier, the Trace Number uniquely identifies an entry within a given file. + For Addenda Records, the Trace Number will be identical to the Trace Number in the associated Entry Detail Record, since the Trace Number is associated with an entry or item rather than a physical record. + example: "124782618117" + addenda02: + $ref: '#/components/schemas/Addenda02' + addenda05: + type: array + description: List of Addenda05 records + items: + $ref: '#/components/schemas/Addenda05' + addenda98: + $ref: '#/components/schemas/Addenda98' + addenda99: + $ref: '#/components/schemas/Addenda99' + addenda99Dishonored: + $ref: '#/components/schemas/Addenda99Dishonored' + addenda99Contested: + $ref: '#/components/schemas/Addenda99Contested' + category: + type: string + description: Category defines if the entry is a Forward, Return, or NOC + example: "Forward" + Addenda02: + properties: + id: + type: string + description: Client-defined string used as a reference to this record. + example: "5ca8d25a" + typeCode: + type: string + description: 02 - NACHA regulations + example: "02" + referenceInformationOne: + type: string + description: | + ReferenceInformationOne may be used for additional reference numbers, identification numbers, + or codes that the merchant needs to identify the particular transaction or customer. + example: '' + referenceInformationTwo: + type: string + description: | + ReferenceInformationTwo may be used for additional reference numbers, identification numbers, + or codes that the merchant needs to identify the particular transaction or customer. + example: '' + terminalIdentificationCode: + type: string + description: | + TerminalIdentificationCode identifies an Electronic terminal with a unique code that allows + a terminal owner and/or switching network to identify the terminal at which an Entry originated. + example: '200509' + transactionSerialNumber: + type: string + description: | + TransactionSerialNumber is assigned by the terminal at the time the transaction is originated. The + number, with the Terminal Identification Code, serves as an audit trail for the transaction and is + usually assigned in ascending sequence. + example: '100049' + transactionDate: + type: string + format: "MMDD" + description: Timestamp identifies the date on which the transaction occurred. (Format MMDD - M=Month, D=Day) + example: '1224' + authorizationCodeOrExpireDate: + type: string + description: Indicates the code that a card authorization center has furnished to the merchant. + example: '123456' + terminalLocation: + type: string + description: Identifies the specific location of a terminal (i.e., street names of an intersection, address, etc.) in accordance with the requirements of Regulation E. + example: 2nd St + terminalCity: + type: string + description: Identifies the city in which the electronic terminal is located. + example: Anytown + terminalState: + type: string + description: Identifies the state in which the electronic terminal is located. + example: CA + traceNumber: + type: string + description: Entry Detail Trace Number + example: "124782618117" + required: + - typeCode + - terminalIdentificationCode + - transactionSerialNumber + - transactionDate + - terminalLocation + - terminalCity + - terminalState + Addenda05: + properties: + id: + type: string + description: Client-defined string used as a reference to this record. + example: 5ca8d25a + typeCode: + type: string + description: 05 - NACHA regulations + example: "05" + paymentRelatedInformation: + type: string + description: Text for describing the related payment + example: "Car Payment" + sequenceNumber: + type: integer + description: SequenceNumber is consecutively assigned to each Addenda05 Record following an Entry Detail Record. The first Addenda05 sequence number must always be a 1. + example: 1 + entryDetailSequenceNumber: + type: integer + description: | + EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + Detail or Corporate Entry Detail Record's trace number. This number is + the same as the last seven digits of the trace number of the related + Entry Detail Record or Corporate Entry Detail Record. + example: 2618117 + required: + - typeCode + - paymentRelatedInformation + - sequenceNumber + - entryDetailSequenceNumber + Addenda10: + properties: + id: + type: string + description: Client-defined string used as a reference to this record. + example: 5ca8d25a + typeCode: + type: string + description: 10 - NACHA regulations + example: "10" + transactionTypeCode: + type: string + description: | + Describes the type of payment: + 'ANN' = Annuity | + 'BUS' = Business/Commercial | + 'DEP' = Deposit | + 'LOA' = Loan | + 'MIS' = Miscellaneous | + 'MOR' = Mortgage | + 'PEN' = Pension | + 'RLS' = Rent/Lease | + 'REM' = Remittance2 | + 'SAL' = Salary/Payroll | + 'TAX' = Tax | + 'TEL' = Telephone-Initiated Transaction | + 'WEB' = Internet-Initiated Transaction | + 'ARC' = Accounts Receivable Entry | + 'BOC' = Back Office Conversion Entry | + 'POP' = Point of Purchase Entry | + 'RCK' = Re-presented Check Entry + example: TEL + foreignPaymentAmount: + type: integer + description: For inbound IAT payments this field should contain the USD amount or may be blank. + example: 1021441 + foreignTraceNumber: + type: string + description: Trace number + example: "19846249812" + name: + type: string + description: Receiving Company Name/Individual Name + example: Jane Smith + entryDetailSequenceNumber: + type: integer + description: | + EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + Detail or Corporate Entry Detail Record's trace number. This number is + the same as the last seven digits of the trace number of the related + Entry Detail Record or Corporate Entry Detail Record. + example: 2618117 + required: + - typeCode + - transactionCode + - foreignPaymentAmount + - name + - entryDetailSequenceNumber + Addenda11: + properties: + id: + type: string + description: Client-defined string used as a reference to this record. + example: 5ca8d25a + typeCode: + type: string + description: 11 - NACHA regulations + example: "11" + originatorName: + type: string + description: Originator's name (your company name / name) + example: Acme Corp + originatorStreetAddress: + type: string + description: Originator's street address + example: 111 1st St + entryDetailSequenceNumber: + type: integer + description: | + EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + Detail or Corporate Entry Detail Record's trace number. This number is + the same as the last seven digits of the trace number of the related + Entry Detail Record or Corporate Entry Detail Record. + example: 2618117 + required: + - typeCode + - originatorName + - originatorStreetAddress + - entryDetailSequenceNumber + Addenda12: + properties: + id: + type: string + description: Client-defined string used as a reference to this record. + example: 5ca8d25a + typeCode: + type: string + description: 12 - NACHA regulations + example: "12" + originatorCityStateProvince: + type: string + description: | + Originator City & State / Province + Data elements City and State / Province should be separated with an asterisk (*) as a delimiter and the field should end with a backslash (\\). + example: | + San Francisco*CA\ + originatorCountryPostalCode: + type: string + description: | + Originator Country & Postal Code + Data elements must be separated by an asterisk (*) and must end with a backslash (\\). + example: | + US*10036\ + entryDetailSequenceNumber: + type: integer + description: | + EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + Detail or Corporate Entry Detail Record's trace number. This number is + the same as the last seven digits of the trace number of the related + Entry Detail Record or Corporate Entry Detail Record. + example: 2618117 + required: + - typeCode + - originatorCityStateProvince + - originatorCountryPostalCode + - entryDetailSequenceNumber + Addenda13: + properties: + id: + type: string + description: Client-defined string used as a reference to this record. + example: 5ca8d25a + typeCode: + type: string + description: 13 - NACHA regulations + example: "13" + ODFIName: + type: string + description: | + Originating DFI Name + For Outbound IAT Entries, this field must contain the name of the U.S. ODFI. + For Inbound IATs: Name of the foreign bank providing funding for the payment transaction + example: My Bank + ODFIIDNumberQualifier: + type: string + description: | + Originating DFI Identification Number Qualifier. + For Inbound IATs: The 2-digit code that identifies the numbering scheme used in the + Foreign DFI Identification Number field: + '01' = National Clearing System | + '02' = BIC Code | + '03' = IBAN Code + example: '01' + ODFIIdentification: + type: string + description: | + Originating DFI Identification. + This field contains the routing number that identifies the U.S. ODFI initiating the entry. + For Inbound IATs: This field contains the bank ID number of the Foreign Bank providing funding + for the payment transaction. + example: '04121503' + ODFIBranchCountryCode: + type: string + description: | + Originating DFI Branch Country Code: + USb = United States + //("b" indicates a blank space) + For Inbound IATs: This 3 position field contains a 2-character code as approved by the + International Organization for Standardization (ISO) used to identify the country in which + the branch of the bank that originated the entry is located. Values for other countries can + be found on the International Organization for Standardization website: www.iso.org. + example: "US " + entryDetailSequenceNumber: + type: integer + description: | + EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + Detail or Corporate Entry Detail Record's trace number. This number is + the same as the last seven digits of the trace number of the related + Entry Detail Record or Corporate Entry Detail Record. + example: 2618117 + required: + - typeCode + - ODFIName + - ODFIIDNumberQualifier + - ODFIBranchCountryCode + - entryDetailSequenceNumber + Addenda14: + properties: + id: + type: string + description: Client-defined string used as a reference to this record. + example: 5ca8d25a + typeCode: + type: string + description: 14 - NACHA regulations + example: "14" + RDFIName: + type: string + description: Name of the Receiver bank + example: 'Citadel Bank' + RDFIIDNumberQualifier: + type: string + description: | + Receiving DFI Identification Number Qualifier. + The 2-digit code that identifies the numbering scheme used in the + Receiving DFI Identification Number field: + '01' = National Clearing System | + '02' = BIC Code | + '03' = IBAN Code + example: '01' + RDFIIdentification: + type: string + description: This field contains the bank identification number of the DFI at which the Receiver maintains his account. + example: '98765432' + RDFIBranchCountryCode: + type: string + description: | + Receiving DFI Branch Country Code + USb" = United States + ("b" indicates a blank space) + This 3 position field contains a 2-character code as approved by the International + Organization for Standardization (ISO) used to identify the country in which the + branch of the bank that receives the entry is located. Values for other countries can + be found on the International Organization for Standardization website: www.iso.org + example: 'US ' + entryDetailSequenceNumber: + type: integer + description: | + EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + Detail or Corporate Entry Detail Record's trace number. This number is + the same as the last seven digits of the trace number of the related + Entry Detail Record or Corporate Entry Detail Record. + example: 2618117 + required: + - typeCode + - RDFIName + - RDFIIDNumberQualifier + - RDFIIdentification + - RDFIBranchCountryCode + - entryDetailSequenceNumber + Addenda15: + properties: + id: + type: string + description: Client-defined string used as a reference to this record. + example: 5ca8d25a + typeCode: + type: string + description: 15 - NACHA regulations + example: "15" + receiverIDNumber: + type: string + description: | + Receiver Identification Number contains the accounting number by which the Originator is known to the Receiver for descriptive purposes. NACHA Rules recommend but do not require the RDFI to print the contents of this field on the receiver's statement. + example: "98765432" + receiverStreetAddress: + type: string + description: Receiver's physical address + example: 123 1st St + entryDetailSequenceNumber: + type: integer + description: | + EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + Detail or Corporate Entry Detail Record's trace number. This number is + the same as the last seven digits of the trace number of the related + Entry Detail Record or Corporate Entry Detail Record. + example: 2618117 + required: + - typeCode + - receiverStreetAddress + - entryDetailSequenceNumber + Addenda16: + properties: + id: + type: string + description: Client-defined string used as a reference to this record. + example: 5ca8d25a + typeCode: + type: string + description: 16 - NACHA regulations + example: "16" + receiverCityStateProvince: + type: string + description: | + Receiver City & State / Province + Data elements City and State / Province should be separated with an asterisk (*) as a delimiter + and the field should end with a backslash (\\). + example: | + San Francisco*CA\ + receiverCountryPostalCode: + type: string + description: | + Receiver Country & Postal Code + Data elements must be separated by an asterisk (*) and must end with a backslash (\\). + example: | + US*10036\ + entryDetailSequenceNumber: + type: integer + description: | + EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + Detail or Corporate Entry Detail Record's trace number. This number is + the same as the last seven digits of the trace number of the related + Entry Detail Record or Corporate Entry Detail Record. + example: 2618117 + required: + - typeCode + - receiverCityStateProvince + - receiverCountryPostalCode + - entryDetailSequenceNumber + Addenda17: + properties: + id: + type: string + description: Client-defined string used as a reference to this record. + example: 5ca8d25a + typeCode: + type: string + description: 17 - NACHA regulations + example: "17" + paymentRelatedInformation: + type: string + description: Additional information related to the payment + example: Test data + sequenceNumber: + type: integer + description: | + SequenceNumber is consecutively assigned to each Addenda17 Record following an Entry Detail Record. The first Addenda17 sequence number must always be a 1. + example: 1 + entryDetailSequenceNumber: + type: integer + description: | + EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + Detail or Corporate Entry Detail Record's trace number. This number is + the same as the last seven digits of the trace number of the related + Entry Detail Record or Corporate Entry Detail Record. + example: 2618117 + required: + - typeCode + - paymentRelatedInformation + - sequenceNumber + - entryDetailSequenceNumber + Addenda18: + properties: + id: + type: string + description: Client-defined string used as a reference to this record. + example: 5ca8d25a + typeCode: + type: string + description: 18 - NACHA regulations + example: "18" + foreignCorrespondentBankName: + type: string + description: Name of the Foreign Correspondent Bank + example: Other Bank + foreignCorrespondentBankIDNumberQualifier: + type: string + description: | + Foreign Correspondent Bank Identification Number Qualifier contains a 2-digit code that identifies the numbering scheme used in the Foreign Correspondent Bank Identification Number field. Code values for this field are: + "01" = National Clearing System | + "02" = BIC Code | + "03" = IBAN Code + example: '01' + foreignCorrespondentBankIDNumber: + type: string + description: Foreign Correspondent Bank Identification Number contains the bank ID number of the Foreign Correspondent Bank + example: '98765432' + foreignCorrespondentBankBranchCountryCode: + type: string + description: | + Foreign Correspondent Bank Branch Country Code contains the two-character code, as approved by the International Organization for Standardization (ISO), to identify the country in which the branch of the Foreign Correspondent Bank is located. Values can be found on the International Organization for Standardization website: www.iso.org + example: US + sequenceNumber: + type: integer + description: | + SequenceNumber is consecutively assigned to each Addenda17 Record following an Entry Detail Record. The first Addenda17 sequence number must always be a 1. + example: 1 + entryDetailSequenceNumber: + type: integer + description: | + EntryDetailSequenceNumber contains the ascending sequence number section of the Entry + Detail or Corporate Entry Detail Record's trace number. This number is + the same as the last seven digits of the trace number of the related + Entry Detail Record or Corporate Entry Detail Record. + example: 2618117 + required: + - typeCode + - foreignCorrespondentBankName + - foreignCorrespondentBankIDNumber + - foreignCorrespondentBankBranchCountryCode + - sequenceNumber + - entryDetailSequenceNumber + Addenda98: + properties: + id: + type: string + description: Client-defined string used as a reference to this record. + example: 5ca8d25a + typeCode: + type: string + description: 98 - NACHA regulations + example: "98" + changeCode: + type: string + description: ChangeCode field contains a standard code used by an ACH Operator or RDFI to describe the reason for a change Entry. + example: C01 + originalTrace: + type: string + description: | + OriginalTrace This field contains the Trace Number as originally included on the forward Entry or Prenotification. + The RDFI must include the Original Entry Trace Number in the Addenda Record of an Entry being returned to an ODFI, + in the Addenda Record of an 98, within an Acknowledgment Entry, or with an RDFI request for a copy of an authorization. + example: "214874812" + originalDFI: + type: string + description: The Receiving DFI Identification (addenda.RDFIIdentification) as originally included on the forward Entry or Prenotification that the RDFI is returning or correcting. + example: "98765432" + correctedData: + type: string + description: Correct field value of what changeCode references + example: "198424892" + traceNumber: + type: string + description: Entry Detail Trace Number + example: "124782618117" + required: + - typeCode + - changeCode + - originalTrace + - originalDFI + - correctedData + Addenda99: + properties: + id: + type: string + description: Client-defined string used as a reference to this record. + example: 5ca8d25a + typeCode: + type: string + description: 99 - NACHA regulations + example: "99" + returnCode: + type: string + description: Standard code used by an ACH Operator or RDFI to describe the reason for returning an Entry. + example: "R01" + originalTrace: + type: string + description: | + OriginalTrace This field contains the Trace Number as originally included on the forward Entry or Prenotification. + The RDFI must include the Original Entry Trace Number in the Addenda Record of an Entry being returned to an ODFI, + in the Addenda Record of an 98, within an Acknowledgment Entry, or with an RDFI request for a copy of an authorization. + example: "214874812" + dateOfDeath: + type: string + format: "YYMMDD" + description: The field date of death is to be supplied on Entries being returned for reason of death (return reason codes R14 and R15). (Format YYMMDD - Y=Year, M=Month, D=Day) + example: "200102" + originalDFI: + type: string + description: Contains the Receiving DFI Identification (addenda.RDFIIdentification) as originally included on the forward Entry or Prenotification that the RDFI is returning or correcting. + example: "98765432" + addendaInformation: + type: string + description: Information related to the return + example: text + traceNumber: + type: string + description: Matches the Entry Detail Trace Number of the entry being returned. + example: "214874812" + required: + - typeCode + - returnCode + - originalTrace + - dateOfDeath + - originalDFI + Addenda99Dishonored: + properties: + id: + type: string + description: Client-defined string used as a reference to this record. + example: 5ca8d25a + typeCode: + type: string + description: 99 - NACHA regulations + example: "99" + originalEntryTraceNumber: + type: string + description: Trace Number of the original entry being returned. + example: "214874812" + originalReceivingDFIIdentification: + type: string + description: Identification of the Original Receiving Depository Institution (ODFI) + example: "12345678" + returnTraceNumber: + type: string + description: Return trace number + example: "214874812" + returnSettlementDate: + type: string + description: Return settlement date + example: "200102" + returnReasonCode: + type: string + description: Return reason code + example: "R12" + dishonoredReturnReasonCode: + type: string + description: Return reason code of the Dishonored Return + example: "R12" + traceNumber: + type: string + description: Matches the Entry Detail Trace Number of the entry being returned. + example: "214874812" + addendaInformation: + type: string + description: Additional data + example: '00134 ' + required: + - typeCode + - originalEntryTraceNumber + - originalReceivingDFIIdentification + - returnTraceNumber + - returnSettlementDate + - returnReasonCode + - dishonoredReturnReasonCode + Addenda99Contested: + properties: + id: + type: string + description: Client-defined string used as a reference to this record. + example: 5ca8d25a + typeCode: + type: string + description: 99 - NACHA regulations + example: "99" + contestedReturnCode: + type: string + description: "return code explaining the contested dishonorment" + example: "R12" + dateOriginalEntryReturned: + type: string + description: Date original entry was returned + example: "200102" + dishonoredReturnReasonCode: + type: string + description: Return reason code of the Dishonored Return + example: "R12" + dishonoredReturnTraceNumber: + type: string + description: Trace number from Dishonored Return + example: "214874812" + dishonoredReturnSettlementDate: + type: string + description: Settlement date of the Dishonored Return + example: "200102" + originalEntryTraceNumber: + type: string + description: Trace Number of the original entry being returned. + example: "214874812" + originalReceivingDFIIdentification: + type: string + description: Identification of the Original Receiving Depository Institution (ODFI) + example: "12345678" + originalSettlementDate: + type: string + description: Original date of settlement + example: "200102" + returnTraceNumber: + type: string + description: Return trace number + example: "214874812" + returnSettlementDate: + type: string + description: Return settlement date + example: "200102" + returnReasonCode: + type: string + description: Return reason code + example: "R12" + traceNumber: + type: string + description: Unique Trace Number for the contested dishonored return + example: "214874812" + required: + - typeCode + - dishonoredReturnReasonCode + - originalEntryTraceNumber + - originalReceivingDFIIdentification + - returnTraceNumber + - returnSettlementDate + - returnReasonCode + - traceNumber + - contestedReturnCode + - dateOriginalEntryReturned + - originalSettlementDate + - dishonoredReturnTraceNumber + - dishonoredReturnSettlementDate + IATBatch: + properties: + ID: + description: Client-defined string used as a reference to this record. + type: string + example: a747e53f + IATBatchHeader: + $ref: '#/components/schemas/IATBatchHeader' + IATEntryDetails: + type: array + items: + $ref: '#/components/schemas/IATEntryDetail' + minItems: 1 + batchControl: + $ref: '#/components/schemas/BatchControl' + required: + - IATBatchHeader + - IATEntryDetails + - batchControl + IATBatchHeader: + properties: + ID: + description: ID is a client-defined string used as a reference to this record. + type: string + example: a747e53f + serviceClassCode: + description: Service Class Code - Mixed Debits and Credits '200', ACH Credits Only '220', or ACH Debits Only '225' + type: integer + example: 220 + IATIndicator: + description: Leave Blank. Only used for corrected IAT entries + type: string + example: "" + foreignExchangeIndicator: + description: | + Code indicating currency conversion: + 'FV' (Fixed-to-Variable) – Entry is originated in a fixed-value amount and is to be received in a variable amount resulting from the execution of the foreign exchange conversion. | + 'VF' (Variable-to-Fixed) – Entry is originated in a variable-value amount based on a specific foreign exchange rate for conversion to a fixed-value amount in which the entry is to be received. | + 'FF' (Fixed-to-Fixed) – Entry is originated in a fixed-value amount and is to be received in the same fixed-value amount in the same currency denomination. There is no foreign exchange conversion for entries transmitted using this code. For entries originated in a fixed value amount, the foreign Exchange Reference Field will be space filled. + type: string + example: FF + foreignExchangeReferenceIndicator: + description: | + Code used to indicate the content of the Foreign Exchange Reference Field and is filled by the gateway operator. Valid entries are + 1 - Foreign Exchange Rate | + 2 - Foreign Exchange Reference Number | + 3 - Space Filled + type: integer + example: 2 + foreignExchangeReference: + description: Contains either the foreign exchange rate used to execute the foreign exchange conversion of a cross-border entry or another reference to the foreign exchange transaction. + type: string + ISODestinationCountryCode: + description: Two-character code, as approved by the International Organization for Standardization (ISO), to identify the country in which the entry is to be received. For United States use US. + type: string + example: US + originatorIdentification: + description: | + For U.S. entities: the number assigned will be your tax ID (often Social Security Number) + For non-U.S. entities: the number assigned will be your DDA number, or the last 9 characters of your account number if it exceeds 9 characters + type: string + example: "123456789" + standardEntryClassCode: + description: | + StandardEntryClassCode for consumer and non consumer international payments is IAT. + Identifies the payment type (product) found within an ACH batch using a 3-character code. + The SEC Code pertains to all items within batch. + Determines format of the detail records. + Determines addenda records (required or optional PLUS one or up to 9,999 records). + Determines rules to follow (return time frames). + Some SEC codes require specific data in predetermined fields within the ACH record. + type: string + example: IAT + companyEntryDescription: + description: | + A description of the entries contained in the batch + The Originator establishes the value of this field to provide a description of the purpose of the entry to be displayed back to the receiver. For example, "GAS BILL," "REG. SALARY," "INS. PREM," "SOC. SEC.," "DTC," "TRADE PAY," "PURCHASE," etc. + This field must contain the word "REVERSAL" (left justified) when the batch contains reversing entries. + This field must contain the word "RECLAIM" (left justified) when the batch contains reclamation entries. + This field must contain the word "NONSETTLED" (left justified) when the batch contains entries which could not settle. + type: string + example: GAS BILL + ISOOriginatingCurrencyCode: + description: | + Three-character code, as approved by the International Organization for Standardization (ISO), to identify the currency denomination in which the entry was first originated. If the source of funds is within the territorial jurisdiction of the U.S., enter 'USD', otherwise refer to International Organization for Standardization website for value: www.iso.org + type: string + example: USD + ISODestinationCurrencyCode: + description: | + ISODestinationCurrencyCode is the three-character code, as approved by the International Organization for Standardization (ISO), to identify the currency denomination in which the entry will ultimately be settled. If the final destination of funds is within the territorial jurisdiction of the U.S., enter "USD", otherwise refer to International Organization for Standardization website for value: www.iso.org + type: string + example: USD + effectiveEntryDate: + description: | + EffectiveEntryDate the date on which the entries are to settle. Format YYMMDD (Y=Year, M=Month, D=Day) + type: string + format: "YYMMDD" + example: '181231' + originatorStatusCode: + description: | + ODFI initiating the Entry. | + 0 - ADV File prepared by an ACH Operator. | + 1 - This code identifies the Originator as a depository financial institution. | + 2 - This code identifies the Originator as a Federal Government entity or agency. + type: integer + example: 1 + ODFIIdentification: + description: | + First 8 digits of the originating DFI transit routing number. For Inbound IAT Entries, this field contains the routing number of the U.S. Gateway Operator. For Outbound IAT Entries, this field contains the standard routing number, as assigned by Accuity, that identifies the U.S. ODFI initiating the Entry. (Format TTTTAAAA - T=Federal Reserve Routing Symbol, A=ABA Institution Identifier) + type: string + format: 'TTTTAAAA' + example: '12345678' + batchNumber: + description: | + BatchNumber is assigned in ascending sequence to each batch by the ODFI or its Sending Point in a given file of entries. Since the batch number in the Batch Header Record and the Batch Control Record is the same, the ascending sequence number should be assigned by batch and not by record. + type: integer + example: 0 + settlementDate: + type: string + description: The date the entries actually settled (this is inserted by the ACH operator) + format: "YYMMDD" + example: "190102" + required: + - serviceClassCode + - standardEntryClassCode + - foreignExchangeIndicator + - foreignExchangeReferenceIndicator + - foreignExchangeReference + - ISODestinationCountryCode + - originatorIdentification + - ISOOriginatingCurrencyCode + - ISODestinationCurrencyCode + - ODFIIdentification + - batchNumber + IATEntryDetail: + properties: + ID: + type: string + description: Entry Detail ID + example: 842a2261 + transactionCode: + type: integer + description: | + Based on transaction type: + 22 - Credit (deposit) to checking account | + 23 - Prenote for credit to checking account | + 27 - Debit (withdrawal) to checking account | + 28 - Prenote for debit to checking account | + 32 - Credit to savings account | + 33 - Prenote for credit to savings account | + 37 - Debit to savings account | + 38 - Prenote for debit to savings account + example: 22 + RDFIIdentification: + type: string + description: RDFI's routing number without the last digit. + example: "12345678" + checkDigit: + type: string + description: Last digit in RDFI routing number. + example: "0" + addendaRecords: + type: integer + description: Number of Addenda Records + example: 1 + amount: + type: integer + description: Number of cents you are debiting/crediting this account + example: 1235 + DFIAccountNumber: + type: string + description: | + The receiver's bank account number you are crediting/debiting. It important to note that this is an alphanumeric field, so it's space padded, not zero padded + example: "181141847" + OFACScreeningIndicator: + type: string + description: Signifies if the record has been screened against OFAC records + example: 'Y' + secondaryOFACScreeningIndicator: + type: string + description: Signifies if the record has been screened against OFAC records by a secondary entry + example: 'Y' + addendaRecordIndicator: + type: integer + description: | + AddendaRecordIndicator indicates the existence of an Addenda Record. A value of "1" indicates that one or more addenda records follow, and "0" means no such record is present. + example: 1 + traceNumber: + type: string + description: Matches the Entry Detail Trace Number of the entry being returned. + example: "214874812" + addenda10: + $ref: '#/components/schemas/Addenda10' + addenda11: + $ref: '#/components/schemas/Addenda11' + addenda12: + $ref: '#/components/schemas/Addenda12' + addenda13: + $ref: '#/components/schemas/Addenda13' + addenda14: + $ref: '#/components/schemas/Addenda14' + addenda15: + $ref: '#/components/schemas/Addenda15' + addenda16: + $ref: '#/components/schemas/Addenda16' + addenda17: + $ref: '#/components/schemas/Addenda17' + addenda18: + $ref: '#/components/schemas/Addenda18' + addenda98: + $ref: '#/components/schemas/Addenda98' + addenda99: + $ref: '#/components/schemas/Addenda99' + category: + type: string + description: Category defines if the entry is a Forward, Return, or NOC + example: Forward + required: + - transactionCode + - RDFIIdentification + - checkDigit + - addendaRecords + - amount + - DFIAccountNumber + - OFACScreeningIndicator + - secondaryOFACScreeningIndicator + - addendaRecordIndicator + - addenda10 + - addenda11 + - addenda12 + - addenda13 + - addenda14 + - addenda15 + - addenda16 + ADVEntryDetail: + required: + - transactionCode + - RDFIIdentification + - checkDigit + - DFIAccountNumber + - adviceRoutingNumber + - amount + - individualName + - achOperatorRoutingNumber + - sequenceNumber + properties: + ID: + type: string + description: Entry Detail ID + example: 842a2261 + transactionCode: + type: integer + description: | + TransactionCode representing Accounting Entries: + 81 - Credit for ACH debits originated | + 82 - Debit for ACH credits originated | + 83 - Credit for ACH credits received | + 84 - Debit for ACH debits received | + 85 - Credit for ACH credits in rejected batches | + 86 - Debit for ACH debits in rejected batches | + 87 - Summary credit for respondent ACH activity | + 88 - Summary debit for respondent ACH activity + example: 81 + RDFIIdentification: + type: string + description: RDFI's routing number without the last digit. + example: "12345678" + checkDigit: + type: string + description: Last digit in RDFI routing number. + example: "0" + DFIAccountNumber: + type: string + description: | + The receiver's bank account number you are crediting/debiting. It important to note that this is an alphanumeric field, so it's space padded, not zero padded + example: "181141847" + amount: + type: integer + description: Number of cents you are debiting/crediting this account + example: 1235 + adviceRoutingNumber: + type: string + description: Suggested routing number to use + example: "987654320" + fileIdentification: + type: string + description: Unique identifier for the File + example: 22c24006 + achOperatorData: + type: string + description: Information related to the ACH opreator + example: text + individualName: + type: string + description: The name of the receiver, usually the name on the bank account + example: Taylor Swift + discretionaryData: + type: string + description: | + DiscretionaryData allows ODFIs to include codes, of significance only to them, to enable specialized handling of the entry. There will be no standardized interpretation for the value of this field. It can either be a single two-character code, or two distinct one-character codes, according to the needs of the ODFI and/or Originator involved. This field must be returned intact for any returned entry. + WEB uses the Discretionary Data Field as the Payment Type Code. + example: AB + addendaRecordIndicator: + type: integer + description: | + AddendaRecordIndicator indicates the existence of an Addenda Record. A value of "1" indicates that one or more addenda records follow, and "0" means no such record is present. + example: 1 + achOperatorRoutingNumber: + type: string + description: Routing number for ACH Operator + example: "987654320" + julianDay: + type: integer + description: Julian Day of the year + example: 12 + sequenceNumber: + type: integer + description: SequenceNumber is consecutively assigned to each Addenda05 Record following an Entry Detail Record. The first Addenda05 sequence number must always be a 1. + example: 1 + addenda99: + description: Addenda99 record for the ADV Entry Detail + items: + $ref: '#/components/schemas/Addenda99' + category: + type: string + description: Category defines if the entry is a Forward, Return, or NOC + example: Forward + ADVBatchControl: + properties: + ID: + description: Batch ID + type: string + example: 62d8f0cd + serviceClassCode: + description: Same as ServiceClassCode in BatchHeader record + type: integer + example: 220 + entryAddendaCount: + description: EntryAddendaCount is a tally of each Entry Detail Record and each Addenda Record processed, within either the batch or file as appropriate. + type: integer + example: 1 + entryHash: + description: | + Validate the Receiving DFI Identification in each Entry Detail Record is hashed to provide a check against inadvertent alteration of data contents due to hardware failure or program error. + In this context the Entry Hash is the sum of the corresponding fields in the Entry Detail Records on the file. + type: integer + example: 0 + totalDebit: + description: Contains accumulated Entry debit totals within the batch. + type: integer + example: 100 + totalCredit: + description: Contains accumulated Entry credit totals within the batch. + type: integer + example: 100 + achOperatorData: + description: Alphanumeric code used to identify an ACH Operator + type: string + example: user + ODFIIdentification: + description: The routing number is used to identify the DFI originating entries within a given branch. + type: string + example: "98765432" + batchNumber: + type: integer + description: BatchNumber is assigned in ascending sequence to each batch by the ODFI or its Sending Point in a given file of entries. Since the batch number in the Batch Header Record and the Batch Control Record is the same, the ascending sequence number should be assigned by batch and not by record. + example: 0 + required: + - serviceClassCode + - entryAddendaCount + - entryHash + - totalDebit + - totalCredit + - achOperatorData + - ODFIIdentification + - batchNumber + ADVFileControl: + properties: + ID: + type: string + description: ADV File Control Record + example: 842a2261 + batchCount: + description: Count of Batches in the File + type: integer + minimum: 1 + example: 1 + blockCount: + description: | + Total number of records in the file (include all headers and trailer) divided by 10 (This number must be evenly divisible by 10. If not, additional records consisting of all 9's are added to the file after the initial '9' record to fill out the block 10.) + type: integer + example: 1 + entryAddendaCount: + description: Total detail and addenda records in the file + type: integer + minimum: 1 + example: 1 + entryHash: + description: Calculated in the same manner as the batch total but includes total from entire file + type: integer + example: 0 + totalDebit: + description: Accumulated Batch debit totals within the file. + type: integer + example: 100 + totalCredit: + description: Accumulated Batch credit totals within the file. + type: integer + example: 20 + required: + - ID + - batchCount + - blockCount + - entryAddendaCount + - entryHash + - totalDebit + - totalCredit + Offset: + properties: + routingNumber: + type: string + description: ABA routing number + example: "987654320" + accountNumber: + type: string + description: Account number used to offset records + example: "216112" + accountType: + type: string + description: Account type used in offset record + enum: + - checking + - savings + description: + type: string + description: Memo for Offset EntryDetail record + example: OFFSET + required: + - routingNumber + - accountNumber + - accountType + - description + SegmentedFiles: + properties: + creditFileID: + type: string + description: File ID + example: "058960d8" + creditFile: + $ref: '#/components/schemas/File' + debitFileID: + type: string + description: File ID + example: "3cac5447" + debitFile: + $ref: '#/components/schemas/File' + error: + type: string + description: An error message describing the problem intended for humans. + example: Validation error(s) present. + ValidateOpts: + properties: + requireABAOrigin: + type: boolean + default: false + description: Require that the FileHeader ImmediateOrigin routing number which checksum matches. + bypassOriginValidation: + type: boolean + default: false + description: Skip ImmediateOrigin validation steps. + bypassDestinationValidation: + type: boolean + default: false + description: Skip ImmediateDestination validation steps. + customTraceNumbers: + type: boolean + default: false + description: Disable Nacha specified checks of TraceNumbers. + allowZeroBatches: + type: boolean + default: false + description: Allow the file to have zero batches. + allowMissingFileHeader: + type: boolean + default: false + description: Allow the file to be read without a FileHeader record. + allowMissingFileControl: + type: boolean + default: false + description: Allow the file to be read without a FileControl record. + bypassCompanyIdentificationMatch: + type: boolean + default: false + description: Allow batches in which the Company Identification field in the batch header and control do not match. + customReturnCodes: + type: boolean + default: false + description: Allow for non-standard/deprecated return codes (e.g. R97) + unequalServiceClassCode: + type: boolean + default: false + description: Skip equality checks for the ServiceClassCode in each pair of BatchHeader and BatchControl records. + allowUnorderedBatchNumbers: + type: boolean + default: false + description: Allow a file to be read with unordered batch numbers. + SegmentFileConfiguration: + properties: {} # TODO: Are there any config options people need? + SegmentFile: + properties: + file: + $ref: '#/components/schemas/File' + opts: + $ref: '#/components/schemas/SegmentFileConfiguration' diff --git a/reader.go b/reader.go index b83199527..ac8884fb5 100644 --- a/reader.go +++ b/reader.go @@ -1,6 +1,19 @@ -// Copyright 2016 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. package ach @@ -8,43 +21,58 @@ import ( "bufio" "fmt" "io" - "strconv" -) + "os" + "strings" + "unicode/utf8" -// ParseError is returned for parsing reader errors. -// The first line is 1. -type ParseError struct { - Line int // Line number where the error accurd - Record string // Name of the record type being parsed - Err error // The actual error -} + "github.com/moov-io/base" +) -func (e *ParseError) Error() string { - if e.Record == "" { - return fmt.Sprintf("line:%d %T %s", e.Line, e.Err, e.Err) - } - return fmt.Sprintf("line:%d record:%s %T %s", e.Line, e.Record, e.Err, e.Err) -} +var ( + // maxLines is the maximum number of lines a file can have. It is limited by the + // EntryAddendaCount field which has 8 digits, and the BatchCount field which has + // 6 digits in the File Control Record. So we can have at most the 2 file records, + // 2 records for each of 10^6 batches, 10^8 entry and addenda records, and 8 lines + // of 9's to round up to the nearest multiple of 10. + maxLines = 2 + 2000000 + 100000000 + 8 +) -// Reader reads records from a ACH-encoded file. +// Reader reads records from an ACH-encoded file. type Reader struct { - // r handles the IO.Reader sent to be parser. - scanner *bufio.Scanner // file is ach.file model being built as r is parsed. File File + + // IATCurrentBatch is the current IATBatch entries being parsed + IATCurrentBatch IATBatch + + // r handles the IO.Reader sent to be parser. + scanner *bufio.Scanner + // line is the current line being parsed from the input r line string + // currentBatch is the current Batch entries being parsed currentBatch Batcher + // line number of the file being parsed lineNum int + // recordName holds the current record name being parsed. recordName string + + // errors holds each error encountered when attempting to parse the file + errors base.ErrorList } -// error creates a new ParseError based on err. -func (r *Reader) error(err error) error { - return &ParseError{ +// error returns a new ParseError based on err +func (r *Reader) parseError(err error) error { + if err == nil { + return nil + } + if _, ok := err.(*base.ParseError); ok { + return err + } + return &base.ParseError{ Line: r.lineNum, Record: r.recordName, Err: err, @@ -57,6 +85,50 @@ func (r *Reader) addCurrentBatch(batch Batcher) { r.currentBatch = batch } +// addCurrentBatch creates the current batch type for the file being read. A successful +// current batch will be added to r.File once parsed. +func (r *Reader) addIATCurrentBatch(iatBatch IATBatch) { + r.IATCurrentBatch = iatBatch +} + +// SetValidation stores ValidateOpts on the Reader's underlying File which are to be used +// to override the default NACHA validation rules. +func (r *Reader) SetValidation(opts *ValidateOpts) { + if r == nil || opts == nil { + return + } + r.File.SetValidation(opts) +} + +// ReadFile attempts to open a file at path and read the contents before closing +// and returning the parsed ACH File. +func ReadFile(path string) (*File, error) { + fd, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("problem reading %s: %v", path, err) + } + defer fd.Close() + + file, err := NewReader(fd).Read() + return &file, err +} + +// ReadFiles attempts to open files at the given paths and read the contents +// of each before closing and returning the parsed ACH Files. +func ReadFiles(paths []string) ([]*File, error) { + var out []*File + for i := range paths { + file, err := ReadFile(paths[i]) + if err != nil { + return nil, err + } + if file != nil { + out = append(out, file) + } + } + return out, nil +} + // NewReader returns a new ACH Reader that reads from r. func NewReader(r io.Reader) *Reader { return &Reader{ @@ -64,48 +136,111 @@ func NewReader(r io.Reader) *Reader { } } -// Read reads each line of the ACH file and defines which parser to use based -// on the first character of each line. It also enforces ACH formating rules and returns -// the appropriate error if issues are found. +// Read reads each line in the underlying io.Reader and returns a File and any errors encountered. +// +// Read enforces ACH formatting rules and the first character of each line determines which parser is used. +// +// The returned File may not be valid. Callers should tabulate the File with File.Create followed by +// File.Validate to ensure it is Nacha compliant. +// +// Invalid files may be rejected by other financial institutions or ACH tools. func (r *Reader) Read() (File, error) { r.lineNum = 0 // read through the entire file for r.scanner.Scan() { line := r.scanner.Text() r.lineNum++ - lineLength := len(line) + if r.lineNum > maxLines { + r.errors.Add(ErrFileTooLong) + return r.File, r.errors + } + + lineLength := utf8.RuneCountInString(line) + switch { - case r.lineNum == 1 && lineLength > RecordLength && lineLength%RecordLength == 0: - if err := r.processFixedWidthFile(&line); err != nil { - return r.File, err + case r.lineNum == 1 && lineLength > RecordLength: + extraChars := lineLength % RecordLength + if extraChars != 0 { + err := fmt.Errorf( + "%d extra character(s) in ACH file: must be %d but found %d", + extraChars, + lineLength-extraChars, + lineLength, + ) + r.errors.Add(r.parseError(err)) + } else if err := r.processFixedWidthFile(&line); err != nil { + r.errors.Add(err) } case lineLength != RecordLength: - msg := fmt.Sprintf(msgRecordLength, lineLength) - err := &FileError{FieldName: "RecordLength", Value: strconv.Itoa(lineLength), Msg: msg} - return r.File, r.error(err) + if lineLength > RecordLength { + line = trimSpacesFromLongLine(line) + } + // right-pad the line with spaces + line, err := rightPadShortLine(line) + if err != nil { + r.errors.Add(r.parseError(err)) + } + r.line = line + // parse the line + if err := r.parseLine(); err != nil { + r.errors.Add(r.parseError(NewRecordWrongLengthErr(lineLength))) + r.errors.Add(err) + } + default: r.line = line if err := r.parseLine(); err != nil { - return r.File, err + r.errors.Add(err) } } } - if (FileHeader{}) == r.File.Header { - // Their must be at least one File Header + + // Add a lingering Batch to the file if there was no BatchControl record. + // This is common when files just contain a BatchHeader and EntryDetail records. + if r.currentBatch != nil { + r.File.AddBatch(r.currentBatch) + r.currentBatch = nil + } + + // Carry through any ValidateOpts for this comparison + if (FileHeader{validateOpts: r.File.validateOpts}) == r.File.Header { + // There must be at least one File Header r.recordName = "FileHeader" - return r.File, r.error(&FileError{Msg: msgFileHeader}) + r.errors.Add(ErrFileHeader) } - if (FileControl{}) == r.File.Control { - // Their must be at least one File Control - r.recordName = "FileControl" - return r.File, r.error(&FileError{Msg: msgFileControl}) + + if !r.File.IsADV() { + if (FileControl{}) == r.File.Control { + // There must be at least one File Control + r.recordName = "FileControl" + r.errors.Add(ErrFileControl) + } + } else { + if (ADVFileControl{}) == r.File.ADVControl { + // There must be at least one File Control + r.recordName = "FileControl" + r.errors.Add(ErrFileControl) + } } + if r.errors.Empty() { + return r.File, nil + } + return r.File, r.errors +} - return r.File, nil +func trimSpacesFromLongLine(s string) string { + return strings.TrimSuffix(s[:94], " ") +} + +func rightPadShortLine(s string) (string, error) { + if n := len(s); n > RecordLength { + return s, NewRecordWrongLengthErr(n) + } + return s + strings.Repeat(" ", 94-len(s)), nil } func (r *Reader) processFixedWidthFile(line *string) error { - // it should be safe to parse this byte by byte since ACH files are ascii only + // It should be safe to parse this byte by byte since ACH files are ASCII only. record := "" for i, c := range *line { record = record + string(c) @@ -127,27 +262,39 @@ func (r *Reader) parseLine() error { return err } case batchHeaderPos: - if err := r.parseBatchHeader(); err != nil { + if err := r.parseBH(); err != nil { return err } case entryDetailPos: - if err := r.parseEntryDetail(); err != nil { + if err := r.parseED(); err != nil { return err } case entryAddendaPos: - if err := r.parseAddenda(); err != nil { + if err := r.parseEDAddenda(); err != nil { return err } case batchControlPos: if err := r.parseBatchControl(); err != nil { return err } - if err := r.currentBatch.Validate(); err != nil { - r.recordName = "Batches" - return r.error(err) + if r.currentBatch != nil { + batch := r.currentBatch + r.currentBatch = nil + batch.SetValidation(r.File.validateOpts) + r.File.AddBatch(batch) + if err := batch.Validate(); err != nil { + r.recordName = "Batches" + return r.parseError(err) + } + } else { + batch := r.IATCurrentBatch + r.IATCurrentBatch = IATBatch{} + r.File.AddIATBatch(batch) + if err := batch.Validate(); err != nil { + r.recordName = "Batches" + return r.parseError(err) + } } - r.File.AddBatch(r.currentBatch) - r.currentBatch = nil case fileControlPos: if r.line[:2] == "99" { // final blocking padding @@ -157,8 +304,49 @@ func (r *Reader) parseLine() error { return err } default: - msg := fmt.Sprintf(msgUnknownRecordType, r.line[:1]) - return r.error(&FileError{FieldName: "recordType", Value: r.line[:1], Msg: msg}) + return NewErrUnknownRecordType(r.line[:1]) + } + return nil +} + +// parseBH parses determines whether to parse an IATBatchHeader or BatchHeader +func (r *Reader) parseBH() error { + if r.line[50:53] == IAT || strings.TrimSpace(r.line[04:20]) == IATCOR { + if err := r.parseIATBatchHeader(); err != nil { + return err + } + } else { + if err := r.parseBatchHeader(); err != nil { + return err + } + } + return nil +} + +// parseEd parses determines whether to parse an IATEntryDetail or EntryDetail +func (r *Reader) parseED() error { + if r.IATCurrentBatch.Header != nil { + if err := r.parseIATEntryDetail(); err != nil { + return err + } + } else { + if err := r.parseEntryDetail(); err != nil { + return err + } + } + return nil +} + +// parseEd parses determines whether to parse an IATEntryDetail Addenda or EntryDetail Addenda +func (r *Reader) parseEDAddenda() error { + if r.currentBatch != nil && r.currentBatch.GetHeader().CompanyName != IATCOR { + if err := r.parseAddenda(); err != nil { + return err + } + } else { + if err := r.parseIATAddenda(); err != nil { + return err + } } return nil } @@ -166,14 +354,16 @@ func (r *Reader) parseLine() error { // parseFileHeader takes the input record string and parses the FileHeaderRecord values func (r *Reader) parseFileHeader() error { r.recordName = "FileHeader" - if (FileHeader{}) != r.File.Header { - // Their can only be one File Header per File exit - r.error(&FileError{Msg: msgFileHeader}) + // Pass through any ValidateOpts from the Reader for this comparison + // as we need to compare the other struct fields (e.g. origin, destination) + if (FileHeader{validateOpts: r.File.validateOpts}) != r.File.Header { + // There can only be one File Header per File exit + return ErrFileHeader } r.File.Header.Parse(r.line) if err := r.File.Header.Validate(); err != nil { - return r.error(err) + return r.parseError(err) } return nil } @@ -183,24 +373,22 @@ func (r *Reader) parseBatchHeader() error { r.recordName = "BatchHeader" if r.currentBatch != nil { // batch header inside of current batch - return r.error(&FileError{Msg: msgFileBatchInside}) + return ErrFileBatchHeaderInsideBatch } // Ensure we have a valid batch header before building a batch. bh := NewBatchHeader() bh.Parse(r.line) if err := bh.Validate(); err != nil { - return r.error(err) + return r.parseError(err) } - // Passing SEC type into NewBatch creates a Batcher of SEC code type. - batch, err := NewBatch(BatchParam{ - StandardEntryClass: bh.StandardEntryClassCode}) + // Passing BatchHeader into NewBatch creates a Batcher of SEC code type. + batch, err := NewBatch(bh) if err != nil { - return r.error(err) + return r.parseError(err) } - batch.SetHeader(bh) r.addCurrentBatch(batch) return nil } @@ -208,71 +396,158 @@ func (r *Reader) parseBatchHeader() error { // parseEntryDetail takes the input record string and parses the EntryDetailRecord values func (r *Reader) parseEntryDetail() error { r.recordName = "EntryDetail" + if r.currentBatch == nil { - return r.error(&FileError{Msg: msgFileBatchOutside}) + return ErrFileEntryOutsideBatch } - ed := new(EntryDetail) - ed.Parse(r.line) - if err := ed.Validate(); err != nil { - return r.error(err) + if r.currentBatch.GetHeader().StandardEntryClassCode != ADV { + ed := NewEntryDetail() + ed.Parse(r.line) + if err := ed.Validate(); err != nil { + return r.parseError(err) + } + r.currentBatch.AddEntry(ed) + } else { + ed := NewADVEntryDetail() + ed.Parse(r.line) + if err := ed.Validate(); err != nil { + return r.parseError(err) + } + r.currentBatch.AddADVEntry(ed) } - r.currentBatch.AddEntry(ed) return nil } -// parseAddendaRecord takes the input record string and parses the AddendaRecord values +// parseAddendaRecord takes the input record string and create an Addenda Type appended to the last EntryDetail func (r *Reader) parseAddenda() error { r.recordName = "Addenda" - if r.currentBatch == nil { - msg := fmt.Sprintf(msgFileBatchOutside) - return r.error(&FileError{FieldName: "Addenda", Msg: msg}) + return ErrFileAddendaOutsideBatch } - if len(r.currentBatch.GetEntries()) == 0 { - return r.error(&FileError{FieldName: "Addenda", Msg: msgFileBatchOutside}) - } - entryIndex := len(r.currentBatch.GetEntries()) - 1 - entry := r.currentBatch.GetEntries()[entryIndex] - switch sec := r.currentBatch.GetHeader().StandardEntryClassCode; sec { - case ppd: - if entry.AddendaRecordIndicator == 1 { - addenda := Addenda{} - addenda.Parse(r.line) - if err := addenda.Validate(); err != nil { - return r.error(err) - } - r.currentBatch.GetEntries()[entryIndex].AddAddenda(addenda) - } else { - msg := fmt.Sprintf(msgBatchAddendaIndicator) - return r.error(&FileError{FieldName: "AddendaRecordIndicator", Msg: msg}) + if r.currentBatch.GetHeader().StandardEntryClassCode != ADV { + if len(r.currentBatch.GetEntries()) == 0 { + return ErrFileAddendaOutsideEntry } - case web, ccd, cor: // only care for returns + entryIndex := len(r.currentBatch.GetEntries()) - 1 + entry := r.currentBatch.GetEntries()[entryIndex] + if entry.AddendaRecordIndicator == 1 { - returnAddenda := ReturnAddenda{} - returnAddenda.Parse(r.line) - if err := returnAddenda.Validate(); err != nil { - return r.error(err) + switch r.line[1:3] { + case "02": + addenda02 := NewAddenda02() + addenda02.Parse(r.line) + if err := addenda02.Validate(); err != nil { + return r.parseError(err) + } + r.currentBatch.GetEntries()[entryIndex].Addenda02 = addenda02 + case "05": + addenda05 := NewAddenda05() + addenda05.Parse(r.line) + if err := addenda05.Validate(); err != nil { + return r.parseError(err) + } + r.currentBatch.GetEntries()[entryIndex].AddAddenda05(addenda05) + case "98": + addenda98 := NewAddenda98() + addenda98.Parse(r.line) + if err := addenda98.Validate(); err != nil { + return r.parseError(err) + } + r.currentBatch.GetEntries()[entryIndex].Category = CategoryNOC + r.currentBatch.GetEntries()[entryIndex].Addenda98 = addenda98 + case "99": + // Addenda99, Addenda99Dishonored, Addenda99Contested records both have their code + // in the same spot, so we need to determine which to parse by the value. + switch { + case IsDishonoredReturnCode(r.line[3:6]): + addenda99Dishonored := NewAddenda99Dishonored() + addenda99Dishonored.Parse(r.line) + addenda99Dishonored.SetValidation(r.File.validateOpts) + if err := addenda99Dishonored.Validate(); err != nil { + return r.parseError(err) + } + r.currentBatch.GetEntries()[entryIndex].Addenda99Dishonored = addenda99Dishonored + r.currentBatch.GetEntries()[entryIndex].Category = CategoryDishonoredReturn + + case IsContestedReturnCode(r.line[3:6]): + addenda99Contested := NewAddenda99Contested() + addenda99Contested.Parse(r.line) + addenda99Contested.SetValidation(r.File.validateOpts) + if err := addenda99Contested.Validate(); err != nil { + return r.parseError(err) + } + r.currentBatch.GetEntries()[entryIndex].Addenda99Contested = addenda99Contested + r.currentBatch.GetEntries()[entryIndex].Category = CategoryDishonoredReturnContested + + default: + addenda99 := NewAddenda99() + addenda99.Parse(r.line) + addenda99.SetValidation(r.File.validateOpts) + if err := addenda99.Validate(); err != nil { + return r.parseError(err) + } + r.currentBatch.GetEntries()[entryIndex].Addenda99 = addenda99 + r.currentBatch.GetEntries()[entryIndex].Category = CategoryReturn + } } - r.currentBatch.GetEntries()[entryIndex].AddReturnAddenda(returnAddenda) } else { - msg := fmt.Sprintf(msgBatchAddendaIndicator) - return r.error(&FileError{FieldName: "AddendaRecordIndicator", Msg: msg}) + return r.parseError(r.currentBatch.Error("AddendaRecordIndicator", ErrBatchAddendaIndicator)) + } + } else { + if err := r.parseADVAddenda(); err != nil { + return err } } return nil } +// parseADVAddenda takes the input record string and create an Addenda99 appended to the last ADVEntryDetail +func (r *Reader) parseADVAddenda() error { + if len(r.currentBatch.GetADVEntries()) == 0 { + return ErrFileAddendaOutsideEntry + } + entryIndex := len(r.currentBatch.GetADVEntries()) - 1 + entry := r.currentBatch.GetADVEntries()[entryIndex] + + if entry.AddendaRecordIndicator != 1 { + return r.parseError(r.currentBatch.Error("AddendaRecordIndicator", ErrBatchAddendaIndicator)) + } + addenda99 := NewAddenda99() + addenda99.Parse(r.line) + if err := addenda99.Validate(); err != nil { + return r.parseError(err) + } + r.currentBatch.GetADVEntries()[entryIndex].Category = CategoryReturn + r.currentBatch.GetADVEntries()[entryIndex].Addenda99 = addenda99 + return nil +} + // parseBatchControl takes the input record string and parses the BatchControlRecord values func (r *Reader) parseBatchControl() error { r.recordName = "BatchControl" - if r.currentBatch == nil { + if r.currentBatch == nil && r.IATCurrentBatch.GetEntries() == nil { // batch Control without a current batch - return r.error(&FileError{Msg: msgFileBatchOutside}) + return ErrFileBatchControlOutsideBatch } - r.currentBatch.GetControl().Parse(r.line) - if err := r.currentBatch.GetControl().Validate(); err != nil { - return r.error(err) + if r.currentBatch != nil { + if r.currentBatch.GetHeader().StandardEntryClassCode == ADV { + r.currentBatch.GetADVControl().Parse(r.line) + if err := r.currentBatch.GetADVControl().Validate(); err != nil { + return r.parseError(err) + } + } else { + r.currentBatch.GetControl().Parse(r.line) + if err := r.currentBatch.GetControl().Validate(); err != nil { + return r.parseError(err) + } + } + } else { + r.IATCurrentBatch.GetControl().Parse(r.line) + if err := r.IATCurrentBatch.GetControl().Validate(); err != nil { + return r.parseError(err) + } + } return nil } @@ -280,13 +555,204 @@ func (r *Reader) parseBatchControl() error { // parseFileControl takes the input record string and parses the FileControlRecord values func (r *Reader) parseFileControl() error { r.recordName = "FileControl" - if (FileControl{}) != r.File.Control { - // Can be only one file control per file - return r.error(&FileError{Msg: msgFileControl}) + + if !r.File.IsADV() { + if (FileControl{}) != r.File.Control { + // Can be only one file control per file + return ErrFileControl + } + r.File.Control.Parse(r.line) + if err := r.File.Control.Validate(); err != nil { + return r.parseError(err) + } + } else { + if (ADVFileControl{}) != r.File.ADVControl { + // Can be only one file control per file + return ErrFileControl + } + r.File.ADVControl.Parse(r.line) + if err := r.File.ADVControl.Validate(); err != nil { + return r.parseError(err) + } + } + return nil +} + +// IAT specific reader functions + +// parseIATBatchHeader takes the input record string and parses the FileHeaderRecord values +func (r *Reader) parseIATBatchHeader() error { + r.recordName = "BatchHeader" + if r.IATCurrentBatch.Header != nil { + // batch header inside of current batch + return ErrFileBatchHeaderInsideBatch + } + + // Ensure we have a valid IAT BatchHeader before building a batch. + bh := NewIATBatchHeader() + bh.Parse(r.line) + if err := bh.Validate(); err != nil { + return r.parseError(err) + } + + // Passing BatchHeader into NewBatchIAT creates a Batcher of IAT SEC code type. + iatBatch := NewIATBatch(bh) + r.addIATCurrentBatch(iatBatch) + + return nil +} + +// parseIATEntryDetail takes the input record string and parses the EntryDetailRecord values +func (r *Reader) parseIATEntryDetail() error { + r.recordName = "EntryDetail" + + if r.IATCurrentBatch.Header == nil { + return ErrFileEntryOutsideBatch + } + + ed := new(IATEntryDetail) + ed.Parse(r.line) + if err := ed.Validate(); err != nil { + return r.parseError(err) + } + r.IATCurrentBatch.AddEntry(ed) + return nil +} + +// parseIATAddenda takes the input record string and create an Addenda Type appended to the last EntryDetail +func (r *Reader) parseIATAddenda() error { + r.recordName = "Addenda" + + if r.IATCurrentBatch.GetEntries() == nil { + return ErrFileAddendaOutsideEntry + } + entryIndex := len(r.IATCurrentBatch.GetEntries()) - 1 + entry := r.IATCurrentBatch.GetEntries()[entryIndex] + + if entry.AddendaRecordIndicator == 1 { + err := r.switchIATAddenda(entryIndex) + if err != nil { + return r.parseError(err) + } + } else { + return r.parseError(fieldError("AddendaRecordIndicator", ErrIATBatchAddendaIndicator)) + } + return nil +} + +func (r *Reader) switchIATAddenda(entryIndex int) error { + switch r.line[1:3] { + // IAT mandatory and optional Addenda + case "10", "11", "12", "13", "14", "15", "16", "17", "18": + err := r.mandatoryOptionalIATAddenda(entryIndex) + if err != nil { + return err + } + // IATNOC + case "98": + err := r.nocIATAddenda(entryIndex) + if err != nil { + return err + } + // IAT return Addenda + case "99": + err := r.returnIATAddenda(entryIndex) + if err != nil { + return err + } + } + return nil +} + +// mandatoryOptionalIATAddenda parses and validates mandatory IAT addenda records: Addenda10, +// Addenda11, Addenda12, Addenda13, Addenda14, Addenda15, Addenda16, Addenda17, Addenda18 +func (r *Reader) mandatoryOptionalIATAddenda(entryIndex int) error { + switch r.line[1:3] { + case "10": + addenda10 := NewAddenda10() + addenda10.Parse(r.line) + if err := addenda10.Validate(); err != nil { + return err + } + r.IATCurrentBatch.Entries[entryIndex].Addenda10 = addenda10 + case "11": + addenda11 := NewAddenda11() + addenda11.Parse(r.line) + if err := addenda11.Validate(); err != nil { + return err + } + r.IATCurrentBatch.Entries[entryIndex].Addenda11 = addenda11 + case "12": + addenda12 := NewAddenda12() + addenda12.Parse(r.line) + if err := addenda12.Validate(); err != nil { + return err + } + r.IATCurrentBatch.Entries[entryIndex].Addenda12 = addenda12 + case "13": + addenda13 := NewAddenda13() + addenda13.Parse(r.line) + if err := addenda13.Validate(); err != nil { + return err + } + r.IATCurrentBatch.Entries[entryIndex].Addenda13 = addenda13 + case "14": + addenda14 := NewAddenda14() + addenda14.Parse(r.line) + if err := addenda14.Validate(); err != nil { + return err + } + r.IATCurrentBatch.Entries[entryIndex].Addenda14 = addenda14 + case "15": + addenda15 := NewAddenda15() + addenda15.Parse(r.line) + if err := addenda15.Validate(); err != nil { + return err + } + r.IATCurrentBatch.Entries[entryIndex].Addenda15 = addenda15 + case "16": + addenda16 := NewAddenda16() + addenda16.Parse(r.line) + if err := addenda16.Validate(); err != nil { + return err + } + r.IATCurrentBatch.Entries[entryIndex].Addenda16 = addenda16 + case "17": + addenda17 := NewAddenda17() + addenda17.Parse(r.line) + if err := addenda17.Validate(); err != nil { + return err + } + r.IATCurrentBatch.Entries[entryIndex].AddAddenda17(addenda17) + case "18": + addenda18 := NewAddenda18() + addenda18.Parse(r.line) + if err := addenda18.Validate(); err != nil { + return err + } + r.IATCurrentBatch.Entries[entryIndex].AddAddenda18(addenda18) + } + return nil +} + +// nocIATAddenda parses and validates IAT NOC record Addenda98 +func (r *Reader) nocIATAddenda(entryIndex int) error { + addenda98 := NewAddenda98() + addenda98.Parse(r.line) + if err := addenda98.Validate(); err != nil { + return err } - r.File.Control.Parse(r.line) - if err := r.File.Control.Validate(); err != nil { - return r.error(err) + r.IATCurrentBatch.Entries[entryIndex].Addenda98 = addenda98 + return nil +} + +// returnIATAddenda parses and validates IAT return record Addenda99 +func (r *Reader) returnIATAddenda(entryIndex int) error { + addenda99 := NewAddenda99() + addenda99.Parse(r.line) + if err := addenda99.Validate(); err != nil { + return err } + r.IATCurrentBatch.Entries[entryIndex].Addenda99 = addenda99 return nil } diff --git a/reader_test.go b/reader_test.go index 10d16db3e..213042835 100644 --- a/reader_test.go +++ b/reader_test.go @@ -1,37 +1,240 @@ -// Copyright 2017 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. package ach import ( + "bytes" + "encoding/json" + "errors" + "io" "os" + "path/filepath" "strings" "testing" + + "github.com/moov-io/base" + "github.com/stretchr/testify/require" ) -func TestParseError(t *testing.T) { - e := &FieldError{FieldName: "testField", Value: "nil", Msg: "could not parse"} - err := &ParseError{Line: 63, Err: e} - if err.Error() != "line:63 *ach.FieldError testField nil could not parse" { - t.Error("ParseError error string formating has changed") +func readACHFilepath(path string) (*File, error) { + fd, err := os.Open(path) + if err != nil { + return nil, err + } + defer fd.Close() + + f, err := NewReader(fd).Read() + return &f, err +} + +func TestReadFile(t *testing.T) { + file, err := ReadFile(filepath.Join("test", "testdata", "web-debit.ach")) + if err != nil { + t.Fatal(err) } - err.Record = "TestRecord" - if err.Error() != "line:63 record:TestRecord *ach.FieldError testField nil could not parse" { - t.Error("ParseError error string formating has changed") + if origin := file.Header.ImmediateOrigin; origin != "231380104" { + t.Errorf("origin=%s", origin) } } -// TestDecode is a complete file decoding test. A canary test -func TestPPDDebitRead(t *testing.T) { - f, err := os.Open("./testdata/ppd-debit.ach") +func TestReadFiles(t *testing.T) { + paths := []string{ + filepath.Join("test", "testdata", "return-WEB.ach"), + filepath.Join("test", "testdata", "web-debit.ach"), + } + files, err := ReadFiles(paths) + if err != nil { + t.Fatal(err) + } + if n := len(files); n != 2 { + t.Fatalf("read %d files", n) + } +} + +func TestReadPartial(t *testing.T) { + file, err := ReadFile(filepath.Join("test", "testdata", "bh-ed-ad-bh-ed-ad-ed-ad.ach")) + if err != nil { + t.Log(err) // we expect errors -- display them + } + if len(file.Batches) != 1 { + t.Fatalf("unexpected batches: %#v", file.Batches) + } + entries := file.Batches[0].GetEntries() + if len(entries) != 3 { + t.Error("unexpected entry details") + for i := range entries { + t.Errorf(" %#v", entries[i]) + } + t.Fatal("") + } + + // batch + bh := file.Batches[0].GetHeader() + if bh.ServiceClassCode != DebitsOnly { + t.Errorf("ServiceClassCode=%d", bh.ServiceClassCode) + } + if bh.CompanyName != "Adam Shannon" { + t.Errorf("CompanyName=%s", bh.CompanyName) + } + if bh.CompanyIdentification != "MOOVYYYYYY" { + t.Errorf("CompanyIdentification=%s", bh.CompanyIdentification) + } + if bh.StandardEntryClassCode != "PPD" { + t.Errorf("StandardEntryClassCode=%s", bh.StandardEntryClassCode) + } + + // entries + entry := entries[0] + if entry.TransactionCode != CheckingReturnNOCDebit { + t.Errorf("TransactionCode=%d", entry.TransactionCode) + } + if num := strings.TrimSpace(entry.DFIAccountNumber); num != "15XXXXXXXXXX1" { + t.Errorf("DFIAccountNumber=%q", num) + } + if entry.Addenda99 != nil { + if code := entry.Addenda99.ReturnCode; code != "R02" { + t.Errorf("ReturnCode=%s", code) + } + } else { + t.Errorf("nil Addenda99") + } + + entry = entries[1] + if entry.TransactionCode != CheckingReturnNOCCredit { + t.Errorf("TransactionCode=%d", entry.TransactionCode) + } + if num := strings.TrimSpace(entry.DFIAccountNumber); num != "1XXXXXXXXXXX2" { + t.Errorf("DFIAccountNumber=%q", num) + } + if entry.Addenda99 != nil { + if code := entry.Addenda99.ReturnCode; code != "R03" { + t.Errorf("ReturnCode=%s", code) + } + } else { + t.Errorf("nil Addenda99") + } +} + +func TestReader__crashers(t *testing.T) { + dir := filepath.Join("test", "testdata", "crashers") + fds, err := os.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + + var currentFile string + + // log current file when we panic + defer func() { + if v := recover(); v != nil { + if _, ok := v.(error); ok { + t.Errorf("panic from parsing %s", filepath.Join(dir, currentFile)) + panic(v) // throw original panic so testing package emits trace + } + } + }() + + for i := range fds { + currentFile = fds[i].Name() + t.Logf("parsing %s to see if it crashes...", currentFile) + + f, err := os.Open(filepath.Join(dir, fds[i].Name())) + if err != nil { + t.Fatal(err) + } + NewReader(f).Read() // making sure we don't panic + } +} + +func TestParser__errors(t *testing.T) { + var errorList base.ErrorList + var buf bytes.Buffer + + // nil + errorList.Print(&buf) + if v := buf.String(); v != "" { + t.Errorf("got %q", v) + } + if v := errorList.Error(); v != "" { + t.Errorf("got %q", v) + } + buf.Reset() + + // empty + errorList = make(base.ErrorList, 0) + errorList.Print(&buf) + if v := buf.String(); v != "" { + t.Errorf("got %q", v) + } + if v := errorList.Error(); v != "" { + t.Errorf("got %q", v) + } + buf.Reset() + + // one error + errorList.Add(errors.New("testing")) + errorList.Print(&buf) + if v := buf.String(); v != "testing" { + t.Errorf("got %q", v) + } + if v := errorList.Error(); v != "testing" { + t.Errorf("got %q", v) + } + buf.Reset() + + // multiple errors + errorList.Add(errors.New("other error")) + errorList.Add(errors.New("last")) + errorList.Print(&buf) + if v := buf.String(); v != "testing\n other error\n last" { + t.Errorf("got %q", v) + } + if v := errorList.Error(); v != "testing\n other error\n last" { + t.Errorf("got %q", v) + } +} + +func TestParser__errorJSON(t *testing.T) { + var errorList base.ErrorList + errorList.Add(errors.New("first")) + errorList.Add(errors.New("second")) + errorList.Add(errors.New("third")) + + // marshal json + bs, err := json.Marshal(errorList) + if err != nil { + t.Fatal(err) + } + v := string(bs) + if v != "\"first\\n second\\n third\"" { // JSON strings are quoted + t.Errorf("got %q", v) + } +} + +// testPPDDebitRead validates reading a PPD debit +func testPPDDebitRead(t testing.TB) { + f, err := os.Open(filepath.Join("test", "testdata", "ppd-debit.ach")) if err != nil { t.Errorf("%T: %s", err, err) } defer f.Close() r := NewReader(f) - _, err = r.Read() - if err != nil { + if _, err := r.Read(); err != nil { t.Errorf("%T: %s", err, err) } if err = r.File.Validate(); err != nil { @@ -39,8 +242,22 @@ func TestPPDDebitRead(t *testing.T) { } } -func TestWEBDebitRead(t *testing.T) { - f, err := os.Open("./testdata/web-debit.ach") +// TestPPDDebitRead tests validating reading a PPD debit +func TestPPDDebitRead(t *testing.T) { + testPPDDebitRead(t) +} + +// BenchmarkPPDDebitRead benchmarks validating reading a PPD debit +func BenchmarkPPDDebitRead(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testPPDDebitRead(b) + } +} + +// testWEBDebitRead validates reading a WEB debit +func testWEBDebitRead(t testing.TB) { + f, err := os.Open(filepath.Join("test", "testdata", "web-debit.ach")) if err != nil { t.Errorf("%T: %s", err, err) } @@ -55,9 +272,22 @@ func TestWEBDebitRead(t *testing.T) { } } -// TestDecode is a complete file decoding test. A canary test -func TestPPDDebitFixedLengthRead(t *testing.T) { - f, err := os.Open("./testdata/ppd-debit-fixedLength.ach") +// TestWEBDebitRead tests validating reading a WEB debit +func TestWEBDebitRead(t *testing.T) { + testWEBDebitRead(t) +} + +// BenchmarkWEBDebitRead benchmarks validating reading a WEB debit +func BenchmarkWEBDebitRead(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testWEBDebitRead(b) + } +} + +// testPPDDebitFixedLengthRead validates reading a PPD debit fixed width length +func testPPDDebitFixedLengthRead(t testing.TB) { + f, err := os.Open(filepath.Join("test", "testdata", "ppd-debit-fixedLength.ach")) if err != nil { t.Errorf("%T: %s", err, err) } @@ -69,42 +299,92 @@ func TestPPDDebitFixedLengthRead(t *testing.T) { } } -func TestRecordTypeUnknown(t *testing.T) { +// TestPPDDebitFixedLengthRead test validates reading a PPD debit fixed width length +func TestPPDDebitFixedLengthRead(t *testing.T) { + testPPDDebitFixedLengthRead(t) +} + +// BenchmarkPPDDebitFixedLengthRead benchmark validates reading a PPD debit fixed width length +func BenchmarkPPDDebitFixedLengthRead(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testPPDDebitFixedLengthRead(b) + } +} + +func TestPPDDebitFixedLengthRead__InvalidLength(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "ppd-debit-fixedLengthInvalid.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + + r := NewReader(f) + _, err = r.Read() + if err == nil { + t.Errorf("expected error") + } + + wantErr := "1 extra character(s) in ACH file: must be 470 but found 471" + if err != nil && !strings.Contains(err.Error(), wantErr) { + t.Errorf("want: %v, got: %v", wantErr, err) + } +} + +// testRecordTypeUnknown validates record type unknown +func testRecordTypeUnknown(t testing.TB) { var line = "301 076401251 0764012510807291511A094101achdestname companyname " r := NewReader(strings.NewReader(line)) _, err := r.Read() - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*FileError); ok { - if e.FieldName != "recordType" { - t.Errorf("%T: %s", e, e) - } - } else { - t.Errorf("%T: %s", err, err) - } + if !base.Has(err, NewErrUnknownRecordType("3")) { + t.Errorf("%T: %s", err, err) } } -func TestTwoFileHeaders(t *testing.T) { +// TestRecordTypeUnknown tests validating record type unknown +func TestRecordTypeUnknown(t *testing.T) { + testRecordTypeUnknown(t) +} + +// BenchmarkRecordTypeUnknown benchmarks validating record type unknown +func BenchmarkRecordTypeUnknown(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testRecordTypeUnknown(b) + } +} + +// testTwoFileHeaders validates one file header +func testTwoFileHeaders(t testing.TB) { var line = "101 076401251 0764012510807291511A094101achdestname companyname " var twoHeaders = line + "\n" + line r := NewReader(strings.NewReader(twoHeaders)) _, err := r.Read() - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*FileError); ok { - if e.Msg != msgFileControl { - t.Errorf("%T: %s", e, e) - } - } else { - t.Errorf("%T: %s", err, err) - } + + if !base.Has(err, ErrFileHeader) { + t.Errorf("%T: %s", err, err) } } -func TestTwoFileControls(t *testing.T) { +// TestTwoFileHeaders tests validating one file header +func TestTwoFileHeaders(t *testing.T) { + testTwoFileHeaders(t) +} + +// BenchmarkTwoFileHeaders benchmarks +func BenchmarkTwoFileHeaders(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testTwoFileHeaders(b) + } +} + +// testTwoFileControls validates one file control +func testTwoFileControls(t testing.TB) { var line = "9000001000001000000010005320001000000010500000000000000 " var twoControls = line + "\n" + line r := NewReader(strings.NewReader(twoControls)) - r.addCurrentBatch(NewBatchPPD()) + r.addCurrentBatch(NewBatchPPD(mockBatchPPDHeader())) bc := BatchControl{EntryAddendaCount: 1, TotalDebitEntryDollarAmount: 10500, EntryHash: 5320001} @@ -112,245 +392,560 @@ func TestTwoFileControls(t *testing.T) { r.File.AddBatch(r.currentBatch) r.File.Control.EntryHash = 5320001 + _, err := r.Read() - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*FileError); ok { - if e.Msg != msgFileControl { - t.Errorf("%T: %s", e, e) - } - } else { - t.Errorf("%T: %s", err, err) - } + if !base.Has(err, ErrFileControl) { + t.Errorf("%T: %s", err, err) } } -func TestFileLineShort(t *testing.T) { - var line = "1 line is only 70 characters ........................................!" +// TestTwoFileControls tests validating one file control +func TestTwoFileControls(t *testing.T) { + testTwoFileControls(t) +} + +// BenchmarkTwoFileControls benchmarks validating one file control +func BenchmarkTwoFileControls(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testTwoFileControls(b) + } +} + +// testFileLineEmpty verifies empty files fail to parse +func testFileLineEmpty(t testing.TB) { + line := "" r := NewReader(strings.NewReader(line)) _, err := r.Read() - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*FileError); ok { - if e.FieldName != "RecordLength" { - t.Errorf("%T: %s", e, e) - } - } else { - t.Errorf("%T: %s", e, e) - } + if !base.Has(err, ErrFileHeader) { + t.Errorf("%T: %s", err, err) } } -func TestFileLineLong(t *testing.T) { - var line = "1 line is 100 characters ..........................................................................!" +// TestFileLineEmpty tests validating empty file fails to parse +func TestFileLineEmpty(t *testing.T) { + testFileLineEmpty(t) +} + +// BenchmarkFileLineEmpty benchmarks validating empty file fails to parse +func BenchmarkFileLineEmpty(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileLineEmpty(b) + } +} + +// testFileLineShort validates file line is short +func testFileLineShort(t testing.TB) { + line := "1 line is only 70 characters ........................................!" r := NewReader(strings.NewReader(line)) _, err := r.Read() - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*FileError); ok { - if e.FieldName != "RecordLength" { - t.Errorf("%T: %s", e, e) - } - } else { - t.Errorf("%T: %s", err, err) - } + + if !base.Has(err, NewRecordWrongLengthErr(70)) { + t.Errorf("%T: %s", err, err) } } -// TestFileFileHeaderErr ensure a parse validation error flows back from the parser. -func TestFileFileHeaderErr(t *testing.T) { +// TestFileLineShort tests validating file line is short +func TestFileLineShort(t *testing.T) { + testFileLineShort(t) +} + +// BenchmarkFileLineShort benchmarks validating file line is short +func BenchmarkFileLineShort(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileLineShort(b) + } +} + +// testFileLineLong validates file line is long +func testFileLineLong(t testing.TB) { + var line = "1 line is 100 characters ..........................................................................!\n2 line is 94 characters ....................................................................!\n" + r := NewReader(strings.NewReader(line)) + _, err := r.Read() + + if !base.Has(err, NewRecordWrongLengthErr(100)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileLineLong tests validating file line is long +func TestFileLineLong(t *testing.T) { + testFileLineLong(t) +} + +// BenchmarkFileLineLong benchmarks validating file line is long +func BenchmarkFileLineLong(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileLineLong(b) + } +} + +// testFileFileHeaderErr validates error flows back from the parser +func testFileFileHeaderErr(t testing.TB) { fh := mockFileHeader() - fh.ImmediateOrigin = 0 + // fh.ImmediateOrigin = "0000000000" + fh.ImmediateOrigin = "" r := NewReader(strings.NewReader(fh.String())) // necessary to have a file control not nil r.File.Control = mockFileControl() _, err := r.Read() - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", e, e) - } - } - } else { + // TODO: is this testing what we want to be testing? + if !base.Has(err, ErrConstructor) { t.Errorf("%T: %s", err, err) } } -// TestFileBatchHeaderErr ensure a parse validation error flows back from the parser. -func TestFileBatchHeaderErr(t *testing.T) { +// TestFileFileHeaderErr tests validating error flows back from the parser +func TestFileFileHeaderErr(t *testing.T) { + testFileFileHeaderErr(t) +} + +// BenchmarkFileFileHeaderErr benchmarks validating error flows back from the parse +func BenchmarkFileFileHeaderErr(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileFileHeaderErr(b) + } +} + +// testFileBatchHeaderErr validates error flows back from the parser +func testFileBatchHeaderErr(t testing.TB) { bh := mockBatchHeader() - bh.ODFIIdentification = 0 + //bh.ODFIIdentification = 0 + bh.ODFIIdentification = "" r := NewReader(strings.NewReader(bh.String())) _, err := r.Read() - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", e, e) - } - } - } else { + // TODO: is this testing what we want to be testing? + if !base.Has(err, ErrFileHeader) { t.Errorf("%T: %s", err, err) } } -// TestFileBatchHeaderErr Error when two batch headers exists in a current batch -func TestFileBatchHeaderDuplicate(t *testing.T) { +// TestFileBatchHeaderErr tests validating error flows back from the parser +func TestFileBatchHeaderErr(t *testing.T) { + testFileBatchHeaderErr(t) +} + +// BenchmarkFileBatchHeaderErr benchmarks validating error flows back from the parser +func BenchmarkFileBatchHeaderErr(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileBatchHeaderErr(b) + } +} + +// testFileBatchHeaderDuplicate validates when two batch headers exists in a current batch +func testFileBatchHeaderDuplicate(t testing.TB) { // create a new Batch header string - bh := mockBatchHeader() + bh := mockBatchPPDHeader() r := NewReader(strings.NewReader(bh.String())) - // instantitate a batch header in the reader - r.addCurrentBatch(NewBatchPPD()) + // instantiate a batch header in the reader + r.addCurrentBatch(NewBatchPPD(bh)) // read should fail because it is parsing a second batch header and there can only be one. _, err := r.Read() - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", e, e) - } - } - } else { + if !base.Has(err, ErrFileBatchHeaderInsideBatch) { t.Errorf("%T: %s", err, err) } } -// TestFileEntryDetailOutsideBatch ensure EntryDetail does not exist outside of Batch -func TestFileEntryDetailOutsideBatch(t *testing.T) { +// TestFileBatchHeaderDuplicate tests validating when two batch headers exists in a current batch +func TestFileBatchHeaderDuplicate(t *testing.T) { + testFileBatchHeaderDuplicate(t) +} + +// BenchmarkFileBatchHeaderDuplicate benchmarks validating when two batch headers exists in a current batch +func BenchmarkFileBatchHeaderDuplicate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileBatchHeaderDuplicate(b) + } +} + +// testFileEntryDetailOutsideBatch validates entry detail does not exist outside of batch +func testFileEntryDetailOutsideBatch(t testing.TB) { ed := mockEntryDetail() r := NewReader(strings.NewReader(ed.String())) _, err := r.Read() - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*FileError); ok { - if e.Msg != msgFileBatchOutside { - t.Errorf("%T: %s", e, e) - } - } - } else { + if !base.Has(err, ErrFileEntryOutsideBatch) { t.Errorf("%T: %s", err, err) } } -// TestFileEntryDetail validation error populates through the reader -func TestFileEntryDetail(t *testing.T) { +// TestFileEntryDetailOutsideBatch tests validating entry detail does not exist outside of batch +func TestFileEntryDetailOutsideBatch(t *testing.T) { + testFileEntryDetailOutsideBatch(t) +} + +// BenchmarkFileEntryDetailOutsideBatch benchmarks validating entry detail does not exist outside of batch +func BenchmarkFileEntryDetailOutsideBatch(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileEntryDetailOutsideBatch(b) + } +} + +// testFileEntryDetail validates error populates through the reader +func testFileEntryDetail(t testing.TB) { ed := mockEntryDetail() ed.TransactionCode = 0 line := ed.String() r := NewReader(strings.NewReader(line)) - r.addCurrentBatch(NewBatchPPD()) + r.addCurrentBatch(NewBatchPPD(mockBatchPPDHeader())) r.currentBatch.SetHeader(mockBatchHeader()) _, err := r.Read() - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", e, e) - } - } - } else { + if !base.Has(err, NewRecordWrongLengthErr(93)) { t.Errorf("%T: %s", err, err) } } -// TestFileAddenda validation error populates through the reader -func TestFileAddenda(t *testing.T) { +// TestFileEntryDetail tests validating error populates through the reader +func TestFileEntryDetail(t *testing.T) { + testFileEntryDetail(t) +} + +// BenchmarkFileEntryDetail benchmarks validating error populates through the reader +func BenchmarkFileEntryDetail(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileEntryDetail(b) + } +} + +// testFileAddenda05 validates error for an invalid addenda05 +func testFileAddenda05(t testing.TB) { bh := mockBatchHeader() ed := mockEntryDetail() - addenda := mockAddenda() - addenda.SequenceNumber = 0 - ed.AddAddenda(addenda) - line := bh.String() + "\n" + ed.String() + "\n" + ed.Addendum[0].String() + addenda05 := mockAddenda05() + addenda05.SequenceNumber = 0 + ed.AddAddenda05(addenda05) + line := bh.String() + "\n" + ed.String() + "\n" + ed.Addenda05[0].String() r := NewReader(strings.NewReader(line)) _, err := r.Read() - if err != nil { - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", e, e) - } - } - } else { - t.Errorf("%T: %s", err, err) - } + if !base.Has(err, ErrBatchAddendaIndicator) { + t.Errorf("%T: %s", err, err) } } -// TestFileAddendaOutsideBatch validation error populates through the reader -func TestFileAddendaOutsideBatch(t *testing.T) { - addenda := mockAddenda() - r := NewReader(strings.NewReader(addenda.String())) - _, err := r.Read() - if err != nil { - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*FileError); ok { - if e.Msg != msgFileBatchOutside { - t.Errorf("%T: %s", e, e) - } - } - } else { - t.Errorf("%T: %s", err, err) - } +// TestFileAddenda05 tests validating error for an invalid addenda05 +func TestFileAddenda05(t *testing.T) { + testFileAddenda05(t) +} + +// BenchmarkFileAddenda05 benchmarks validating error for an invalid addenda05 +func BenchmarkFileAddenda05(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileAddenda05(b) } } -// TestFileAddendaNoIndicator -func TestFileAddendaNoIndicator(t *testing.T) { - bh := mockBatchHeader() - ed := mockEntryDetail() - addenda := mockAddenda() - line := bh.String() + "\n" + ed.String() + "\n" + addenda.String() +// testFileAddenda02invalid validates error for an invalid addenda02 +func testFileAddenda02invalid(t testing.TB) { + bh := mockBatchPOSHeader() + ed := mockPOSEntryDetail() + addenda02 := mockAddenda02() + addenda02.TransactionDate = "0000" + ed.Addenda02 = addenda02 + line := bh.String() + "\n" + ed.String() + "\n" + ed.Addenda02.String() r := NewReader(strings.NewReader(line)) _, err := r.Read() - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*FileError); ok { - if e.FieldName != "AddendaRecordIndicator" { - t.Errorf("%T: %s", e, e) - } - } - } else { + if !base.Has(err, ErrBatchAddendaIndicator) { t.Errorf("%T: %s", err, err) } } -func TestFileFileControlErr(t *testing.T) { - fc := mockFileControl() - fc.BatchCount = 0 - r := NewReader(strings.NewReader(fc.String())) - _, err := r.Read() - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", e, e) - } - } - } else { - t.Errorf("%T: %s", err, err) +// TestFileAddenda02invalid tests validating error for an invalid addenda02 +func TestFileAddenda02invalid(t *testing.T) { + testFileAddenda02invalid(t) +} + +// BenchmarkFileAddenda02invalid benchmarks validating error for an invalid addenda02 +func BenchmarkFileAddenda02invalid(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileAddenda02invalid(b) } } -func TestFileBatchHeaderSEC(t *testing.T) { + +// testFileAddenda02 validates a valid addenda02 +func testFileAddenda02(t testing.TB) { + bh := mockBatchPOSHeader() + ed := mockPOSEntryDetail() + addenda02 := mockAddenda02() + ed.Addenda02 = addenda02 + line := bh.String() + "\n" + ed.String() + "\n" + ed.Addenda02.String() + r := NewReader(strings.NewReader(line)) + _, err := r.Read() + if !base.Has(err, ErrBatchAddendaIndicator) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileAddenda02invalid tests validating a valid addenda02 +func TestFileAddenda02(t *testing.T) { + testFileAddenda02(t) +} + +// BenchmarkFileAddenda02 benchmarks validating a valid addenda02 +func BenchmarkFileAddenda02(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileAddenda02(b) + } +} + +// testFileAddenda98 validates error for an invalid addenda98 +func testFileAddenda98invalid(t testing.TB) { + bh := mockBatchPPDHeader() + ed := mockPPDEntryDetail() + addenda98 := mockAddenda98() + addenda98.TraceNumber = "0000001" + addenda98.ChangeCode = "C50" + addenda98.CorrectedData = "ACME One Corporation" + ed.Category = CategoryNOC + ed.Addenda98 = addenda98 + line := bh.String() + "\n" + ed.String() + "\n" + ed.Addenda98.String() + r := NewReader(strings.NewReader(line)) + _, err := r.Read() + if !base.Has(err, ErrBatchAddendaIndicator) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileAddenda98 tests validating error for an invalid addenda98 +func TestFileAddenda98invalid(t *testing.T) { + testFileAddenda98invalid(t) +} + +// BenchmarkFileAddenda98 benchmarks validating error for an invalid addenda98 +func BenchmarkFileAddenda98invalid(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileAddenda98invalid(b) + } +} + +// testFileAddenda98 validates a valid addenda98 +func testFileAddenda98(t testing.TB) { + bh := mockBatchHeader() + ed := mockEntryDetail() + addenda98 := mockAddenda98() + addenda98.TraceNumber = "0000001" + addenda98.ChangeCode = "C10" + addenda98.CorrectedData = "ACME One Corporation" + ed.Category = CategoryNOC + ed.Addenda98 = addenda98 + line := bh.String() + "\n" + ed.String() + "\n" + ed.Addenda98.String() + r := NewReader(strings.NewReader(line)) + _, err := r.Read() + if !base.Has(err, ErrBatchAddendaIndicator) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileAddenda98 tests validating a valid addenda98 +func TestFileAddenda98(t *testing.T) { + testFileAddenda98(t) +} + +// BenchmarkFileAddenda98 benchmarks validating a valid addenda98 +func BenchmarkFileAddenda98(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileAddenda98(b) + } +} + +// testFileAddenda99invalid validates error for an invalid addenda99 +func testFileAddenda99invalid(t testing.TB) { + bh := mockBatchPPDHeader() + ed := mockPPDEntryDetail() + addenda99 := mockAddenda99() + addenda99.TraceNumber = "0000001" + addenda99.ReturnCode = "100" + ed.Category = CategoryReturn + ed.Addenda99 = addenda99 + line := bh.String() + "\n" + ed.String() + "\n" + ed.Addenda99.String() + r := NewReader(strings.NewReader(line)) + _, err := r.Read() + if !base.Has(err, ErrBatchAddendaIndicator) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileAddenda99invalid tests validating error for an invalid addenda99 +func TestFileAddenda99invalid(t *testing.T) { + testFileAddenda99invalid(t) +} + +// BenchmarkFileAddenda99invalid benchmarks validating error for an invalid addenda99 +func BenchmarkFileAddenda99invalid(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileAddenda99invalid(b) + } +} + +// testFileAddenda99 validates a valid addenda99 +func testFileAddenda99(t testing.TB) { + bh := mockBatchHeader() + ed := mockEntryDetail() + addenda99 := mockAddenda99() + addenda99.TraceNumber = "0000001" + addenda99.ReturnCode = "R02" + ed.Category = CategoryReturn + ed.Addenda99 = addenda99 + line := bh.String() + "\n" + ed.String() + "\n" + ed.Addenda99.String() + r := NewReader(strings.NewReader(line)) + _, err := r.Read() + if !base.Has(err, ErrBatchAddendaIndicator) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileAddenda99 tests validating a valid addenda99 +func TestFileAddenda99(t *testing.T) { + testFileAddenda99(t) +} + +// BenchmarkFileAddenda99 benchmarks validating a valid addenda99 +func BenchmarkFileAddenda99(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileAddenda99(b) + } +} + +// testFileAddendaOutsideBatch validates error populates through the reader +func testFileAddendaOutsideBatch(t testing.TB) { + ed := mockEntryDetail() + addenda := mockAddenda05() + line := ed.String() + "\n" + addenda.String() + r := NewReader(strings.NewReader(line)) + _, err := r.Read() + + // Note that the entry doesn't get counted since it is rejected due to being outside of a batch + // So the parser considers the addenda to be outside of an entry since there are no valid entries + if !base.Has(err, ErrFileAddendaOutsideEntry) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileAddendaOutsideBatch tests validating error populates through the reader +func TestFileAddendaOutsideBatch(t *testing.T) { + testFileAddendaOutsideBatch(t) +} + +// BenchmarkFileAddendaOutsideBatch benchmarks validating error populates through the reader +func BenchmarkFileAddendaOutsideBatch(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileAddendaOutsideBatch(b) + } +} + +// testFileAddendaNoIndicator validates no addenda indicator +func testFileAddendaNoIndicator(t testing.TB) { + bh := mockBatchHeader() + ed := mockEntryDetail() + addenda := mockAddenda05() + line := bh.String() + "\n" + ed.String() + "\n" + addenda.String() + r := NewReader(strings.NewReader(line)) + _, err := r.Read() + if !base.Has(err, ErrBatchAddendaIndicator) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileAddendaNoIndicator tests validating no addenda indicator +func TestFileAddendaNoIndicator(t *testing.T) { + testFileAddendaNoIndicator(t) +} + +// BenchmarkFileAddendaNoIndicator benchmarks validating no addenda indicator +func BenchmarkFileAddendaNoIndicator(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileAddendaNoIndicator(b) + } +} + +// testFileFileControlErr validates a file control error +func testFileFileControlErr(t testing.TB) { + fc := mockFileControl() + fc.BatchCount = 0 + r := NewReader(strings.NewReader(fc.String())) + _, err := r.Read() + if !base.Has(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileFileControlErr tests validating a file control error +func TestFileFileControlErr(t *testing.T) { + testFileFileControlErr(t) +} + +// BenchmarkFileFileControlErr benchmarks validating a file control error +func BenchmarkFileFileControlErr(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileFileControlErr(b) + } +} + +// testFileBatchHeaderSEC validates batch header SEC +func testFileBatchHeaderSEC(t testing.TB) { bh := mockBatchHeader() bh.StandardEntryClassCode = "ABC" r := NewReader(strings.NewReader(bh.String())) _, err := r.Read() - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*FileError); ok { - if e.FieldName != "StandardEntryClassCode" { - t.Errorf("%T: %s", e, e) - } - } - } else { + if !base.Has(err, ErrSECCode) { t.Errorf("%T: %s", err, err) } } -func TestFileFileControlNoCurrentBatch(t *testing.T) { +// TestFileBatchHeaderSEC tests validating batch header SEC +func TestFileBatchHeaderSEC(t *testing.T) { + testFileBatchHeaderSEC(t) +} + +// BenchmarkFileBatchHeaderSEC benchmarks validating batch header SEC +func BenchmarkFileBatchHeaderSEC(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileBatchHeaderSEC(b) + } +} + +// testFileBatchControlNoCurrentBatch validates no current batch +func testFileBatchControlNoCurrentBatch(t testing.TB) { bc := mockBatchControl() r := NewReader(strings.NewReader(bc.String())) _, err := r.Read() - if p, ok := err.(*ParseError); ok { - if p.Record != "BatchControl" { - t.Errorf("%T: %s", p, p) - } - } else { + if !base.Has(err, ErrFileBatchControlOutsideBatch) { t.Errorf("%T: %s", err, err) } } -func TestFileBatchControlValidate(t *testing.T) { +// TestFileBatchControlNoCurrentBatch tests validating no current batch +func TestFileBatchControlNoCurrentBatch(t *testing.T) { + testFileBatchControlNoCurrentBatch(t) +} + +// BenchmarkFileBatchControlNoCurrentBatch benchmarks validating no current batch +func BenchmarkFileBatchControlNoCurrentBatch(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileBatchControlNoCurrentBatch(b) + } +} + +// testFileBatchControlValidate validates a batch control +func testFileBatchControlValidate(t testing.TB) { bh := mockBatchHeader() ed := mockEntryDetail() bc := mockBatchControl() @@ -358,89 +953,1028 @@ func TestFileBatchControlValidate(t *testing.T) { line := bh.String() + "\n" + ed.String() + "\n" + bc.String() r := NewReader(strings.NewReader(line)) _, err := r.Read() - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*FieldError); ok { - if e.FieldName != "CompanyIdentification" { - t.Errorf("%T: %s", e, e) - } - } - } else { + if !base.Has(err, NewErrBatchHeaderControlEquality(220, 200)) { t.Errorf("%T: %s", err, err) } } -func TestFileAddBatchValidation(t *testing.T) { +// TestFileBatchControlValidate tests validating a batch control +func TestFileBatchControlValidate(t *testing.T) { + testFileBatchControlValidate(t) +} + +// BenchmarkFileBatchControlValidate benchmarks validating a batch control +func BenchmarkFileBatchControlValidate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileBatchControlValidate(b) + } +} + +// testFileAddBatchValidation validates a batch +func testFileAddBatchValidation(t testing.TB) { bh := mockBatchHeader() ed := mockEntryDetail() bc := mockBatchControl() line := bh.String() + "\n" + ed.String() + "\n" + bc.String() r := NewReader(strings.NewReader(line)) _, err := r.Read() - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*BatchError); ok { - if e.FieldName != "EntryAddendaCount" { - t.Errorf("%T: %s", e, e) - } - } - } else { + if !base.Has(err, NewErrBatchCalculatedControlEquality(1, 0)) { t.Errorf("%T: %s", err, err) } } -/** -func TestFileHeaderExists(t *testing.T) { - file := mockFilePPD() - file.SetHeader(FileHeader{}) - buf := new(bytes.Buffer) - w := NewWriter(buf) - w.Write(file) - w.Flush() - r := NewReader(strings.NewReader(buf.String())) - f, err := r.Read() - if err != nil { - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*FileError); ok { - if e.Msg != msgFileHeader { - t.Errorf("%T: %s", e, e) - } - } - } else { - // error is nil if the file was parsed properly. - t.Errorf("%T: %s", err, err) - } - } else { - fmt.Println(f.Header.String()) +// TestFileAddBatchValidation tests validating a batch +func TestFileAddBatchValidation(t *testing.T) { + testFileAddBatchValidation(t) +} + +// BenchmarkFileAddBatchValidation benchmarks validating a batch +func BenchmarkFileAddBatchValidation(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileAddBatchValidation(b) } } -**/ -// TestFileLongErr Batch Header Service Class is 000 which does not validate -func TestFileLongErr(t *testing.T) { +// testFileLongErr Batch Header Service Class is 000 which does not validate +func testFileLongErr(t testing.TB) { line := "101 076401251 0764012510807291511A094101achdestname companyname 5000companyname origid PPDCHECKPAYMT000002080730 1076401250000001" r := NewReader(strings.NewReader(line)) _, err := r.Read() - if e, ok := err.(*ParseError); ok { - if e, ok := e.Err.(*FieldError); ok { - if e.Msg != msgFieldInclusion { - t.Errorf("%T: %s", err, err) - } - } + if !base.Has(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) } } -func TestFileAddendaOutsideEntry(t *testing.T) { +// TestFileLongErr tests Batch Header Service Class is 000 which does not validate +func TestFileLongErr(t *testing.T) { + testFileLongErr(t) +} + +// BenchmarkFileLongErr benchmarks Batch Header Service Class is 000 which does not validate +func BenchmarkFileLongErr(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileLongErr(b) + } +} + +// testFileAddendaOutsideEntry validates an addenda is within an entry detail +func testFileAddendaOutsideEntry(t testing.TB) { bh := mockBatchHeader() - addenda := mockAddenda() + addenda := mockAddenda05() line := bh.String() + "\n" + addenda.String() r := NewReader(strings.NewReader(line)) _, err := r.Read() - if p, ok := err.(*ParseError); ok { - if e, ok := p.Err.(*FileError); ok { - if e.FieldName != "Addenda" { - t.Errorf("%T: %s", e, e) - } - } + + if !base.Has(err, ErrFileAddendaOutsideEntry) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileAddendaOutsideEntry tests validating an addenda is within an entry detail +func TestFileAddendaOutsideEntry(t *testing.T) { + testFileAddendaOutsideEntry(t) +} + +// BenchmarkFileAddendaOutsideEntry benchmarks validating an addenda is within an entry detail +func BenchmarkFileAddendaOutsideEntry(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileAddendaOutsideEntry(b) + } +} + +// testFileFHImmediateOrigin validates file header immediate origin +func testFileFHImmediateOrigin(t testing.TB) { + fh := mockFileHeader() + fh.ImmediateDestination = "" + r := NewReader(strings.NewReader(fh.String())) + // necessary to have a file control not nil + r.File.Control = mockFileControl() + _, err := r.Read() + if !base.Has(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileFHImmediateOrigin tests validating file header immediate origin +func TestFileFHImmediateOrigin(t *testing.T) { + testFileFHImmediateOrigin(t) +} + +// BenchmarkFileFHImmediateOrigin benchmarks validating file header immediate origin +func BenchmarkFileFHImmediateOrigin(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileFHImmediateOrigin(b) + } +} + +// testACHFileRead validates reading a file with PPD and IAT entries +func testACHFileRead(t testing.TB) { + f, err := os.Open(filepath.Join("test", "testdata", "20110805A.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if err != nil { + t.Errorf("%T: %s", err, err) + } + + err2 := r.File.Validate() + // TODO: is this supposed to have an error here? + if !base.Match(err2, NewErrFileCalculatedControlEquality("BatchCount", 2, 5)) { + t.Errorf("%T: %s", err2, err2) + } +} + +// TestACHFileRead tests validating reading a file with PPD and IAT entries +func TestACHFileRead(t *testing.T) { + testACHFileRead(t) +} + +// BenchmarkACHFileRead benchmarks validating reading a file with PPD and IAT entries +func BenchmarkACHFileRead(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testACHFileRead(b) + } +} + +// testACHFileRead2 validates reading a file with PPD and IAT entries +func testACHFileRead2(t testing.TB) { + f, err := os.Open(filepath.Join("test", "testdata", "20110805A.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if err != nil { + t.Errorf("%T: %s", err, err) + } + + err2 := r.File.Validate() + // TODO: is this supposed to have an error here? + if !base.Match(err2, NewErrFileCalculatedControlEquality("BatchCount", 2, 5)) { + t.Errorf("%T: %s", err2, err2) + } +} + +// TestACHFileRead2 tests validating reading a file with PPD and IAT entries +func TestACHFileRead2(t *testing.T) { + testACHFileRead2(t) +} + +// BenchmarkACHFileRead2 benchmarks validating reading a file with PPD and IAT entries +func BenchmarkACHFileRead2(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testACHFileRead2(b) + } +} + +// testACHFileRead3 validates reading a file with IAT entries only +func testACHFileRead3(t testing.TB) { + f, err := os.Open(filepath.Join("test", "testdata", "20180713-IAT.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + + if file, err := NewReader(f).Read(); err != nil { + t.Error(err) } else { + if err := file.Validate(); err != nil { + t.Error(err) + } + } +} + +// TestACHFileRead3 tests validating reading a file with IAT entries that only +func TestACHFileRead3(t *testing.T) { + testACHFileRead3(t) +} + +// BenchmarkACHFileRead3 benchmarks validating reading a file with IAT entries only +func BenchmarkACHFileRead3(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testACHFileRead3(b) + } +} + +// testACHIATAddenda17 validates reading a file with IAT and Addenda17 entries +func testACHIATAddenda17(t testing.TB) { + f, err := os.Open(filepath.Join("test", "testdata", "20180716-IAT-A17.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if err != nil { + t.Errorf("%T: %s", err, err) + } + + err2 := r.File.Validate() + if err2 != nil { + t.Errorf("%T: %s", err2, err2) + } +} + +// TestACHIATAddenda17 tests validating reading a file with IAT and Addenda17 entries that +func TestACHIATAddenda17(t *testing.T) { + testACHIATAddenda17(t) +} + +// BenchmarkACHIATAddenda17 benchmarks validating reading a file with IAT and Addenda17 entries +func BenchmarkACHIATAddenda17(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testACHIATAddenda17(b) + } +} + +// testACHIATAddenda1718 validates reading a file with IAT and Addenda17 and Addenda18 entries +func testACHIATAddenda1718(t testing.TB) { + f, err := os.Open(filepath.Join("test", "testdata", "20180716-IAT-A17-A18.ach")) + if err != nil { t.Errorf("%T: %s", err, err) } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if err != nil { + t.Errorf("%T: %s", err, err) + } + + err2 := r.File.Validate() + if err2 != nil { + t.Errorf("%T: %s", err2, err2) + } +} + +// TestACHIATAddenda1718 tests validating reading a file with IAT and Addenda17 and Addenda18 entries +func TestACHIATAddenda1718(t *testing.T) { + testACHIATAddenda1718(t) +} + +// BenchmarkACHIATAddenda17 benchmarks validating reading a file with IAT Addenda17 and Addenda18 entries +func BenchmarkACHIATAddenda1718(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testACHIATAddenda1718(b) + } +} + +// testACHFileIATBatchHeader validates error when reading an invalid IATBatchHeader +func testACHFileIATBatchHeader(t testing.TB) { + f, err := os.Open(filepath.Join("test", "testdata", "iat-invalidBatchHeader.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrServiceClass) { + t.Errorf("%T: %s", err, err) + } +} + +// TestACHFileIATBatchHeader tests validating error when reading an invalid IATBatchHeader +func TestACHFileIATBatchHeader(t *testing.T) { + testACHFileIATBatchHeader(t) +} + +// BenchmarkACHFileIATBatchHeader benchmarks validating error when reading an invalid IATBatchHeader +func BenchmarkACHFileIATBatchHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testACHFileIATBatchHeader(b) + } +} + +// testACHFileIATEntryDetail validates error when reading an invalid IATEntryDetail +func testACHFileIATEntryDetail(t testing.TB) { + f, err := os.Open(filepath.Join("test", "testdata", "iat-invalidEntryDetail.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrTransactionCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestACHFileIATEntryDetail tests validating error when reading an invalid IATEntryDetail +func TestACHFileIATEntryDetail(t *testing.T) { + testACHFileIATEntryDetail(t) +} + +// BenchmarkACHFileIATEntryDetail benchmarks validating error when reading an invalid IATEntryDetail +func BenchmarkACHFileIATEntryDetail(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testACHFileIATEntryDetail(b) + } +} + +// TestIATAddendaRecordIndicator validates error when reading an invalid IATEntryDetail +func TestIATAddendaRecordIndicator(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "iat-invalidAddendaRecordIndicator.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrIATBatchAddendaIndicator) { + t.Errorf("%T: %s", err, err) + } +} + +// TestACHFileIATAddenda10 validates error when reading an invalid IATAddenda10 +func TestACHFileIATAddenda10(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "iat-invalidAddenda10.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrTransactionTypeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestACHFileIATAddenda11 validates error when reading an invalid IATAddenda10 +func TestACHFileIATAddenda11(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "iat-invalidAddenda11.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestACHFileIATAddenda12 validates error when reading an invalid IATAddenda10 +func TestACHFileIATAddenda12(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "iat-invalidAddenda12.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestACHFileIATAddenda13 validates error when reading an invalid IATAddenda13 +func TestACHFileIATAddenda13(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "Iat-invalidAddenda13.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestACHFileIATAddenda14 validates error when reading an invalid IATAddenda14 +func TestACHFileIATAddenda14(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "iat-invalidAddenda14.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestACHFileIATAddenda15 validates error when reading an invalid IATAddenda15 +func TestACHFileIATAddenda15(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "iat-invalidAddenda15.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestACHFileIATAddenda16 validates error when reading an invalid IATAddenda16 +func TestACHFileIATAddenda16(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "iat-invalidAddenda16.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestACHFileIATAddenda17 validates error when reading an invalid IATAddenda17 +func TestACHFileIATAddenda17(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "iat-invalidAddenda17.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestACHFileIATAddenda18 validates error when reading an invalid IATAddenda18 +func TestACHFileIATAddenda18(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "iat-invalidAddenda18.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestACHFileIATAddenda98 validates error when reading an invalid IATAddenda98 +func TestACHFileIATAddenda98(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "iat-invalidAddenda98.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrAddenda98ChangeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestACHFileIATAddenda99 validates error when reading an invalid IATAddenda99 +func TestACHFileIATAddenda99(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "iat-invalidAddenda99.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrAddenda99ReturnCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestPOSInvalidReturnFile validates error when reading an invalid POS Return +func TestPOSInvalidReturnFile(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "pos-invalidReturnFile.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrAddenda99ReturnCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestWEBInvalidNOCFile validates error when reading an invalid WEB NOC +func TestWEBInvalidNOCFile(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "web-invalidNOCFile.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrAddenda98ChangeCode) { + t.Errorf("%T: %s", err, err) + } +} + +// TestPOSInvalidEntryDetail validates error when reading an invalid POS EntryDetail +func TestPOSInvalidEntryDetail(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "pos-invalidEntryDetail.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrNonAlphanumeric) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVInvalidBatchEntries validates error when reading an invalid ADV file with no entries in a batch +func TestADVInvalidBatchEntries(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "adv-invalidBatchEntries.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrFileAddendaOutsideEntry) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVNoFileControl validates error when reading an invalid ADV file with no FileControl +func TestADVNoFileControl(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "adv-noFileControl.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrFileControl) { + t.Errorf("%T: %s", err, err) + } +} + +func TestADVCategoryReturn(t *testing.T) { + fd, err := os.Open(filepath.Join("test", "testdata", "adv-return.json")) + if err != nil { + t.Error(err) + } + bs, err := io.ReadAll(fd) + if err != nil { + t.Error(err) + } + file, err := FileFromJSON(bs) + if err != nil { + t.Error(err) + } + + if n := len(file.Batches); n != 1 { + t.Errorf("got %d Batches", n) + } + if file.Batches[0].Category() != CategoryReturn { + t.Errorf("Category=%s", file.Batches[0].Category()) + } +} + +// testACHFileIATBC validates error when reading an invalid IAT Batch Control +func testACHFileIATBC(t testing.TB) { + f, err := os.Open(filepath.Join("test", "testdata", "iat-invalidBatchControl.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, NewErrBatchHeaderControlEquality(23138010, 23100000)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestACHFileIATBC tests validating error when reading an invalid IAT Batch Control +func TestACHFileIATBC(t *testing.T) { + testACHFileIATBC(t) +} + +// BenchmarkACHFileIATBC benchmarks validating error when reading an invalid IAT Batch Control +func BenchmarkACHFileIATBC(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testACHFileIATBC(b) + } +} + +// testACHFileIATBH validates error when reading an invalid IAT Batch Header +func testACHFileIATBH(t testing.TB) { + f, err := os.Open(filepath.Join("test", "testdata", "iat-batchHeaderErr.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrFileBatchHeaderInsideBatch) { + t.Errorf("%T: %s", err, err) + } +} + +// TestACHFileIATBH tests validating error when reading an invalid IAT Batch Header +func TestACHFileIATBH(t *testing.T) { + testACHFileIATBH(t) +} + +// BenchmarkACHFileIATBH benchmarks validating error when reading an invalid IAT Batch Header +func BenchmarkACHFileIATBH(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testACHFileIATBH(b) + } +} + +// TestReturnACHFile test loading WEB return file +func TestReturnACHFile(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "return-WEB.ach")) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + r := NewReader(f) + data, err := r.Read() + if err != nil { + t.Fatal(err) + } + if err := data.Validate(); err != nil { + t.Fatal(err) + } +} + +// TestReturnACHFile test loading PPD return file with a custom return code +func TestReturnACHFileCustomReasonCode(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "return-PPD-custom-reason-code.ach")) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + r := NewReader(f) + r.SetValidation(&ValidateOpts{ + CustomReturnCodes: true, + }) + data, err := r.Read() + if err != nil { + t.Fatal(err) + } + if err := data.Validate(); err != nil { + t.Fatal(err) + } +} + +// TestADVReturnError returns a Parse Error +func TestADVReturnError(t *testing.T) { + file := NewFile().SetHeader(mockFileHeader()) + entry := mockADVEntryDetail() + entry.Addenda99 = mockAddenda99() + entry.Category = CategoryReturn + advHeader := mockBatchADVHeader() + batch := NewBatchADV(advHeader) + batch.SetHeader(advHeader) + batch.AddADVEntry(entry) + if err := batch.Create(); err != nil { + t.Fatal(err) + } + file.AddBatch(batch) + + if err := file.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + if err := file.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + + b := &bytes.Buffer{} + f := NewWriter(b) + + if err := f.Write(file); err != nil { + t.Errorf("%T: %s", err, err) + } + + r := NewReader(strings.NewReader(b.String())) + _, err := r.Read() + if !base.Has(err, NewErrBatchCalculatedControlEquality(1, 2)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVFileControl validates error when reading an invalid ADV File Control +func TestADVFileControl(t *testing.T) { + f, err := os.Open(filepath.Join("test", "testdata", "adv-invalidFileControl.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrConstructor) { + t.Errorf("%T: %s", err, err) + } +} + +// TestTwoFileADVControls validates one file control +func TestTwoFileADVControls(t *testing.T) { + var line = "9000001000001000000010005320001000000010500000000000000 " + var twoControls = line + "\n" + line + r := NewReader(strings.NewReader(twoControls)) + r.addCurrentBatch(NewBatchADV(mockBatchADVHeader())) + bc := ADVBatchControl{ + TotalDebitEntryDollarAmount: 10500, + EntryHash: 5320001} + r.currentBatch.SetADVControl(&bc) + + r.File.AddBatch(r.currentBatch) + r.File.ADVControl.EntryHash = 5320001 + + _, err := r.Read() + if !base.Has(err, ErrFileControl) { + t.Errorf("%T: %s", err, err) + } +} + +// testACHFileTooLongErr checks that it errors on a file that is too long +func testACHFileTooLongErr(t testing.TB) { + // To make testing this more manageable, we'll artificially cap the size of the file to 200 lines + maxLines = 200 + + f, err := os.Open(filepath.Join("test", "testdata", "20110729A.ach")) + if err != nil { + t.Errorf("%T: %s", err, err) + } + defer f.Close() + r := NewReader(f) + _, err = r.Read() + + if !base.Has(err, ErrFileTooLong) { + t.Errorf("%T: %s", err, err) + } + + // reset maxLines to its original value + maxLines = 2 + 2000000 + 100000000 + 8 +} + +// TestCategoryAssignment ensures entry categories are set correctly +func TestCategoryAssignment(t *testing.T) { + // Create a file containing entries of each category. + file := NewFile().SetHeader(mockFileHeader()) + + // "Forward" + forwardEntry := mockEntryDetail() + forwardEntry.Category = CategoryForward + forwardEntry.DiscretionaryData = "01" + forwardBatch := NewBatchWEB(mockBatchWEBHeader()) + forwardBatch.AddEntry(forwardEntry) + if err := forwardBatch.Create(); err != nil { + t.Fatal(err) + } + file.AddBatch(forwardBatch) + + // "Return" + returnEntry := mockEntryDetail() + returnEntry.Addenda99 = mockAddenda99() + returnEntry.AddendaRecordIndicator = 1 + returnEntry.Category = CategoryReturn + returnEntry.DiscretionaryData = "02" + returnBatch := NewBatchWEB(mockBatchWEBHeader()) + returnBatch.AddEntry(returnEntry) + if err := returnBatch.Create(); err != nil { + t.Fatal(err) + } + file.AddBatch(returnBatch) + + // "DishonoredReturn" + dishonoredReturnEntry := mockEntryDetail() + dishonoredReturnEntry.Addenda99Dishonored = mockAddenda99Dishonored() + dishonoredReturnEntry.AddendaRecordIndicator = 1 + dishonoredReturnEntry.Category = CategoryDishonoredReturn + dishonoredReturnEntry.DiscretionaryData = "03" + dishonoredReturnBatch := NewBatchWEB(mockBatchWEBHeader()) + dishonoredReturnBatch.AddEntry(dishonoredReturnEntry) + if err := dishonoredReturnBatch.Create(); err != nil { + t.Fatal(err) + } + file.AddBatch(dishonoredReturnBatch) + + // "DishonoredReturnContested" + contestedDishonoredReturnEntry := mockEntryDetail() + contestedDishonoredReturnEntry.Addenda99Contested = mockAddenda99Contested() + contestedDishonoredReturnEntry.AddendaRecordIndicator = 1 + contestedDishonoredReturnEntry.Category = CategoryDishonoredReturnContested + contestedDishonoredReturnEntry.DiscretionaryData = "04" + contestedDishonoredReturnBatch := NewBatchWEB(mockBatchWEBHeader()) + contestedDishonoredReturnBatch.AddEntry(contestedDishonoredReturnEntry) + if err := contestedDishonoredReturnBatch.Create(); err != nil { + t.Fatal(err) + } + file.AddBatch(contestedDishonoredReturnBatch) + + // "NOC" + nocEntry := mockCOREntryDetail() + nocEntry.Addenda98 = mockAddenda98() + nocEntry.AddendaRecordIndicator = 1 + nocEntry.Category = CategoryNOC + nocEntry.DiscretionaryData = "05" + nocBatch := NewBatchCOR(mockBatchCORHeader()) + nocBatch.AddEntry(nocEntry) + if err := nocBatch.Create(); err != nil { + t.Fatal(err) + } + file.AddBatch(nocBatch) + + if err := file.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + if err := file.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + + b := &bytes.Buffer{} + f := NewWriter(b) + + if err := f.Write(file); err != nil { + t.Errorf("%T: %s", err, err) + } + + r := NewReader(strings.NewReader(b.String())) + readFile, err := r.Read() + if err != nil { + t.Fatal(err) + } + + categoriesByDiscretionaryData := map[string]string{} + for _, batch := range readFile.Batches { + for _, entry := range batch.GetEntries() { + categoriesByDiscretionaryData[entry.DiscretionaryData] = entry.Category + } + } + + require.Equal(t, CategoryForward, categoriesByDiscretionaryData["01"]) + require.Equal(t, CategoryReturn, categoriesByDiscretionaryData["02"]) + require.Equal(t, CategoryDishonoredReturn, categoriesByDiscretionaryData["03"]) + require.Equal(t, CategoryDishonoredReturnContested, categoriesByDiscretionaryData["04"]) + require.Equal(t, CategoryNOC, categoriesByDiscretionaryData["05"]) +} + +// TestACHFileTooLongErr checks that it errors on a file that is too long +func TestACHFileTooLongErr(t *testing.T) { + testACHFileTooLongErr(t) +} + +func TestReader_AddendaParse(t *testing.T) { + var line = "702REFONEAREFTERM021000490614123456Target Store 0049 PHILADELPHIA PA12104288000" + r := NewReader(strings.NewReader(line)) + r.line = line + + if err := r.parseAddenda(); err != nil { + if !base.Match(err, ErrFileAddendaOutsideBatch) { + t.Errorf("%T: %s", err, err) + } + } +} + +func TestReader__ShortLines(t *testing.T) { + file, err := readACHFilepath(filepath.Join("test", "testdata", "short-line.ach")) + if err != nil { + t.Fatal(err) + } + if n := len(file.Batches); n != 1 { + t.Errorf("got %d batches: %#v", n, file.Batches) + } +} + +func TestReader__LongLine(t *testing.T) { + file, err := readACHFilepath(filepath.Join("test", "testdata", "long-line.ach")) + if err != nil { + t.Fatal(err) + } + if n := len(file.Batches); n != 1 { + t.Errorf("got %d batches: %#v", n, file.Batches) + } +} + +func TestReader__morphing(t *testing.T) { + out := trimSpacesFromLongLine(strings.Repeat("a", 94) + " ") + if len(out) != 94 { + t.Errorf("out=%q (%d)", out, len(out)) + } + + out = trimSpacesFromLongLine(strings.Repeat("a", 94)) + if len(out) != 94 { + t.Errorf("out=%q (%d)", out, len(out)) + } + + out, err := rightPadShortLine("") + if err != nil { + t.Error(err) + } + if len(out) != 94 { + t.Errorf("out=%q (%d)", out, len(out)) + } + + out, err = rightPadShortLine("aaaaaaaaaa") + if err != nil { + t.Error(err) + } + if len(out) != 94 { + t.Errorf("out=%q (%d)", out, len(out)) + } + + out, err = rightPadShortLine(strings.Repeat("a", 95)) + if err == nil { + t.Error("expected error") + } + if len(out) != 95 { + t.Errorf("out=%q (%d)", out, len(out)) + } +} + +func TestReader__partial(t *testing.T) { + file, err := readACHFilepath(filepath.Join("test", "testdata", "invalid-two-micro-deposits.ach")) + if err == nil { + t.Error("expected error") + } + if file == nil { + t.Fatal("nil File") + } + + // Under our current parser setup we append a lingering Batch to the resulting file + // if an EntryDetail fails to parse or BatchControl record is missing. A partial File + // is returend with any records that did parse. This test checks we don't silently break + // this behavior. + + if n := file.Control.BatchCount; n != 2 { + t.Errorf("got FileControl.BatchCount=%d", n) + } + if len(file.Batches) != 2 { + t.Fatalf("got %d Batches", len(file.Batches)) + } + entries := file.Batches[0].GetEntries() + if len(entries) != 3 { + t.Errorf("got %d entries", len(entries)) + } + entries = file.Batches[1].GetEntries() + if len(entries) != 3 { + t.Errorf("got %d entries", len(entries)) + } +} + +func TestJSONReader__IncludeFieldName(t *testing.T) { + // Write a test so that we verify the field name is included in the error message so + // it's easier to debug. Otherwise we get generic error messages that just include + // "cannot unmarshal %T into %T" + bs, err := os.ReadFile(filepath.Join("test", "testdata", "invalid-batchNumber.json")) + if err != nil { + t.Fatal(err) + } + + if _, err := FileFromJSON(bs); err == nil { + t.Fatal("expected error") + } else { + if !strings.Contains(err.Error(), `batchHeader.batchNumber: json: cannot unmarshal string into Go struct field BatchHeader.batchHeader.batchNumber of type int`) { + t.Fatalf("unexpected error:\n %v", err) + } + } } diff --git a/recordParams_test.go b/recordParams_test.go deleted file mode 100644 index ad2aac61d..000000000 --- a/recordParams_test.go +++ /dev/null @@ -1,191 +0,0 @@ -package ach - -import ( - "bytes" - "testing" -) - -func TestFileParam(t *testing.T) { - f := NewFile( - FileParam{ - ImmediateDestination: "0210000890", - ImmediateOrigin: "123456789", - ImmediateDestinationName: "Your Bank", - ImmediateOriginName: "Your Company", - ReferenceCode: "#00000A1"}) - if err := f.Header.Validate(); err != nil { - t.Errorf("%T: %s", err, err) - } - - if f.Header.ImmediateOriginName != "Your Company" { - t.Errorf("FileParam value was not copied to file.Header") - } -} - -func TestBatchParam(t *testing.T) { - companyName := "Your Company" - batch := NewBatchPPD(BatchParam{ - ServiceClassCode: "220", - CompanyName: companyName, - StandardEntryClass: "PPD", - CompanyIdentification: "123456789", - CompanyEntryDescription: "Trans. Description", - CompanyDescriptiveDate: "Oct 23", - ODFIIdentification: "123456789"}) - - if err := batch.header.Validate(); err != nil { - t.Errorf("%T: %s", err, err) - } - if batch.header.CompanyName != companyName { - t.Errorf("BatchParam value was not copied to batch.header.CompanyName") - } -} -func TestEntryParam(t *testing.T) { - entry := NewEntryDetail(EntryParam{ - ReceivingDFI: "102001017", - RDFIAccount: "5343121", - Amount: "17500", - TransactionCode: "27", - IDNumber: "ABC##jvkdjfuiwn", - IndividualName: "Bob Smith", - DiscretionaryData: "B1"}) - - if err := entry.Validate(); err != nil { - t.Errorf("%T: %s", err, err) - } -} - -func TestEntryParamPaymentType(t *testing.T) { - entry := NewEntryDetail(EntryParam{ - ReceivingDFI: "102001017", - RDFIAccount: "5343121", - Amount: "17500", - TransactionCode: "27", - IDNumber: "ABC##jvkdjfuiwn", - IndividualName: "Bob Smith", - PaymentType: "R"}) - - if err := entry.Validate(); err != nil { - t.Errorf("%T: %s", err, err) - } -} - -func TestEntryParamReceivingCompany(t *testing.T) { - entry := NewEntryDetail(EntryParam{ - ReceivingDFI: "102001017", - RDFIAccount: "5343121", - Amount: "17500", - TransactionCode: "27", - IDNumber: "location #23", - ReceivingCompany: "Best Co. #23"}) - - if err := entry.Validate(); err != nil { - t.Errorf("%T: %s", err, err) - } -} - -func TestAddendaParam(t *testing.T) { - addenda := NewAddenda(AddendaParam{ - PaymentRelatedInfo: "Currently string needs ASC X12 Interchange Control Structures", - }) - if err := addenda.Validate(); err != nil { - t.Errorf("%T: %s", err, err) - } -} - -func TestBuildFileParam(t *testing.T) { - // To create a file - file := NewFile(FileParam{ - ImmediateDestination: "0210000890", - ImmediateOrigin: "123456789", - ImmediateDestinationName: "Your Bank", - ImmediateOriginName: "Your Company", - ReferenceCode: "#00000A1"}) - - // To create a batch. Errors only if payment type is not supported. - batch, _ := NewBatch(BatchParam{ - ServiceClassCode: "225", - CompanyName: "Your Company", - StandardEntryClass: "PPD", - CompanyIdentification: "123456789", - CompanyEntryDescription: "Trans. Description", - CompanyDescriptiveDate: "Oct 23", - ODFIIdentification: "123456789"}) - - // To create an entry - entry := NewEntryDetail(EntryParam{ - ReceivingDFI: "102001017", - RDFIAccount: "5343121", - Amount: "17500", - TransactionCode: "27", - IDNumber: "ABC##jvkdjfuiwn", - IndividualName: "Robert Smith", - DiscretionaryData: "B1"}) - - // To add one or more optional addenda records for an entry - - addenda := NewAddenda(AddendaParam{ - PaymentRelatedInfo: "bonus pay for amazing work on #OSS"}) - entry.AddAddenda(addenda) - - // Entries are added to batches like so: - - batch.AddEntry(entry) - - // When all of the Entries are added to the batch we must build it. - - if err := batch.Create(); err != nil { - t.Errorf("%T: %s", err, err) - } - - // And batches are added to files much the same way: - - file.AddBatch(batch) - - // Now add a new batch for accepting payments on the web - - batch, _ = NewBatch(BatchParam{ - ServiceClassCode: "220", - CompanyName: "Your Company", - StandardEntryClass: "WEB", - CompanyIdentification: "123456789", - CompanyEntryDescription: "monthly subscription", - CompanyDescriptiveDate: "Oct 23", - ODFIIdentification: "123456789"}) - - // Add an entry and define if it is a single or reoccuring payment - // The following is a reoccuring payment for $7.99 - - entry = NewEntryDetail(EntryParam{ - ReceivingDFI: "102001017", - RDFIAccount: "5343121", - Amount: "799", - TransactionCode: "22", - IDNumber: "#123456", - IndividualName: "Wade Arnold", - PaymentType: "R"}) - - addenda = NewAddenda(AddendaParam{ - PaymentRelatedInfo: "Monthly Membership Subscription"}) - - // add the entry to the batch - entry.AddAddenda(addenda) - - // add the second batch to the file - - file.AddBatch(batch) - - // Once we added all our batches we must build the file - - if err := file.Create(); err != nil { - t.Errorf("%T: %s", err, err) - } - - // Finally we write the file to an io.Writer - var b bytes.Buffer - w := NewWriter(&b) - if err := w.WriteAll([]*File{file}); err != nil { - t.Errorf("%T: %s", err, err) - } - w.Flush() -} diff --git a/record_test.go b/record_test.go new file mode 100644 index 000000000..2c72a946a --- /dev/null +++ b/record_test.go @@ -0,0 +1,262 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "bytes" + "testing" +) + +// testFileRecord validates a file record +func testFileRecord(t testing.TB) { + f := NewFile() + f.SetHeader(mockFileHeader()) + if err := f.Header.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + + if f.Header.ImmediateOriginName != "My Bank Name" { + t.Errorf("FileParam value was not copied to file.Header") + } +} + +// TestFileRecord tests validating a file record +func TestFileRecord(t *testing.T) { + testFileRecord(t) +} + +// BenchmarkFileRecord benchmarks validating a file record +func BenchmarkFileRecord(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileRecord(b) + } +} + +// testBatchRecord validates a batch record +func testBatchRecord(t testing.TB) { + companyName := "ACME Corporation" + batch, _ := NewBatch(mockBatchPPDHeader()) + + bh := batch.GetHeader() + if err := bh.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + if bh.CompanyName != companyName { + t.Errorf("BatchParam value was not copied to batch.Header.CompanyName") + } +} + +// TestBatchRecord tests validating a batch record +func TestBatchRecord(t *testing.T) { + testBatchRecord(t) +} + +// BenchmarkBatchRecord benchmarks validating a batch record +func BenchmarkBatchRecord(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchRecord(b) + } +} + +// testEntryDetail validates an entry detail record +func testEntryDetail(t testing.TB) { + entry := mockEntryDetail() + //override mockEntryDetail + entry.TransactionCode = CheckingDebit + + if err := entry.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestEntryDetail tests validating an entry detail record +func TestEntryDetail(t *testing.T) { + testEntryDetail(t) +} + +// BenchmarkEntryDetail benchmarks validating an entry detail record +func BenchmarkEntryDetail(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEntryDetail(b) + } +} + +// testEntryDetailPaymentType validates an entry detail record payment type +func testEntryDetailPaymentType(t testing.TB) { + entry := mockEntryDetail() + //override mockEntryDetail + entry.TransactionCode = CheckingDebit + entry.DiscretionaryData = "R" + if err := entry.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestEntryDetailPaymentType tests validating an entry detail record payment type +func TestEntryDetailPaymentType(t *testing.T) { + testEntryDetailPaymentType(t) +} + +// BenchmarkEntryDetailPaymentType benchmarks validating an entry detail record payment type +func BenchmarkEntryDetailPaymentType(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEntryDetailPaymentType(b) + } +} + +// testEntryDetailReceivingCompany validates an entry detail record receiving company +func testEntryDetailReceivingCompany(t testing.TB) { + entry := mockEntryDetail() + //override mockEntryDetail + entry.TransactionCode = CheckingDebit + entry.IdentificationNumber = "location #23" + entry.IndividualName = "Best Co. #23" + + if err := entry.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestEntryDetailReceivingCompany tests validating an entry detail record receiving company +func TestEntryDetailReceivingCompany(t *testing.T) { + testEntryDetailReceivingCompany(t) +} + +// BenchmarkEntryDetailReceivingCompany benchmarks validating an entry detail record receiving company +func BenchmarkEntryDetailReceivingCompany(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testEntryDetailReceivingCompany(b) + } +} + +// testAddendaRecord validates an addenda record +func testAddendaRecord(t testing.TB) { + addenda05 := NewAddenda05() + addenda05.PaymentRelatedInformation = "Currently string needs ASC X12 Interchange Control Structures" + addenda05.SequenceNumber = 1 + addenda05.EntryDetailSequenceNumber = 1234567 + + if err := addenda05.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestAddendaRecord tests validating an addenda record +func TestAddendaRecord(t *testing.T) { + testAddendaRecord(t) +} + +// BenchmarkAddendaRecord benchmarks validating an addenda record +func BenchmarkAddendaRecord(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testAddendaRecord(b) + } +} + +// testBuildFile validates building a file +func testBuildFile(t testing.TB) { + // To create a file + file := NewFile() + file.SetHeader(mockFileHeader()) + + // To create a batch. Errors only if payment type is not supported. + batch, _ := NewBatch(mockBatchHeader()) + + // To create an entry + entry := mockPPDEntryDetail() + entry.AddendaRecordIndicator = 1 + + // To add one or more optional addenda records for an entry + addendaPPD := NewAddenda05() + addendaPPD.PaymentRelatedInformation = "Currently string needs ASC X12 Interchange Control Structures" + + // Add the addenda record to the detail entry + entry.AddAddenda05(addendaPPD) + + // Entries are added to batches like so: + batch.AddEntry(entry) + + // When all of the Entries are added to the batch we must build it. + + if err := batch.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + + // And batches are added to files much the same way: + + file.AddBatch(batch) + + // Now add a new batch for accepting payments on the web + + batch, _ = NewBatch(mockBatchWEBHeader()) + + // Add an entry and define if it is a single or recurring payment + // The following is a reoccuring payment for $7.99 + + entry = mockWEBEntryDetail() + entry.AddendaRecordIndicator = 1 + + addendaWEB := NewAddenda05() + addendaWEB.PaymentRelatedInformation = "Monthly Membership Subscription" + + // Add the addenda record to the detail entry + entry.AddAddenda05(addendaWEB) + + // Add the entry to the batch + batch.AddEntry(entry) + + // Now we must build this batch + if err := batch.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + + // And add the second batch to the file + file.AddBatch(batch) + + // Once we added all our batches we must build the file + if err := file.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + + // Finally we write the file to an io.Writer + var b bytes.Buffer + w := NewWriter(&b) + if err := w.Write(file); err != nil { + t.Errorf("%T: %s", err, err) + } + w.Flush() +} + +// TestBuildFile tests validating building a file +func TestBuildFile(t *testing.T) { + testBuildFile(t) +} + +// BenchmarkBuildFile benchmarks validating building a file +func BenchmarkBuildFile(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBuildFile(b) + } +} diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000..031be2c24 --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "extends": [ + "config:base" + ], + "groupName": "all" +} diff --git a/segmentFileConfiguration.go b/segmentFileConfiguration.go new file mode 100644 index 000000000..40f63af8b --- /dev/null +++ b/segmentFileConfiguration.go @@ -0,0 +1,30 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +// SegmentFileConfiguration contains configuration setting for sorting during Segment File Creation. +// +// It is currently not defined, but can/will be expanded later and File.SegmentFile enhanced to use the +// configuration settings +type SegmentFileConfiguration struct{} + +// SegmentFileConfiguration returns a new SegmentFileConfiguration with default values for non exported fields +func NewSegmentFileConfiguration() *SegmentFileConfiguration { + sfc := &SegmentFileConfiguration{} + return sfc +} diff --git a/segmentFileConfiguration_test.go b/segmentFileConfiguration_test.go new file mode 100644 index 000000000..12985b264 --- /dev/null +++ b/segmentFileConfiguration_test.go @@ -0,0 +1,34 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import "testing" + +// mockSegmentFileConfiguration creates a Segment File Configuration +func mockSegmentFileConfiguration() *SegmentFileConfiguration { + sfc := NewSegmentFileConfiguration() + return sfc +} + +// TestSegmentFileConfiguration validates Segment File Configuration +func TestSegmentFileConfiguration(t *testing.T) { + sfc := mockSegmentFileConfiguration() + if sfc == nil { + t.Error("mockSegmentFileConfiguration does not validate and will break other tests") + } +} diff --git a/server/batches.go b/server/batches.go new file mode 100644 index 000000000..6b2143dee --- /dev/null +++ b/server/batches.go @@ -0,0 +1,306 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package server + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + + "github.com/moov-io/ach" + moovhttp "github.com/moov-io/base/http" + "github.com/moov-io/base/log" + + "github.com/go-kit/kit/endpoint" + "github.com/gorilla/mux" +) + +type createBatchRequest struct { + FileID string + Batch ach.Batcher + + requestID string +} + +type createBatchResponse struct { + ID string `json:"id"` + Err error `json:"error"` +} + +func (r createBatchResponse) error() error { return r.Err } + +func createBatchEndpoint(s Service, logger log.Logger) endpoint.Endpoint { + return func(_ context.Context, request interface{}) (interface{}, error) { + req, ok := request.(createBatchRequest) + if !ok { + err := errors.New("invalid request") + return createBatchResponse{ + Err: err, + }, err + } + + id, err := s.CreateBatch(req.FileID, req.Batch) + + if logger != nil { + logger := logger.With(log.Fields{ + "batches": log.String("createBatch"), + "file": log.String(req.FileID), + "requestID": log.String(req.requestID), + }) + if err != nil { + logger.Error().LogError(err) + } else { + logger.Info().Log("creating batch") + } + } + + return createBatchResponse{ + ID: id, + Err: err, + }, nil + } +} + +func decodeCreateBatchRequest(_ context.Context, r *http.Request) (interface{}, error) { + var req createBatchRequest + req.requestID = moovhttp.GetRequestID(r) + + vars := mux.Vars(r) + id, ok := vars["fileID"] + if !ok { + return nil, ErrBadRouting + } + req.FileID = id + + body, err := io.ReadAll(r.Body) + if err != nil { + return nil, err + } + // In order to use FileFromJSON we need a populated JSON structure that can be parsed. + // We're going to copy the body into this shim to parse the Batch, otherwise we'd have + // to copy/export the logic of reading batches from their JSON representation. + fileContentsShim := `{"fileHeader": { + "immediateOriginName": "Test Sender", + "immediateDestinationName": "Test Dest", + "fileIDModifier": "1", + "fileCreationTime": "0437", + "fileCreationDate": "200217", + "immediateOrigin": "123456780", + "immediateDestination": "987654320", + "id": "" +}, "batches":[%v] }` + file, err := ach.FileFromJSON([]byte(fmt.Sprintf(fileContentsShim, string(body)))) + if err != nil { + return nil, err + } + if len(file.Batches) == 1 { + req.Batch = file.Batches[0] + } + if req.Batch == nil { + return nil, errors.New("no Batch provided") + } + if err := req.Batch.Validate(); err != nil { + return nil, err + } + return req, nil +} + +type getBatchesRequest struct { + fileID string + + requestID string +} + +type getBatchesResponse struct { + // TODO(adam): change this to JSON encode without wrapper {"batches": [..]} + // We don't wrap json objects in other responses, so why here? + Batches []ach.Batcher `json:"batches"` + Err error `json:"error"` +} + +func (r getBatchesResponse) count() int { return len(r.Batches) } + +func (r getBatchesResponse) error() error { return r.Err } + +func decodeGetBatchesRequest(_ context.Context, r *http.Request) (interface{}, error) { + var req getBatchesRequest + req.requestID = moovhttp.GetRequestID(r) + + vars := mux.Vars(r) + id, ok := vars["fileID"] + if !ok { + return nil, ErrBadRouting + } + req.fileID = id + return req, nil +} + +func getBatchesEndpoint(s Service, logger log.Logger) endpoint.Endpoint { + return func(_ context.Context, request interface{}) (interface{}, error) { + req, ok := request.(getBatchesRequest) + if !ok { + err := errors.New("invalid request") + return getBatchesResponse{ + Err: err, + }, err + } + + if logger != nil { + logger.With(log.Fields{ + "batches": log.String("getBatches"), + "file": log.String(req.fileID), + "requestID": log.String(req.requestID), + }).Log("get batches") + } + + return getBatchesResponse{ + Batches: s.GetBatches(req.fileID), + Err: nil, + }, nil + } +} + +type getBatchRequest struct { + fileID string + batchID string + + requestID string +} + +type getBatchResponse struct { + Batch ach.Batcher `json:"batch"` + Err error `json:"error"` +} + +func (r getBatchResponse) error() error { return r.Err } + +func decodeGetBatchRequest(_ context.Context, r *http.Request) (interface{}, error) { + var req getBatchRequest + req.requestID = moovhttp.GetRequestID(r) + + vars := mux.Vars(r) + fileID, ok := vars["fileID"] + if !ok { + return nil, ErrBadRouting + } + batchID, ok := vars["batchID"] + if !ok { + return nil, ErrBadRouting + } + + req.fileID = fileID + req.batchID = batchID + return req, nil +} + +func getBatchEndpoint(s Service, logger log.Logger) endpoint.Endpoint { + return func(_ context.Context, request interface{}) (interface{}, error) { + req, ok := request.(getBatchRequest) + if !ok { + err := errors.New("invalid request") + return getBatchResponse{ + Err: err, + }, err + } + + batch, err := s.GetBatch(req.fileID, req.batchID) + + if logger != nil { + logger := logger.With(log.Fields{ + "batches": log.String("getBatch"), + "file": log.String(req.fileID), + "requestID": log.String(req.requestID), + }) + if err != nil { + logger.Error().LogError(err) + } else { + logger.Info().Log("get batch") + } + } + + return getBatchResponse{ + Batch: batch, + Err: err, + }, nil + } +} + +type deleteBatchRequest struct { + fileID string + batchID string + + requestID string +} + +type deleteBatchResponse struct { + Err error `json:"error"` +} + +func (r deleteBatchResponse) error() error { return r.Err } + +func decodeDeleteBatchRequest(_ context.Context, r *http.Request) (interface{}, error) { + var req deleteBatchRequest + req.requestID = moovhttp.GetRequestID(r) + + vars := mux.Vars(r) + fileID, ok := vars["fileID"] + if !ok { + return nil, ErrBadRouting + } + batchID, ok := vars["batchID"] + if !ok { + return nil, ErrBadRouting + } + + req.fileID = fileID + req.batchID = batchID + return req, nil +} + +func deleteBatchEndpoint(s Service, logger log.Logger) endpoint.Endpoint { + return func(_ context.Context, request interface{}) (interface{}, error) { + req, ok := request.(deleteBatchRequest) + if !ok { + err := errors.New("invalid request") + return deleteBatchResponse{ + Err: err, + }, err + } + + err := s.DeleteBatch(req.fileID, req.batchID) + + if logger != nil { + logger := logger.With(log.Fields{ + "batches": log.String("deleteBatch"), + "file": log.String(req.fileID), + "requestID": log.String(req.requestID), + }) + if err != nil { + logger.Error().LogError(err) + } else { + logger.Info().Log("delete batch") + } + } + + return deleteBatchResponse{ + Err: err, + }, nil + } +} diff --git a/server/batches_test.go b/server/batches_test.go new file mode 100644 index 000000000..39d61a16c --- /dev/null +++ b/server/batches_test.go @@ -0,0 +1,315 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package server + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/moov-io/ach" + "github.com/moov-io/base/log" + + kitlog "github.com/go-kit/log" +) + +func TestFiles__decodeCreateBatchRequest(t *testing.T) { + f := ach.NewFile() + f.ID = "foo" + + // Setup our persistence + repo := NewRepositoryInMemory(testTTLDuration, log.NewNopLogger()) + svc := NewService(repo) + if err := repo.StoreFile(f); err != nil { + t.Fatal(err) + } + + var body bytes.Buffer + if err := json.NewEncoder(&body).Encode(mockBatchWEB()); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest("POST", "/files/foo/batches", &body) + req.Header.Set("x-request-id", "test") + + // setup our HTTP handler + handler := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // execute our HTTP request + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("bogus HTTP status code: %d: %s", w.Code, w.Body.String()) + } + + // bad JSON body + body.Reset() + if _, err := body.WriteString(`{"batchHeader": "expected-an-object"}`); err != nil { + t.Fatal(err) + } + req = httptest.NewRequest("POST", "/files/foo/batches", &body) + req.Header.Set("x-request-id", "test") + + w = httptest.NewRecorder() + handler.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusInternalServerError { + t.Errorf("bogus HTTP status code: %d: %s", w.Code, w.Body.String()) + } +} + +func TestFiles__createBatchEndpoint(t *testing.T) { + repo := NewRepositoryInMemory(testTTLDuration, log.NewNopLogger()) + svc := NewService(repo) + + body := strings.NewReader(`{"random":"json"}`) + + resp, err := createBatchEndpoint(svc, log.NewNopLogger())(context.TODO(), body) + r, ok := resp.(createBatchResponse) + if !ok { + t.Errorf("got %#v", resp) + } + if err == nil || r.Err == nil { + t.Errorf("expected error: err=%v resp.Err=%v", err, r.Err) + } + + f := ach.NewFile() + f.ID = "create-batch" + if err := repo.StoreFile(f); err != nil { + t.Fatal(err) + } + + // successful batch + resp, err = createBatchEndpoint(svc, log.NewNopLogger())(context.TODO(), createBatchRequest{ + FileID: f.ID, + Batch: &mockBatchWEB().Batch, + }) + if r, ok := resp.(createBatchResponse); ok { + if r.ID != "54321" || err != nil { + t.Errorf("id=%s error=%v", r.ID, r.Err) + } + } else { + t.Errorf("%T %#v", resp, resp) + } +} + +func TestFiles__decodeGetBatchesRequest(t *testing.T) { + f := ach.NewFile() + f.ID = "foo" + + // Setup our persistence + repo := NewRepositoryInMemory(testTTLDuration, log.NewNopLogger()) + svc := NewService(repo) + if err := repo.StoreFile(f); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest("GET", "/files/foo/batches", nil) + req.Header.Set("x-request-id", "test") + + // setup our HTTP handler + handler := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // execute our HTTP request + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("bogus HTTP status code: %d: %s", w.Code, w.Body.String()) + } +} + +func TestFiles__getBatchesEndpoint(t *testing.T) { + repo := NewRepositoryInMemory(testTTLDuration, log.NewNopLogger()) + svc := NewService(repo) + + body := strings.NewReader(`{"random":"json"}`) + + resp, err := getBatchesEndpoint(svc, log.NewNopLogger())(context.TODO(), body) + r, ok := resp.(getBatchesResponse) + if !ok { + t.Errorf("got %#v", resp) + } + if err == nil || r.Err == nil { + t.Errorf("expected error: err=%v resp.Err=%v", err, r.Err) + } + + // successful batch + f := ach.NewFile() + f.ID = "get-batches" + f.AddBatch(mockBatchWEB()) + if err := repo.StoreFile(f); err != nil { + t.Fatal(err) + } + resp, err = getBatchesEndpoint(svc, log.NewNopLogger())(context.TODO(), getBatchesRequest{ + fileID: f.ID, + }) + if r, ok := resp.(getBatchesResponse); ok { + if len(r.Batches) != 1 { + t.Errorf("got %d Batches=%#v", len(r.Batches), r.Batches) + } + if err != nil { + t.Error(r.Err) + } + } else { + t.Errorf("%T %#v", resp, resp) + } +} + +func TestFiles__decodeGetBatchRequest(t *testing.T) { + f := ach.NewFile() + f.ID = "foo" + b := mockBatchWEB() + b.SetID("foo2") + f.AddBatch(b) + + // Setup our persistence + repo := NewRepositoryInMemory(testTTLDuration, log.NewNopLogger()) + svc := NewService(repo) + if err := repo.StoreFile(f); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest("GET", "/files/foo/batches/foo2", nil) + req.Header.Set("x-request-id", "test") + + // setup our HTTP handler + handler := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // execute our HTTP request + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("bogus HTTP status code: %d: %s", w.Code, w.Body.String()) + } +} + +func TestFiles__getBatchEndpoint(t *testing.T) { + repo := NewRepositoryInMemory(testTTLDuration, log.NewNopLogger()) + svc := NewService(repo) + + body := strings.NewReader(`{"random":"json"}`) + + resp, err := getBatchEndpoint(svc, log.NewNopLogger())(context.TODO(), body) + r, ok := resp.(getBatchResponse) + if !ok { + t.Errorf("got %#v", resp) + } + if err == nil || r.Err == nil { + t.Errorf("expected error: err=%v resp.Err=%v", err, r.Err) + } + + // successful batch + f := ach.NewFile() + f.ID = "get-batch" + b := mockBatchWEB() + f.AddBatch(b) + if err := repo.StoreFile(f); err != nil { + t.Fatal(err) + } + resp, err = getBatchEndpoint(svc, log.NewNopLogger())(context.TODO(), getBatchRequest{ + fileID: f.ID, + batchID: b.ID(), + }) + if r, ok := resp.(getBatchResponse); ok { + if r.Batch == nil { + t.Error("nil ach.Batcher") + } + if err != nil { + t.Error(r.Err) + } + } else { + t.Errorf("%T %#v", resp, resp) + } +} + +func TestFiles__decodeDeleteBatchRequest(t *testing.T) { + f := ach.NewFile() + f.ID = "foo" + b := mockBatchWEB() + b.SetID("foo2") + f.AddBatch(b) + + // Setup our persistence + repo := NewRepositoryInMemory(testTTLDuration, log.NewNopLogger()) + svc := NewService(repo) + if err := repo.StoreFile(f); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest("DELETE", "/files/foo/batches/foo2", nil) + req.Header.Set("x-request-id", "test") + + // setup our HTTP handler + handler := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // execute our HTTP request + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("bogus HTTP status code: %d: %s", w.Code, w.Body.String()) + } +} + +func TestFiles__deleteBatchEndpoint(t *testing.T) { + repo := NewRepositoryInMemory(testTTLDuration, log.NewNopLogger()) + svc := NewService(repo) + + body := strings.NewReader(`{"random":"json"}`) + + resp, err := deleteBatchEndpoint(svc, log.NewNopLogger())(context.TODO(), body) + r, ok := resp.(deleteBatchResponse) + if !ok { + t.Errorf("got %#v", resp) + } + if err == nil || r.Err == nil { + t.Errorf("expected error: err=%v resp.Err=%v", err, r.Err) + } + + // successful batch + f := ach.NewFile() + f.ID = "delete-batch" + b := mockBatchWEB() + f.AddBatch(b) + if err := repo.StoreFile(f); err != nil { + t.Fatal(err) + } + resp, err = deleteBatchEndpoint(svc, log.NewNopLogger())(context.TODO(), deleteBatchRequest{ + fileID: f.ID, + batchID: b.ID(), + }) + if r, ok := resp.(deleteBatchResponse); ok { + if err != nil { + t.Error(r.Err) + } + } else { + t.Errorf("%T %#v", resp, resp) + } +} diff --git a/server/benchmark_test.go b/server/benchmark_test.go new file mode 100644 index 000000000..5b3868d8c --- /dev/null +++ b/server/benchmark_test.go @@ -0,0 +1,172 @@ +package server + +import ( + "bytes" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/moov-io/ach" + "github.com/moov-io/base/log" + + kitlog "github.com/go-kit/log" +) + +const ( + numTransactions = 75000 + numBatches = 1000 +) + +// Benchmark creating a large ACH file through the Go library +func BenchmarkCreateBigFile__Library(b *testing.B) { + for i := 0; i < b.N; i++ { + createBigFile(b, numTransactions, numBatches) + } +} + +// Benchmark creating a large ACH file through the HTTP server +func BenchmarkCreateBigFile__Server(b *testing.B) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(24*time.Hour, logger) + service := NewService(repo) + // setup our HTTP handler + handler := MakeHTTPHandler(service, repo, kitlog.NewNopLogger()) + // Spin up a local HTTP server + server := httptest.NewServer(handler) + defer server.Close() + + // Create a file with 75K entries and convert it to a NACHA file + bb := &bytes.Buffer{} + writer := ach.NewWriter(bb) + f := createBigFile(b, numTransactions, numBatches) + if err := writer.Write(f); err != nil { + b.Fatal(err) + } + + // Make the request + for i := 0; i < b.N; i++ { + req, err := http.NewRequest("POST", server.URL+"/files/create", bytes.NewReader(bb.Bytes())) + if err != nil { + b.Fatal(err) + } + req.Header.Set("Content-Type", "text/plain") + resp, err := server.Client().Do(req) + if err != nil { + b.Fatal(err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + b.Fatalf("non-200 response code: %v", resp.StatusCode) + } + } +} + +// Benchmark for flattening an ACH file +func Benchmark__FlattenBigFile(b *testing.B) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(24*time.Hour, logger) + service := NewService(repo) + // setup our HTTP handler + handler := MakeHTTPHandler(service, repo, kitlog.NewNopLogger()) + // Spin up a local HTTP server + server := httptest.NewServer(handler) + defer server.Close() + + // Create a file with 75K entries and convert it to a NACHA file + bb := &bytes.Buffer{} + writer := ach.NewWriter(bb) + f := createBigFile(b, numTransactions, numBatches) + if err := writer.Write(f); err != nil { + b.Fatal(err) + } + + if err := repo.StoreFile(f); err != nil { + b.Fatal(err) + } + + // Make the request + url := fmt.Sprintf("%s/files/%s/flatten", server.URL, f.ID) + for i := 0; i < b.N; i++ { + req, err := http.NewRequest("POST", url, nil) + if err != nil { + b.Fatal(err) + } + req.Header.Set("Content-Type", "text/plain") + resp, err := server.Client().Do(req) + if err != nil { + b.Fatal(err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + body, err := io.ReadAll(resp.Body) + if err != nil { + b.Fatal(err) + } + + b.Log(string(body)) + b.Fatalf("non-200 response code: %v", resp.StatusCode) + + } + } +} + +func createBigFile(b *testing.B, numTransactions int, numBatches int) *ach.File { + fmt.Printf("Benchmarks for ACH file with %d transactions split up in %d batches\n", numTransactions, numBatches) + + f := ach.NewFile() + f.ID = "foo" + f.Header = *mockFileHeader() + + if numTransactions < numBatches { + b.Fatal("numTransactions must be greater than numBatches") + } + + if (numTransactions % numBatches) != 0 { + b.Fatal("number of transactions must be evenly distributed across batches") + } + + entriesPerBatch := numTransactions / numBatches + + traceNumSeq := 0 + // Create and add the batch + for i := 0; i < numBatches; i++ { + // Create entries and add to current batch + batch := ach.NewBatchWEB(mockBatchHeaderWeb()) + + for j := 0; j < entriesPerBatch; j++ { + entry := ach.NewEntryDetail() + entry.ID = "98765" + entry.TransactionCode = ach.CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "123456789" + entry.Amount = 1 + entry.IndividualName = "Wade Arnold" + + traceNumSeq += 1 + entry.SetTraceNumber(batch.Header.ODFIIdentification, traceNumSeq) + //entry.TraceNumber = fmt.Sprintf("%d", i+j) + entry.SetPaymentType("S") + + batch.AddEntry(entry) + } + + if err := batch.Create(); err != nil { + b.Fatal(err) + } + f.AddBatch(batch) + } + + if err := f.Create(); err != nil { + b.Fatal(err) + } + if err := f.Validate(); err != nil { + b.Fatal(err) + } + + return f +} diff --git a/server/files.go b/server/files.go new file mode 100644 index 000000000..2d639e0bf --- /dev/null +++ b/server/files.go @@ -0,0 +1,840 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package server + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" + "strings" + + "github.com/moov-io/ach" + "github.com/moov-io/base" + moovhttp "github.com/moov-io/base/http" + "github.com/moov-io/base/log" + + "github.com/go-kit/kit/endpoint" + "github.com/go-kit/kit/metrics/prometheus" + "github.com/gorilla/mux" + stdprometheus "github.com/prometheus/client_golang/prometheus" +) + +var ( + filesCreated = prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Name: "ach_files_created", + Help: "The number of ACH files created", + }, []string{"destination", "origin"}) + + filesDeleted = prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Name: "ach_files_deleted", + Help: "The number of ACH files deleted", + }, nil) +) + +type createFileRequest struct { + File *ach.File + parseError error + requestID string + validateOpts *ach.ValidateOpts +} + +type createFileResponse struct { + ID string `json:"id"` + File *ach.File `json:"file"` + + Err error `json:"error"` +} + +func (r createFileResponse) error() error { return r.Err } + +func createFileEndpoint(s Service, r Repository, logger log.Logger) endpoint.Endpoint { + return func(_ context.Context, request interface{}) (interface{}, error) { + req, ok := request.(createFileRequest) + if !ok { + return createFileResponse{Err: ErrFoundABug}, ErrFoundABug + } + + // record a metric for files created + if req.File != nil && req.File.Header.ImmediateDestination != "" && req.File.Header.ImmediateOrigin != "" { + filesCreated.With("destination", req.File.Header.ImmediateDestination, "origin", req.File.Header.ImmediateOrigin).Add(1) + } + + // Create a random file ID if none was provided + if req.File.ID == "" { + req.File.ID = base.ID() + } + + if req.validateOpts != nil { + req.File.SetValidation(req.validateOpts) + } + + err := r.StoreFile(req.File) + if logger != nil { + logger := logger.With(log.Fields{ + "files": log.String("createFile"), + "requestID": log.String(req.requestID), + }) + if err != nil { + logger.Error().LogError(err) + } else { + logger.Info().Log("create file") + } + } + + resp := createFileResponse{ + ID: req.File.ID, + File: req.File, + Err: err, + } + if req.parseError != nil { + resp.Err = req.parseError + } + + return resp, nil + } +} + +func decodeCreateFileRequest(_ context.Context, request *http.Request) (interface{}, error) { + var r io.Reader + req := createFileRequest{ + File: ach.NewFile(), + requestID: moovhttp.GetRequestID(request), + validateOpts: &ach.ValidateOpts{}, + } + + const ( + requireABAOrigin = "requireABAOrigin" + bypassOrigin = "bypassOrigin" + bypassDestination = "bypassDestination" + customTraceNumbers = "customTraceNumbers" + allowZeroBatches = "allowZeroBatches" + allowMissingFileHeader = "allowMissingFileHeader" + allowMissingFileControl = "allowMissingFileControl" + bypassCompanyIdentificationMatch = "bypassCompanyIdentificationMatch" + customReturnCodes = "customReturnCodes" + unequalServiceClassCode = "unequalServiceClassCode" + unorderedBatchNumbers = "unorderedBatchNumbers" + ) + + validationNames := []string{ + requireABAOrigin, + bypassOrigin, + bypassDestination, + customTraceNumbers, + allowZeroBatches, + allowMissingFileHeader, + allowMissingFileControl, + bypassCompanyIdentificationMatch, + customReturnCodes, + unequalServiceClassCode, + unorderedBatchNumbers, + } + + for _, name := range validationNames { + input := request.URL.Query().Get(name) + if input == "" { + continue + } + + ok, err := strconv.ParseBool(input) + if err != nil { + return nil, fmt.Errorf("invalid bool: %v", err) + } + if !ok { + continue + } + + switch name { + case requireABAOrigin: + req.validateOpts.RequireABAOrigin = true + case bypassOrigin: + req.validateOpts.BypassOriginValidation = true + case bypassDestination: + req.validateOpts.BypassDestinationValidation = true + case customTraceNumbers: + req.validateOpts.CustomTraceNumbers = true + case allowZeroBatches: + req.validateOpts.AllowZeroBatches = true + case allowMissingFileHeader: + req.validateOpts.AllowMissingFileHeader = true + case allowMissingFileControl: + req.validateOpts.AllowMissingFileControl = true + case bypassCompanyIdentificationMatch: + req.validateOpts.BypassCompanyIdentificationMatch = true + case customReturnCodes: + req.validateOpts.CustomReturnCodes = true + case unequalServiceClassCode: + req.validateOpts.UnequalServiceClassCode = true + case unorderedBatchNumbers: + req.validateOpts.AllowUnorderedBatchNumbers = true + } + } + + bs, err := io.ReadAll(request.Body) + if err != nil { + return nil, err + } + + h := strings.ToLower(request.Header.Get("Content-Type")) + if strings.Contains(h, "application/json") { + // Read body as ACH file in JSON + f, err := ach.FileFromJSONWith(bs, req.validateOpts) + if f != nil { + req.File = f + } + req.parseError = err + } else { + // Attempt parsing body as an ACH File + r = bytes.NewReader(bs) + achReader := ach.NewReader(r) + achReader.SetValidation(req.validateOpts) + + f, err := achReader.Read() + req.File = &f + req.parseError = err + } + + // Set the fileID from the request + fileID, ok := mux.Vars(request)["fileID"] + if ok && fileID != "" && fileID != "create" { + req.File.ID = fileID + } + + return req, nil +} + +type getFilesRequest struct { + requestID string +} + +type getFilesResponse struct { + Files []*ach.File `json:"files"` + Err error `json:"error"` +} + +func (r getFilesResponse) count() int { return len(r.Files) } + +func (r getFilesResponse) error() error { return r.Err } + +func getFilesEndpoint(s Service) endpoint.Endpoint { + return func(_ context.Context, _ interface{}) (interface{}, error) { + return getFilesResponse{ + Files: s.GetFiles(), + Err: nil, + }, nil + } +} + +func decodeGetFilesRequest(_ context.Context, r *http.Request) (interface{}, error) { + return getFilesRequest{ + requestID: moovhttp.GetRequestID(r), + }, nil +} + +type getFileRequest struct { + ID string + + requestID string +} + +type getFileResponse struct { + File *ach.File `json:"file"` + Err error `json:"error"` +} + +func (r getFileResponse) error() error { return r.Err } + +func getFileEndpoint(s Service, logger log.Logger) endpoint.Endpoint { + return func(_ context.Context, request interface{}) (interface{}, error) { + req, ok := request.(getFileRequest) + if !ok { + return getFileResponse{Err: ErrFoundABug}, ErrFoundABug + } + + f, err := s.GetFile(req.ID) + + if logger != nil { + logger := logger.With(log.Fields{ + "files": log.String("getFile"), + "requestID": log.String(req.requestID), + }) + if err != nil { + logger.Error().LogError(err) + } else { + logger.Info().Log("get file") + } + } + + return getFileResponse{ + File: f, + Err: err, + }, nil + } +} + +func decodeGetFileRequest(_ context.Context, r *http.Request) (interface{}, error) { + vars := mux.Vars(r) + id, ok := vars["id"] + if !ok { + return nil, ErrBadRouting + } + return getFileRequest{ + ID: id, + requestID: moovhttp.GetRequestID(r), + }, nil +} + +type deleteFileRequest struct { + ID string + + requestID string +} + +type deleteFileResponse struct { + Err error `json:"err"` +} + +func (r deleteFileResponse) error() error { return r.Err } + +func deleteFileEndpoint(s Service, logger log.Logger) endpoint.Endpoint { + return func(_ context.Context, request interface{}) (interface{}, error) { + req, ok := request.(deleteFileRequest) + if !ok { + return deleteFileResponse{Err: ErrFoundABug}, ErrFoundABug + } + + filesDeleted.Add(1) + + err := s.DeleteFile(req.ID) + + if logger != nil { + logger := logger.With(log.Fields{ + "files": log.String("deleteFile"), + "requestID": log.String(req.requestID), + }) + if err != nil { + logger.Error().LogError(err) + } else { + logger.Info().Log("delete file") + } + } + + return deleteFileResponse{ + Err: err, + }, nil + } +} + +func decodeDeleteFileRequest(_ context.Context, r *http.Request) (interface{}, error) { + vars := mux.Vars(r) + id, ok := vars["id"] + if !ok { + return nil, ErrBadRouting + } + return deleteFileRequest{ + ID: id, + requestID: moovhttp.GetRequestID(r), + }, nil +} + +type buildFileRequest struct { + ID string + + requestID string +} + +type buildFileResponse struct { + File *ach.File `json:"file"` + Err error `json:"error"` +} + +func (v buildFileResponse) error() error { return v.Err } + +func buildFileEndpoint(s Service, r Repository, logger log.Logger) endpoint.Endpoint { + return func(_ context.Context, request interface{}) (interface{}, error) { + req, ok := request.(buildFileRequest) + if !ok { + return buildFileResponse{Err: ErrFoundABug}, ErrFoundABug + } + + file, err := s.BuildFile(req.ID) + + logger := logger.With(log.Fields{ + "files": log.String("buildFile"), + "fileID": log.String(req.ID), + "requestID": log.String(req.requestID), + }) + if err != nil { + logger.Error().LogError(err) + } else { + logger.Info().Log("build file") + } + + return buildFileResponse{ + File: file, + Err: err, + }, nil + } +} + +func decodeBuildFileRequest(_ context.Context, r *http.Request) (interface{}, error) { + vars := mux.Vars(r) + id, ok := vars["id"] + if !ok { + return nil, ErrBadRouting + } + return buildFileRequest{ + ID: id, + requestID: moovhttp.GetRequestID(r), + }, nil +} + +type getFileContentsRequest struct { + ID string + + requestID string +} + +type getFileContentsResponse struct { + Err error `json:"error"` +} + +func (v getFileContentsResponse) error() error { return v.Err } + +func getFileContentsEndpoint(s Service, logger log.Logger) endpoint.Endpoint { + return func(_ context.Context, request interface{}) (interface{}, error) { + req, ok := request.(getFileContentsRequest) + if !ok { + return getFileContentsResponse{Err: ErrFoundABug}, ErrFoundABug + } + + r, err := s.GetFileContents(req.ID) + + if logger != nil { + logger := logger.With(log.Fields{ + "files": log.String("getFileContents"), + "requestID": log.String(req.requestID), + }) + if err != nil { + logger.Error().LogError(err) + } else { + logger.Info().Log("get file contents") + } + } + if err != nil { + return getFileContentsResponse{Err: err}, nil + } + + return r, nil + } +} + +func decodeGetFileContentsRequest(_ context.Context, r *http.Request) (interface{}, error) { + vars := mux.Vars(r) + id, ok := vars["id"] + if !ok { + return nil, ErrBadRouting + } + return getFileContentsRequest{ + ID: id, + requestID: moovhttp.GetRequestID(r), + }, nil +} + +type validateFileRequest struct { + ID string + requestID string + + opts *ach.ValidateOpts +} + +type validateFileResponse struct { + Err error `json:"error"` +} + +func (v validateFileResponse) error() error { return v.Err } + +func validateFileEndpoint(s Service, logger log.Logger) endpoint.Endpoint { + return func(_ context.Context, request interface{}) (interface{}, error) { + req, ok := request.(validateFileRequest) + if !ok { + return validateFileResponse{Err: ErrFoundABug}, ErrFoundABug + } + + err := s.ValidateFile(req.ID, req.opts) + if logger != nil { + logger := logger.With(log.Fields{ + "files": log.String("validateFile"), + "requestID": log.String(req.requestID), + }) + if err != nil { + logger.Error().LogError(err) + } else { + logger.Info().Log("validate file") + } + } + if err != nil { // wrap err with context + err = fmt.Errorf("%v: %v", errInvalidFile, err) + } + return validateFileResponse{err}, nil + } +} + +func decodeValidateFileRequest(_ context.Context, r *http.Request) (interface{}, error) { + vars := mux.Vars(r) + id, ok := vars["id"] + if !ok { + return nil, ErrBadRouting + } + + req := validateFileRequest{ + ID: id, + requestID: moovhttp.GetRequestID(r), + } + + var opts ach.ValidateOpts + if err := json.NewDecoder(r.Body).Decode(&opts); err == nil { + req.opts = &opts + } + + return req, nil +} + +type balanceFileRequest struct { + fileID string + offset *ach.Offset + requestID string +} + +type balanceFileResponse struct { + FileID string `json:"id"` + Err error `json:"error"` +} + +func balanceFileEndpoint(s Service, r Repository, logger log.Logger) endpoint.Endpoint { + return func(_ context.Context, request interface{}) (interface{}, error) { + req, ok := request.(balanceFileRequest) + if !ok { + return balanceFileResponse{Err: ErrFoundABug}, ErrFoundABug + } + balancedFile, err := s.BalanceFile(req.fileID, req.offset) + if balancedFile != nil && logger != nil { + logger := logger.With(log.Fields{ + "files": log.String(fmt.Sprintf("balance file created %s", balancedFile.ID)), + "requestID": log.String(req.requestID), + }) + if err != nil { + logger.Error().LogError(err) + } else { + logger.Info().Log("balance file") + } + } + if err != nil { + if logger != nil { + logger := logger.With(log.Fields{ + "files": log.String(fmt.Sprintf("problem balancing %s: %v", req.fileID, err)), + "requestID": log.String(req.requestID), + }) + if err != nil { + logger.Error().LogError(err) + } else { + logger.Info().Log("balance file") + } + + } + return balanceFileResponse{Err: err}, err + } + return balanceFileResponse{ + FileID: balancedFile.ID, + }, nil + } +} + +func decodeBalanceFileRequest(_ context.Context, r *http.Request) (interface{}, error) { + vars := mux.Vars(r) + fileID, ok := vars["fileID"] + if !ok { + return nil, ErrBadRouting + } + + var off ach.Offset + if err := json.NewDecoder(r.Body).Decode(&off); err != nil { + return nil, err + } + if off.RoutingNumber == "" || off.AccountNumber == "" || string(off.AccountType) == "" { + return nil, errors.New("missing some offset json fields") + } + return balanceFileRequest{ + fileID: fileID, + offset: &off, + requestID: moovhttp.GetRequestID(r), + }, nil +} + +type segmentFileIDRequest struct { + fileID string + requestID string + + opts *ach.SegmentFileConfiguration +} + +type segmentedFilesResponse struct { + CreditFileID string `json:"creditFileID"` + CreditFile *ach.File `json:"creditFile"` + + DebitFileID string `json:"debitFileID"` + DebitFile *ach.File `json:"debitFile"` + + Err error `json:"error"` +} + +func segmentFileIDEndpoint(s Service, r Repository, logger log.Logger) endpoint.Endpoint { + return func(_ context.Context, request interface{}) (interface{}, error) { + req, ok := request.(segmentFileIDRequest) + if !ok { + return segmentedFilesResponse{Err: ErrFoundABug}, ErrFoundABug + } + + creditFile, debitFile, err := s.SegmentFileID(req.fileID, req.opts) + + if logger != nil { + logger.With(log.Fields{ + "files": log.String("segmentFileID"), + "requestID": log.String(req.requestID), + }) + if err != nil { + logger.Error().LogError(err) + } else { + logger.Info().Log("segment fileID") + } + } + if err != nil { + return segmentedFilesResponse{Err: err}, err + } + + var resp segmentedFilesResponse + + if creditFile.ID != "" { + err = r.StoreFile(creditFile) + if logger != nil && err != nil { + logger.With(log.Fields{ + "files": log.String("storeCreditFile"), + "requestID": log.String(req.requestID), + }).LogError(err) + } + resp.CreditFile = creditFile + resp.CreditFileID = creditFile.ID + } + + if debitFile.ID != "" { + err = r.StoreFile(debitFile) + if logger != nil && err != nil { + logger.With(log.Fields{ + "files": log.String("storeDebitFile"), + "requestID": log.String(req.requestID), + }).LogError(err) + } + resp.DebitFile = debitFile + resp.DebitFileID = debitFile.ID + } + + resp.Err = err + + return resp, nil + } +} + +func decodeSegmentFileIDRequest(_ context.Context, r *http.Request) (interface{}, error) { + vars := mux.Vars(r) + fileID, ok := vars["fileID"] + if !ok { + return nil, ErrBadRouting + } + + req := segmentFileIDRequest{ + fileID: fileID, + requestID: moovhttp.GetRequestID(r), + } + + var opts ach.SegmentFileConfiguration + if err := json.NewDecoder(r.Body).Decode(&opts); err == nil { + req.opts = &opts + } + + return req, nil +} + +type segmentFileRequest struct { + File *ach.File + requestID string + + opts *ach.SegmentFileConfiguration +} + +func segmentFileEndpoint(s Service, r Repository, logger log.Logger) endpoint.Endpoint { + return func(_ context.Context, request interface{}) (interface{}, error) { + req, ok := request.(segmentFileRequest) + if !ok { + return segmentedFilesResponse{Err: ErrFoundABug}, ErrFoundABug + } + + creditFile, debitFile, err := s.SegmentFile(req.File, req.opts) + if logger != nil { + logger.With(log.Fields{ + "files": log.String("segmentFile"), + "requestID": log.String(req.requestID), + }) + if err != nil { + logger.Error().LogError(err) + } else { + logger.Info().Log("segment file") + } + } + if err != nil { + return segmentedFilesResponse{Err: err}, err + } + + var resp segmentedFilesResponse + + if creditFile.ID != "" { + err = r.StoreFile(creditFile) + if logger != nil && err != nil { + logger.With(log.Fields{ + "files": log.String("storeCreditFile"), + "requestID": log.String(req.requestID), + }).LogError(err) + } + resp.CreditFile = creditFile + resp.CreditFileID = creditFile.ID + } + + if debitFile.ID != "" { + err = r.StoreFile(debitFile) + if logger != nil && err != nil { + logger.With(log.Fields{ + "files": log.String("storeDebitFile"), + "requestID": log.String(req.requestID), + }).LogError(err) + } + resp.DebitFile = debitFile + resp.DebitFileID = debitFile.ID + } + + resp.Err = err + + return resp, nil + } +} + +func decodeSegmentFileRequest(_ context.Context, r *http.Request) (interface{}, error) { + var wrapper struct { + File *ach.File `json:"file"` + Opts *ach.SegmentFileConfiguration `json:"opts"` + } + + header := strings.ToLower(r.Header.Get("content-type")) + if strings.Contains(header, "application/json") { + err := json.NewDecoder(r.Body).Decode(&wrapper) + if err != nil { + return segmentedFilesResponse{Err: err}, err + } + } else { + file, err := ach.NewReader(r.Body).Read() + if err != nil { + return segmentedFilesResponse{Err: err}, err + } + wrapper.File = &file + } + + return segmentFileRequest{ + File: wrapper.File, + requestID: moovhttp.GetRequestID(r), + opts: wrapper.Opts, + }, nil +} + +type flattenBatchesRequest struct { + fileID string + requestID string +} + +type flattenBatchesResponse struct { + ID string `json:"id"` + File *ach.File `json:"file"` + Err error `json:"error"` +} + +func flattenBatchesEndpoint(s Service, r Repository, logger log.Logger) endpoint.Endpoint { + return func(_ context.Context, request interface{}) (interface{}, error) { + req, ok := request.(flattenBatchesRequest) + if !ok { + return flattenBatchesResponse{Err: ErrFoundABug}, ErrFoundABug + } + flattenFile, err := s.FlattenBatches(req.fileID) + if logger != nil { + logger := logger.With(log.Fields{ + "files": log.String("FlattenBatches"), + "requestID": log.String(req.requestID), + }) + if err != nil { + logger.Error().LogError(err) + } else { + logger.Info().Log("flatten batches") + } + } + if err != nil { + return flattenBatchesResponse{Err: err}, err + } + if flattenFile.ID != "" { + err = r.StoreFile(flattenFile) + if logger != nil { + logger := logger.With(log.Fields{ + "files": log.String("storeFlattenFile"), + "requestID": log.String(req.requestID), + }) + if err != nil { + logger.Error().LogError(err) + } else { + logger.Info().Log("flatten batches") + } + } + } + return flattenBatchesResponse{ + ID: flattenFile.ID, + File: flattenFile, + Err: err, + }, nil + } +} + +func decodeFlattenBatchesRequest(_ context.Context, r *http.Request) (interface{}, error) { + vars := mux.Vars(r) + fileID, ok := vars["fileID"] + if !ok { + return nil, ErrBadRouting + } + return flattenBatchesRequest{ + fileID: fileID, + requestID: moovhttp.GetRequestID(r), + }, nil +} diff --git a/server/files_test.go b/server/files_test.go new file mode 100644 index 000000000..b9b0d1ba6 --- /dev/null +++ b/server/files_test.go @@ -0,0 +1,1235 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package server + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/moov-io/ach" + "github.com/moov-io/base" + "github.com/moov-io/base/log" + + httptransport "github.com/go-kit/kit/transport/http" + kitlog "github.com/go-kit/log" + "github.com/gorilla/mux" + "github.com/stretchr/testify/require" +) + +func TestFiles__decodeCreateFileRequest(t *testing.T) { + f := ach.NewFile() + f.ID = "foo" + f.Header = *mockFileHeader() + batch := mockBatchWEB() + f.AddBatch(batch) + + // Setup our persistence + repo := NewRepositoryInMemory(testTTLDuration, log.NewNopLogger()) + svc := NewService(repo) + + var body bytes.Buffer + err := json.NewEncoder(&body).Encode(f) + require.NoError(t, err) + + req := httptest.NewRequest("POST", "/files/create", &body) + req.Header.Set("x-request-id", "test") + req.Header.Set("content-type", "application/json") + + // setup our HTTP handler + handler := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // execute our HTTP request + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("bogus HTTP status code: %d: %s", w.Code, w.Body.String()) + } + + var resp createFileResponse + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatal(err) + } + if resp.ID == "" || resp.Err != nil { + t.Errorf("id=%q error=%v", resp.ID, resp.Err) + } + require.Equal(t, "foo", f.ID) + require.NotNil(t, resp.File) + require.Equal(t, "foo", resp.File.ID) + + // Check stored file state + got, _ := svc.GetFile(f.ID) + if got.Batches[0].ID() != batch.ID() { + t.Fatalf("batch ID: got %v, want %v", got.Batches[0].ID(), batch.ID()) + } +} + +func TestFiles__createFileEndpoint(t *testing.T) { + repo := NewRepositoryInMemory(testTTLDuration, nil) + svc := NewService(repo) + + body := strings.NewReader(`{"random":"json"}`) + + resp, err := createFileEndpoint(svc, repo, nil)(context.TODO(), body) + r, ok := resp.(createFileResponse) + if !ok { + t.Errorf("got %#v", resp) + } + if err == nil || r.Err == nil { + t.Errorf("expected error: err=%v resp.Err=%v", err, r.Err) + } +} + +func TestFiles__CreateFileNacha(t *testing.T) { + repo := NewRepositoryInMemory(testTTLDuration, log.NewNopLogger()) + svc := NewService(repo) + + fd, err := os.Open(filepath.Join("..", "test", "testdata", "ppd-debit.ach")) + require.NoError(t, err) + defer fd.Close() + + fileID := base.ID() + req := httptest.NewRequest("POST", fmt.Sprintf("/files/%s", fileID), fd) + req.Header.Set("content-type", "text/html") + + handler := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + + require.Equal(t, http.StatusOK, w.Code) + + var resp createFileResponse + err = json.NewDecoder(w.Body).Decode(&resp) + require.NoError(t, err) + require.Equal(t, fileID, resp.ID) + require.NotNil(t, resp.File) + require.Equal(t, fileID, resp.File.ID) + + got, _ := svc.GetFile(fileID) + require.Len(t, got.Batches, 1) + + entries := got.Batches[0].GetEntries() + require.Len(t, entries, 1) + require.Equal(t, "121042880000001", entries[0].TraceNumber) +} + +func TestFiles__CustomJsonValidation(t *testing.T) { + repo := NewRepositoryInMemory(testTTLDuration, nil) + svc := NewService(repo) + + body, err := os.Open(filepath.Join("..", "test", "testdata", "json-bypass-origin-and-destination.json")) + require.NoError(t, err) + + req := httptest.NewRequest("POST", "/files/create?bypassDestination=true&bypassOrigin=true", body) + req.Header.Set("content-type", "application/json") + + // setup our HTTP handler + handler := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // execute our HTTP request + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + w.Flush() + + require.Equal(t, http.StatusOK, w.Code) + + var resp createFileResponse + err = json.NewDecoder(w.Body).Decode(&resp) + require.ErrorContains(t, err, "ImmediateDestination 000000000 is a mandatory field") + require.Equal(t, "adam-01", resp.ID) + require.NotNil(t, resp.File) + require.Equal(t, nil, resp.Err) +} + +func TestFiles__decodeCreateFileRequest__validateOpts(t *testing.T) { + f := ach.NewFile() + f.ID = "foo" + f.Header = *mockFileHeader() + f.AddBatch(mockBatchWEB()) + + var body bytes.Buffer + if err := json.NewEncoder(&body).Encode(f); err != nil { + t.Fatal(err) + } + + tests := []struct { + query string + expect ach.ValidateOpts + }{ + { + query: "?bypassCompanyIdentificationMatch=true", + expect: ach.ValidateOpts{ + BypassCompanyIdentificationMatch: true, + }, + }, + { + query: "?bypassOrigin=true&requireABAOrigin=true", + expect: ach.ValidateOpts{ + RequireABAOrigin: true, + BypassOriginValidation: true, + }, + }, + { + query: "?bypassDestination=1&requireABAOrigin=0", + expect: ach.ValidateOpts{ + BypassDestinationValidation: true, + }, + }, + { + query: "?requireABAOrigin=TRUE&bypassOrigin=true&bypassDestination=1", + expect: ach.ValidateOpts{ + RequireABAOrigin: true, + BypassOriginValidation: true, + BypassDestinationValidation: true, + }, + }, + { + query: "?requireABAOrigin=false&bypassOrigin=true&bypassDestination=true", + expect: ach.ValidateOpts{ + RequireABAOrigin: false, + BypassOriginValidation: true, + BypassDestinationValidation: true, + }, + }, + { + query: "?customTraceNumbers=true", + expect: ach.ValidateOpts{ + CustomTraceNumbers: true, + }, + }, + { + query: "?allowMissingFileHeader=true&allowMissingFileControl=true", + expect: ach.ValidateOpts{ + AllowMissingFileHeader: true, + AllowMissingFileControl: true, + }, + }, + { + query: "?unorderedBatchNumbers=true", + expect: ach.ValidateOpts{ + AllowUnorderedBatchNumbers: true, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.query, func(t *testing.T) { + httpRequest := httptest.NewRequest("POST", "/files/create"+tc.query, &body) + httpRequest.Header.Set("x-request-id", "test") + + decodedReq, err := decodeCreateFileRequest(context.TODO(), httpRequest) + if err != nil { + t.Fatal(err) + } + req := decodedReq.(createFileRequest) + if !reflect.DeepEqual(tc.expect, *req.validateOpts) { + t.Fatalf("validateOpts: want %v, got %v", tc.expect, *req.validateOpts) + } + }) + } +} + +func TestFiles__getFilesEndpoint(t *testing.T) { + repo := NewRepositoryInMemory(testTTLDuration, nil) + svc := NewService(repo) + + f := ach.NewFile() + f.ID = "foo" + f.Header = *mockFileHeader() + f.AddBatch(mockBatchWEB()) + if err := repo.StoreFile(f); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest("GET", "/files", nil) + req.Header.Set("x-request-id", "test") + req.Header.Set("content-type", "application/json") + + // setup our HTTP handler + handler := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // execute our HTTP request + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("bogus HTTP status code: %d: %s", w.Code, w.Body.String()) + } + + // sad path + body := strings.NewReader(`{"random":"json"}`) + resp, err := getFilesEndpoint(svc)(context.TODO(), body) + _, ok := resp.(getFilesResponse) + if !ok || err != nil { + t.Errorf("got %#v : err=%v", resp, err) + } +} + +func TestFiles__getFileEndpoint(t *testing.T) { + repo := NewRepositoryInMemory(testTTLDuration, nil) + svc := NewService(repo) + + f := ach.NewFile() + f.ID = "foo" + f.Header = *mockFileHeader() + f.AddBatch(mockBatchWEB()) + if err := repo.StoreFile(f); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest("GET", "/files/foo", nil) + req.Header.Set("x-request-id", "test") + req.Header.Set("content-type", "application/json") + + // setup our HTTP handler + handler := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // execute our HTTP request + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("bogus HTTP status code: %d: %s", w.Code, w.Body.String()) + } + + // sad path + body := strings.NewReader(`{"random":"json"}`) + resp, err := getFileEndpoint(svc, nil)(context.TODO(), body) + r, ok := resp.(getFileResponse) + if !ok { + t.Errorf("got %#v", resp) + } + if err == nil || r.Err == nil { + t.Errorf("expected error: err=%v resp.Err=%v", err, r.Err) + } +} + +func TestFiles__buildFileEndpoint(t *testing.T) { + repo := NewRepositoryInMemory(testTTLDuration, nil) + svc := NewService(repo) + + f := ach.NewFile() + f.ID = "foo" + f.Header = *mockFileHeader() + f.AddBatch(mockBatchWEB()) + if err := repo.StoreFile(f); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest("GET", "/files/foo/build", nil) + req.Header.Set("x-request-id", "test") + req.Header.Set("content-type", "application/json") + + // setup our HTTP handler + handler := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // execute our HTTP request + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + w.Flush() + + require.Equal(t, http.StatusOK, w.Code) + + var response struct { + File ach.File `json:"file"` + Error error `json:"error"` + } + err := json.NewDecoder(w.Body).Decode(&response) + require.NoError(t, err) + + require.Len(t, response.File.Batches, 1) + + entries := response.File.Batches[0].GetEntries() + require.Len(t, entries, 1) + require.Equal(t, "121042880000001", entries[0].TraceNumber) +} + +func TestFiles__getFileContentsEndpoint(t *testing.T) { + repo := NewRepositoryInMemory(testTTLDuration, nil) + svc := NewService(repo) + + f := ach.NewFile() + f.ID = "foo" + f.Header = *mockFileHeader() + f.AddBatch(mockBatchWEB()) + if err := repo.StoreFile(f); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest("GET", "/files/foo/contents", nil) + req.Header.Set("x-request-id", "test") + + // setup our HTTP handler + handler := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // execute our HTTP request + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("bogus HTTP status code: %d: %s", w.Code, w.Body.String()) + } + if v := w.Header().Get("content-type"); v != "text/plain" { + t.Errorf("content-type: %s", v) + } + + // sad path + body := strings.NewReader(`{"random":"json"}`) + resp, err := getFileContentsEndpoint(svc, nil)(context.TODO(), body) + _, ok := resp.(getFileContentsResponse) + if !ok { + t.Errorf("got %#v", resp) + } + if err == nil { + t.Errorf("expected error: err=%v", err) + } +} + +func TestFiles__validateFileEndpoint(t *testing.T) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(testTTLDuration, logger) + svc := NewService(repo) + + rawBody := `{"random":"json"}` + + resp, err := validateFileEndpoint(svc, logger)(context.TODO(), strings.NewReader(rawBody)) + r, ok := resp.(validateFileResponse) + if !ok { + t.Errorf("got %#v", resp) + } + if err == nil || r.Err == nil { + t.Errorf("expected error: err=%v resp.Err=%v", err, r.Err) + } + + // write an ACH file into repository + fd, err := os.Open(filepath.Join("..", "test", "testdata", "ppd-valid.json")) + if fd == nil { + t.Fatalf("empty ACH file: %v", err) + } + defer fd.Close() + bs, _ := io.ReadAll(fd) + file, _ := ach.FileFromJSON(bs) + file.Header.ImmediateDestination = "" // invalid routing number + repo.StoreFile(file) + + // test status code + w := httptest.NewRecorder() + req := httptest.NewRequest("GET", fmt.Sprintf("/files/%s/validate", file.ID), strings.NewReader(rawBody)) + + router := mux.NewRouter() + router.Methods("GET").Path("/files/{id}/validate").Handler( + httptransport.NewServer(validateFileEndpoint(svc, logger), decodeValidateFileRequest, encodeResponse), + ) + + req.Header.Set("Origin", "https://moov.io") + req.Header.Set("X-Request-Id", "55555") + router.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusBadRequest { + t.Errorf("bogus HTTP status: %d", w.Code) + } + if !strings.HasPrefix(w.Body.String(), `{"error":"invalid ACH file: ImmediateDestination`) { + t.Errorf("unknown error: %v\n%v", err, w.Body.String()) + } +} + +func TestFiles__ValidateOpts(t *testing.T) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(testTTLDuration, logger) + svc := NewService(repo) + + // Write file into storage + fd, err := os.Open(filepath.Join("..", "test", "testdata", "ppd-valid.json")) + if fd == nil { + t.Fatalf("empty ACH file: %v", err) + } + defer fd.Close() + + bs, _ := io.ReadAll(fd) + file, _ := ach.FileFromJSON(bs) + file.Header.ImmediateOrigin = "123456789" // invalid routing number + repo.StoreFile(file) + + // validate, expect failure + w := httptest.NewRecorder() + body := strings.NewReader(`{"requireABAOrigin": true}`) + req := httptest.NewRequest("POST", fmt.Sprintf("/files/%s/validate", file.ID), body) + + router := mux.NewRouter() + router.Methods("POST").Path("/files/{id}/validate").Handler( + httptransport.NewServer(validateFileEndpoint(svc, logger), decodeValidateFileRequest, encodeResponse), + ) + + req.Header.Set("X-Request-Id", "55555") + router.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusBadRequest { + t.Errorf("bogus HTTP status: %d", w.Code) + } + + // correct file + file.Header.ImmediateOrigin = "987654320" // routing number + repo.StoreFile(file) + + // retry, but with different ValidateOpts + w = httptest.NewRecorder() + body = strings.NewReader(`{"requireABAOrigin": true}`) + req = httptest.NewRequest("POST", fmt.Sprintf("/files/%s/validate", file.ID), body) + router.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("bogus HTTP status: %d: %s", w.Code, w.Body.String()) + } +} + +func TestFilesErr__balanceFileEndpoint(t *testing.T) { + repo := NewRepositoryInMemory(testTTLDuration, nil) + svc := NewService(repo) + + resp, err := balanceFileEndpoint(svc, repo, log.NewNopLogger())(context.TODO(), nil) + r, ok := resp.(balanceFileResponse) + if !ok { + t.Errorf("got %#v", resp) + } + if err == nil || r.Err == nil { + t.Errorf("expected error: err=%v resp.Err=%v", err, r.Err) + } +} + +func TestFiles__balanceFileEndpoint(t *testing.T) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(testTTLDuration, logger) + svc := NewService(repo) + router := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // write an ACH file into the repository + fd, err := os.Open(filepath.Join("..", "test", "testdata", "ppd-mixedDebitCredit-valid.json")) + if err != nil { + t.Fatalf("empty ACH file: %v", err) + } + defer fd.Close() + + bs, _ := io.ReadAll(fd) + file, _ := ach.FileFromJSON(bs) + repo.StoreFile(file) + + w := httptest.NewRecorder() + body := strings.NewReader(`{"routingNumber": "987654320", "accountNumber": "216112", "accountType": "checking", "description": "OFFSET"}`) + req := httptest.NewRequest("POST", fmt.Sprintf("/files/%s/balance", file.ID), body) + req.Header.Set("X-Request-ID", base.ID()) + + router.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("bogus HTTP status: %d", w.Code) + } + + var resp balanceFileResponse + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatal(err) + } + if resp.FileID == "" { + t.Error("empty FileID") + } + + // check for ErrBadRouting + if _, err := decodeBalanceFileRequest(context.TODO(), &http.Request{}); err != ErrBadRouting { + t.Errorf("unexpected error: %v", err) + } +} + +func TestFilesErr__balanceInvalidFile(t *testing.T) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(testTTLDuration, logger) + svc := NewService(repo) + router := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // write an invalid (partial) file + fh := ach.NewFileHeader() + fileID, err := svc.CreateFile(&fh) + if err != nil { + t.Fatal(err) + } + + w := httptest.NewRecorder() + body := strings.NewReader(`{"routingNumber": "987654320", "accountNumber": "216112", "accountType": "checking", "description": "OFFSET"}`) + req := httptest.NewRequest("POST", fmt.Sprintf("/files/%s/balance", fileID), body) + req.Header.Set("X-Request-ID", base.ID()) + + router.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusBadRequest { + t.Errorf("bogus HTTP status: %d", w.Code) + } + + var resp struct { + Err string `json:"error"` + } + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatal(err) + } + if resp.Err == "" { + t.Errorf("resp.Err=%q", resp.Err) + } +} + +func TestFilesErr__balanceFileEndpointJSON(t *testing.T) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(testTTLDuration, logger) + svc := NewService(repo) + router := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // write an ACH file into the repository + fd, err := os.Open(filepath.Join("..", "test", "testdata", "ppd-mixedDebitCredit-valid.json")) + if err != nil { + t.Fatalf("empty ACH file: %v", err) + } + defer fd.Close() + + bs, _ := io.ReadAll(fd) + file, _ := ach.FileFromJSON(bs) + repo.StoreFile(file) + + w := httptest.NewRecorder() + + body := strings.NewReader(`{"routingNumber": "987654320"}`) // partial JSON, but we left off fields + req := httptest.NewRequest("POST", fmt.Sprintf("/files/%s/balance", file.ID), body) + req.Header.Set("X-Request-ID", base.ID()) + + router.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusInternalServerError { + t.Errorf("bogus HTTP status: %d", w.Code) + } + + var resp struct { + Err string `json:"error"` + } + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatal(err) + } + if resp.Err == "" { + t.Errorf("resp.Err=%q", resp.Err) + } + + // send totally invalid JSON + w = httptest.NewRecorder() + body = strings.NewReader(`invalid-json asdlsk`) + req = httptest.NewRequest("POST", fmt.Sprintf("/files/%s/balance", file.ID), body) + req.Header.Set("X-Request-ID", base.ID()) + + router.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusInternalServerError { + t.Errorf("bogus HTTP status: %d", w.Code) + } +} + +// TestFilesError__segmentFileIDEndpoint test an error returned from segmentFileIDEndpoint +func TestFilesError__segmentFileIDEndpoint(t *testing.T) { + repo := NewRepositoryInMemory(testTTLDuration, nil) + svc := NewService(repo) + + resp, err := segmentFileIDEndpoint(svc, repo, nil)(context.TODO(), nil) + r, ok := resp.(segmentedFilesResponse) + if !ok { + t.Errorf("got %#v", resp) + } + if err == nil || r.Err == nil { + t.Errorf("expected error: err=%v resp.Err=%v", err, r.Err) + } +} + +// TestFiles__segmentFileIDEndpoint tests segmentFileIDEndpoints +func TestFiles__segmentFileIDEndpoint(t *testing.T) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(testTTLDuration, logger) + svc := NewService(repo) + router := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // write an ACH file into repository + fd, err := os.Open(filepath.Join("..", "test", "testdata", "ppd-mixedDebitCredit-valid.json")) + if fd == nil { + t.Fatalf("empty ACH file: %v", err) + } + defer fd.Close() + bs, _ := io.ReadAll(fd) + file, _ := ach.FileFromJSON(bs) + repo.StoreFile(file) + + body := strings.NewReader(`{}`) + + // test status code + w := httptest.NewRecorder() + req := httptest.NewRequest("POST", fmt.Sprintf("/files/%s/segment", file.ID), body) + req.Header.Set("Origin", "https://moov.io") + req.Header.Set("X-Request-Id", "11111") + + router.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("bogus HTTP status: %d", w.Code) + } + + var resp segmentedFilesResponse + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatal(err) + } + require.NotEmpty(t, resp.CreditFileID) + require.NotNil(t, resp.CreditFile) + require.NotEmpty(t, resp.DebitFileID) + require.NotNil(t, resp.DebitFile) +} + +func TestFiles__segmentFileEndpoint(t *testing.T) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(testTTLDuration, logger) + svc := NewService(repo) + router := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + fd, err := os.Open(filepath.Join("..", "test", "testdata", "ppd-mixedDebitCredit.ach")) + require.NoError(t, err) + + w := httptest.NewRecorder() + req := httptest.NewRequest("POST", "/segment", fd) + req.Header.Set("Origin", "https://moov.io") + req.Header.Set("X-Request-Id", "222222") + + router.ServeHTTP(w, req) + w.Flush() + + require.Equal(t, http.StatusOK, w.Code) + + var resp segmentedFilesResponse + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatal(err) + } + require.NotEmpty(t, resp.CreditFileID) + require.NotNil(t, resp.CreditFile) + require.NotEmpty(t, resp.DebitFileID) + require.NotNil(t, resp.DebitFile) +} + +func TestFiles__segmentFileEndpointJSON(t *testing.T) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(testTTLDuration, logger) + svc := NewService(repo) + router := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + file, err := ach.ReadFile(filepath.Join("..", "test", "testdata", "ppd-mixedDebitCredit.ach")) + require.NoError(t, err) + + var buf bytes.Buffer + err = json.NewEncoder(&buf).Encode(struct { + File *ach.File `json:"file"` + }{ + File: file, + }) + require.NoError(t, err) + + w := httptest.NewRecorder() + req := httptest.NewRequest("POST", "/segment", &buf) + req.Header.Set("Origin", "https://moov.io") + req.Header.Set("X-Request-Id", "222222") + req.Header.Set("Content-Type", "application/json") + + router.ServeHTTP(w, req) + w.Flush() + + require.Equal(t, http.StatusOK, w.Code) + + var resp segmentedFilesResponse + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatal(err) + } + require.NotEmpty(t, resp.CreditFileID) + require.NotNil(t, resp.CreditFile) + require.NotEmpty(t, resp.DebitFileID) + require.NotNil(t, resp.DebitFile) +} + +// TestFiles__decodeSegmentFileIDRequest tests segmentFileIDEndpoints +func TestFiles__decodeSegmentFileIDRequest(t *testing.T) { + req := httptest.NewRequest("POST", "/files/segment", nil) + req.Header.Set("Origin", "https://moov.io") + req.Header.Set("X-Request-Id", "11111") + + _, err := decodeSegmentFileIDRequest(context.TODO(), req) + + if !base.Match(err, ErrBadRouting) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFiles__flattenFileEndpoint tests flattenFileEndpoints +func TestFiles__flattenFileEndpoint(t *testing.T) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(testTTLDuration, logger) + svc := NewService(repo) + router := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // write an ACH file into repository + fd, err := os.Open(filepath.Join("..", "test", "testdata", "ppd-mixedDebitCredit-valid.json")) + if fd == nil { + t.Fatalf("empty ACH file: %v", err) + } + defer fd.Close() + bs, _ := io.ReadAll(fd) + file, _ := ach.FileFromJSON(bs) + repo.StoreFile(file) + + // test status code + w := httptest.NewRecorder() + req := httptest.NewRequest("POST", fmt.Sprintf("/files/%s/flatten", file.ID), nil) + req.Header.Set("Origin", "https://moov.io") + req.Header.Set("X-Request-Id", "11111") + + router.ServeHTTP(w, req) + w.Flush() + + require.Equal(t, http.StatusOK, w.Code) + + var resp flattenBatchesResponse + err = json.NewDecoder(w.Body).Decode(&resp) + require.NoError(t, err) + require.NotEqual(t, file.ID, resp.ID) + require.NotNil(t, resp.File) + require.NotEqual(t, file.ID, resp.File.ID) +} + +// TestFilesError__flattenFileEndpoint test an error returned from flattenFileEndpoint +func TestFilesError__flattenFileEndpoint(t *testing.T) { + repo := NewRepositoryInMemory(testTTLDuration, nil) + svc := NewService(repo) + + resp, err := flattenBatchesEndpoint(svc, repo, nil)(context.TODO(), nil) + r, ok := resp.(flattenBatchesResponse) + if !ok { + t.Errorf("got %#v", resp) + } + if err == nil || r.Err == nil { + t.Errorf("expected error: err=%v resp.Err=%v", err, r.Err) + } + +} + +// TestFiles__decodeFlattenFileRequest tests segmentFileIDEndpoints +func TestFiles__decodeFlattenFileRequest(t *testing.T) { + req := httptest.NewRequest("POST", "/files/flatten", nil) + req.Header.Set("Origin", "https://moov.io") + req.Header.Set("X-Request-Id", "11111") + + _, err := decodeFlattenBatchesRequest(context.TODO(), req) + + if !base.Match(err, ErrBadRouting) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFilesByID__getFileEndpoint tests getFileEndpoint by File ID +func TestFilesByID__getFileEndpoint(t *testing.T) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(testTTLDuration, logger) + svc := NewService(repo) + router := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // write an ACH file into repository + fd, err := os.Open(filepath.Join("..", "test", "testdata", "ppd-mixedDebitCredit-valid.json")) + if fd == nil { + t.Fatalf("empty ACH file: %v", err) + } + defer fd.Close() + bs, _ := io.ReadAll(fd) + file, _ := ach.FileFromJSON(bs) + repo.StoreFile(file) + + // test status code + w := httptest.NewRecorder() + req := httptest.NewRequest("GET", fmt.Sprintf("/files/%s", file.ID), nil) + req.Header.Set("Origin", "https://moov.io") + req.Header.Set("X-Request-Id", "11112") + + router.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("bogus HTTP status: %d", w.Code) + } + +} + +// TestFileContentsByID__getFileContentsEndpoint tests getFileContentsEndpoint by File ID +func TestFileContentsByID__getFileContentsEndpoint(t *testing.T) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(testTTLDuration, logger) + svc := NewService(repo) + router := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // write an ACH file into repository + fd, err := os.Open(filepath.Join("..", "test", "testdata", "ppd-mixedDebitCredit-valid.json")) + if fd == nil { + t.Fatalf("empty ACH file: %v", err) + } + defer fd.Close() + bs, _ := io.ReadAll(fd) + file, _ := ach.FileFromJSON(bs) + repo.StoreFile(file) + + // test status code + w := httptest.NewRecorder() + req := httptest.NewRequest("GET", fmt.Sprintf("/files/%s/contents", file.ID), nil) + req.Header.Set("Origin", "https://moov.io") + req.Header.Set("X-Request-Id", "11112") + + router.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("bogus HTTP status: %d", w.Code) + } + +} + +// TestFilesByID__deleteFileEndpoint tests by File ID +func TestFilesByID__deleteFileEndpoint(t *testing.T) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(testTTLDuration, logger) + svc := NewService(repo) + router := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // write an ACH file into repository + fd, err := os.Open(filepath.Join("..", "test", "testdata", "ppd-mixedDebitCredit-valid.json")) + if fd == nil { + t.Fatalf("empty ACH file: %v", err) + } + defer fd.Close() + bs, _ := io.ReadAll(fd) + file, _ := ach.FileFromJSON(bs) + repo.StoreFile(file) + + // test status code + w := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", fmt.Sprintf("/files/%s", file.ID), nil) + req.Header.Set("Origin", "https://moov.io") + req.Header.Set("X-Request-Id", "11113") + + router.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("bogus HTTP status: %d", w.Code) + } + +} + +// TestFilesError__deleteFileEndpoint tests error returned for deleteFileEndpoint +func TestFilesError__deleteFileEndpoint(t *testing.T) { + repo := NewRepositoryInMemory(testTTLDuration, nil) + svc := NewService(repo) + + body := strings.NewReader(`{"random":"json"}`) + + resp, err := deleteFileEndpoint(svc, nil)(context.TODO(), body) + r, ok := resp.(deleteFileResponse) + if !ok { + t.Errorf("got %#v", resp) + } + if err == nil || r.Err == nil { + t.Errorf("expected error: err=%v resp.Err=%v", err, r.Err) + } + +} + +// TestFiles__CreateFileEndpoint test CreateFileEndpoint +func TestFiles__CreateFileEndpoint(t *testing.T) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(testTTLDuration, logger) + svc := NewService(repo) + router := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + testCases := []struct { + filename string + queryParams string + expectedStatusCode int + }{ + { + filename: "ppd-debit.ach", + queryParams: "", + expectedStatusCode: http.StatusOK, + }, + { + filename: "ppd-debit-customTraceNumber.ach", + queryParams: "?bypassOrigin=true", + expectedStatusCode: http.StatusOK, + }, + { + filename: "ppd-debit-customTraceNumber.ach", + queryParams: "?bypassOrigin=false", + expectedStatusCode: http.StatusBadRequest, + }, + } + + for _, tc := range testCases { + // write an ACH file into repository + fd, err := os.Open(filepath.Join("..", "test", "testdata", tc.filename)) + if fd == nil { + t.Fatalf("empty ACH file: %v", err) + } + defer fd.Close() + + // test status code + w := httptest.NewRecorder() + req := httptest.NewRequest("POST", fmt.Sprintf("/files/create%s", tc.queryParams), fd) + req.Header.Set("Origin", "https://moov.io") + req.Header.Set("X-Request-Id", "11114") + + router.ServeHTTP(w, req) + w.Flush() + + require.Equal(t, tc.expectedStatusCode, w.Code) + + var resp struct { + ID string `json:"id"` + } + err = json.NewDecoder(w.Body).Decode(&resp) + require.NoError(t, err) + + require.NotEmpty(t, resp.ID) + require.NotEqual(t, "create", resp.ID) + } +} + +func TestFiles__CreateFileWithZeroBatches(t *testing.T) { + repo := NewRepositoryInMemory(testTTLDuration, log.NewNopLogger()) + svc := NewService(repo) + handler := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + f := ach.NewFile() + f.ID = "foo" + f.Header = *mockFileHeader() + + var body bytes.Buffer + if err := json.NewEncoder(&body).Encode(f); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest("POST", "/files/create?allowZeroBatches=true", &body) + req.Header.Set("content-type", "application/json") + + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + w.Flush() + + // Get contents back + req = httptest.NewRequest("GET", fmt.Sprintf("/files/%s/contents", f.ID), nil) + w = httptest.NewRecorder() + handler.ServeHTTP(w, req) + w.Flush() + + got, err := ach.NewReader(w.Body).Read() + if err != nil { + t.Fatalf("reading file: %v", err) + } + if err := got.Validate(); err != nil { + t.Fatalf("validating file: %v", err) + } +} + +func TestFiles__CreateFileEndpointErr(t *testing.T) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(testTTLDuration, logger) + svc := NewService(repo) + router := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // write an ACH file into repository + fd, err := os.Open(filepath.Join("..", "test", "issues", "testdata", "issue702.ach")) + if fd == nil { + t.Fatalf("empty ACH file: %v", err) + } + defer fd.Close() + + // test status code + w := httptest.NewRecorder() + req := httptest.NewRequest("POST", "/files/create", fd) + req.Header.Set("Origin", "https://moov.io") + req.Header.Set("X-Request-Id", "11114") + + router.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusBadRequest { + t.Errorf("bogus HTTP status: %d: %s", w.Code, w.Body.String()) + } + + var resp struct { + ID string `json:"id"` + Err string `json:"error"` + } + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatal(err) + } + if resp.ID == "" || resp.Err == "" { + t.Errorf("resp.ID=%q resp.Err=%q", resp.ID, resp.Err) + } +} + +func TestFiles__CreateFileEndpointJSONErr(t *testing.T) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(testTTLDuration, logger) + svc := NewService(repo) + router := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // write an ACH file into repository + fd, err := os.Open(filepath.Join("..", "test", "testdata", "ppd-noBatches.json")) + if fd == nil { + t.Fatalf("empty ACH file: %v", err) + } + defer fd.Close() + + // test status code + w := httptest.NewRecorder() + req := httptest.NewRequest("POST", "/files/create", fd) + req.Header.Set("Origin", "https://moov.io") + req.Header.Set("X-Request-Id", "11114") + req.Header.Set("Content-Type", "application/json") + + router.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusBadRequest { + t.Errorf("bogus HTTP status: %d: %s", w.Code, w.Body.String()) + } + + var resp struct { + ID string `json:"id"` + Err string `json:"error"` + } + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatal(err) + } + if resp.ID == "" || resp.Err == "" { + t.Errorf("resp.ID=%q resp.Err=%q", resp.ID, resp.Err) + } +} + +// TestFiles_segmentFileIDEndpointError tests segmentFileIDEndpoints +func TestFiles__segmentFileIDEndpointError(t *testing.T) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(testTTLDuration, logger) + svc := NewService(repo) + router := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // write an ACH file into repository + fd, err := os.Open(filepath.Join("..", "test", "testdata", "ppd-mixedDebitCredit-valid.json")) + if fd == nil { + t.Fatalf("empty ACH file: %v", err) + } + defer fd.Close() + bs, _ := io.ReadAll(fd) + file, _ := ach.FileFromJSON(bs) + file.Header.ImmediateDestination = "" // invalid routing number + repo.StoreFile(file) + + // test status code + w := httptest.NewRecorder() + req := httptest.NewRequest("POST", fmt.Sprintf("/files/%s/segment", file.ID), nil) + req.Header.Set("Origin", "https://moov.io") + req.Header.Set("X-Request-Id", "11110") + + router.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusBadRequest { + t.Errorf("bogus HTTP status: %d", w.Code) + } + + var resp struct { + Err string `json:"error"` + } + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatal(err) + } + if resp.Err == "" { + t.Errorf("resp.Err=%q", resp.Err) + } +} + +// TestFiles_flattenFileEndpointError tests flattenFileEndpoints +func TestFiles__flattenFileEndpointError(t *testing.T) { + logger := log.NewNopLogger() + repo := NewRepositoryInMemory(testTTLDuration, logger) + svc := NewService(repo) + router := MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // write an ACH file into repository + fd, err := os.Open(filepath.Join("..", "test", "testdata", "ppd-mixedDebitCredit-valid.json")) + if fd == nil { + t.Fatalf("empty ACH file: %v", err) + } + defer fd.Close() + bs, _ := io.ReadAll(fd) + file, _ := ach.FileFromJSON(bs) + file.Header.ImmediateDestination = "" // invalid routing number + repo.StoreFile(file) + + // test status code + w := httptest.NewRecorder() + req := httptest.NewRequest("POST", fmt.Sprintf("/files/%s/flatten", file.ID), nil) + req.Header.Set("Origin", "https://moov.io") + req.Header.Set("X-Request-Id", "11110") + + router.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusBadRequest { + t.Errorf("bogus HTTP status: %d", w.Code) + } + + var resp struct { + Err string `json:"error"` + } + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatal(err) + } + if resp.Err == "" { + t.Errorf("resp.Err=%q", resp.Err) + } +} diff --git a/server/mock_test.go b/server/mock_test.go new file mode 100644 index 000000000..276982ece --- /dev/null +++ b/server/mock_test.go @@ -0,0 +1,89 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package server + +import ( + "time" + + "github.com/moov-io/ach" +) + +func mockServiceInMemory() Service { + repository := NewRepositoryInMemory(testTTLDuration, nil) + repository.StoreFile(&ach.File{ID: "98765"}) + repository.StoreBatch("98765", mockBatchWEB()) + return NewService(repository) +} + +func mockFileHeader() *ach.FileHeader { + fh := ach.NewFileHeader() + fh.ID = "12345" + fh.ImmediateDestination = "231380104" + fh.ImmediateOrigin = "121042882" + fh.FileCreationDate = time.Now().Format("060102") + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + return &fh +} + +func mockBatchHeaderWeb() *ach.BatchHeader { + bh := ach.NewBatchHeader() + bh.ID = "54321" + bh.ServiceClassCode = ach.CreditsOnly + bh.StandardEntryClassCode = ach.WEB + bh.CompanyName = "Your Company, inc" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "Online Order" + bh.ODFIIdentification = "12104288" + return bh +} + +// mockWEBEntryDetail creates a WEB entry detail +func mockWEBEntryDetail() *ach.EntryDetail { + entry := ach.NewEntryDetail() + entry.ID = "98765" + entry.TransactionCode = ach.CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "123456789" + entry.Amount = 100000000 + entry.IndividualName = "Wade Arnold" + entry.SetTraceNumber(mockBatchHeaderWeb().ODFIIdentification, 1) + entry.SetPaymentType("S") + entry.AddAddenda05(mockAddenda05()) + return entry +} + +// mockBatchWEB creates a WEB batch +func mockBatchWEB() *ach.BatchWEB { + mockBatch := ach.NewBatchWEB(mockBatchHeaderWeb()) + mockBatch.SetID(mockBatch.Header.ID) + mockBatch.AddEntry(mockWEBEntryDetail()) + mockBatch.Entries[0].AddendaRecordIndicator = 1 + if err := mockBatch.Create(); err != nil { + panic(err) + } + return mockBatch +} + +func mockAddenda05() *ach.Addenda05 { + addenda05 := ach.NewAddenda05() + addenda05.ID = "56789" + addenda05.SequenceNumber = 1 + addenda05.EntryDetailSequenceNumber = 0000001 + return addenda05 +} diff --git a/server/repository.go b/server/repository.go new file mode 100644 index 000000000..62f038c8c --- /dev/null +++ b/server/repository.go @@ -0,0 +1,215 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package server + +import ( + "errors" + "fmt" + "sync" + "time" + + "github.com/moov-io/ach" + "github.com/moov-io/base/log" +) + +// Repository is the Service storage mechanism abstraction +type Repository interface { + StoreFile(file *ach.File) error + FindFile(id string) (*ach.File, error) + FindAllFiles() []*ach.File + DeleteFile(id string) error + StoreBatch(fileID string, batch ach.Batcher) error + FindBatch(fileID string, batchID string) (ach.Batcher, error) + FindAllBatches(fileID string) []ach.Batcher + DeleteBatch(fileID string, batchID string) error +} + +type repositoryInMemory struct { + mtx sync.RWMutex + files map[string]*ach.File + + ttl time.Duration + + logger log.Logger +} + +// NewRepositoryInMemory is an in memory ach storage repository for files +func NewRepositoryInMemory(ttl time.Duration, logger log.Logger) Repository { + repo := &repositoryInMemory{ + files: make(map[string]*ach.File), + ttl: ttl, + logger: logger, + } + + if ttl <= 0*time.Second { + // Don't run the cleanup if we've disabled the TTL + return repo + } + + // Run our anon goroutine to cleanup old ACH files + go func() { + t := time.NewTicker(1 * time.Minute) + for range t.C { + repo.cleanupOldFiles() + } + }() + + return repo +} + +func (r *repositoryInMemory) StoreFile(f *ach.File) error { + if f == nil { + return errors.New("nil ACH file provided") + } + + r.mtx.Lock() + defer r.mtx.Unlock() + if _, ok := r.files[f.ID]; ok { + return ErrAlreadyExists + } + r.files[f.ID] = f + return nil +} + +// FindFile retrieves a ach.File based on the supplied ID +func (r *repositoryInMemory) FindFile(id string) (*ach.File, error) { + r.mtx.RLock() + defer r.mtx.RUnlock() + if val, ok := r.files[id]; ok { + return val, nil + } + return nil, ErrNotFound +} + +// FindAllFiles returns all files that have been saved in memory +func (r *repositoryInMemory) FindAllFiles() []*ach.File { + r.mtx.RLock() + defer r.mtx.RUnlock() + files := make([]*ach.File, 0, len(r.files)) + for i := range r.files { + files = append(files, r.files[i]) + } + return files +} + +func (r *repositoryInMemory) DeleteFile(id string) error { + r.mtx.Lock() + defer r.mtx.Unlock() + delete(r.files, id) + return nil +} + +// TODO(adam): was copying ach.Batcher causing issues? +func (r *repositoryInMemory) StoreBatch(fileID string, batch ach.Batcher) error { + r.mtx.Lock() + defer r.mtx.Unlock() + + // Ensure the file does not already exist + file, ok := r.files[fileID] + if !ok || file == nil { + return ErrNotFound + } + + // ensure the batch does not already exist + for _, val := range file.Batches { + if val.ID() == batch.ID() { + return ErrAlreadyExists + } + } + + // Add the batch to the file + r.files[fileID].AddBatch(batch) + + return nil +} + +// FindBatch retrieves a ach.Batcher based on the supplied ID +func (r *repositoryInMemory) FindBatch(fileID string, batchID string) (ach.Batcher, error) { + r.mtx.RLock() + defer r.mtx.RUnlock() + + file, ok := r.files[fileID] + if !ok || file == nil { + return nil, ErrNotFound + } + + for _, val := range file.Batches { + if val.ID() == batchID { + return val, nil + } + } + + return nil, ErrNotFound +} + +// FindAllBatches +func (r *repositoryInMemory) FindAllBatches(fileID string) []ach.Batcher { + r.mtx.RLock() + defer r.mtx.RUnlock() + + file, ok := r.files[fileID] + if !ok || file == nil { + return nil + } + + batches := make([]ach.Batcher, 0, len(file.Batches)) + batches = append(batches, file.Batches...) + + return batches +} + +func (r *repositoryInMemory) DeleteBatch(fileID string, batchID string) error { + r.mtx.Lock() + defer r.mtx.Unlock() + + file, ok := r.files[fileID] + if !ok || file == nil { + return fmt.Errorf("%v: no file %s with batch %s found", ErrNotFound, fileID, batchID) + } + + for i := len(file.Batches) - 1; i >= 0; i-- { + if file.Batches[i].ID() == batchID { + file.Batches = append(file.Batches[:i], file.Batches[i+1:]...) + return nil + } + } + + return ErrNotFound +} + +// cleanupOldFiles will iterate through r.files and delete entries which are older than +// the environmental variable ACH_FILE_TTL (parsed as a time.Duration). +func (r *repositoryInMemory) cleanupOldFiles() { + r.mtx.Lock() + defer r.mtx.Unlock() + + removed := 0 + tooOld := time.Now().Add(-1 * r.ttl) + tooOldStr := tooOld.Format("060102") // YYMMDD + + for i := range r.files { + if r.files[i].Header.FileCreationDate < tooOldStr { + removed++ + delete(r.files, i) + } + } + + if r.logger != nil { + r.logger.Info().Logf("removed %d ACH files older than %v", removed, tooOld.Format(time.RFC3339)) + } +} diff --git a/server/repository_test.go b/server/repository_test.go new file mode 100644 index 000000000..0189a4c08 --- /dev/null +++ b/server/repository_test.go @@ -0,0 +1,133 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package server + +import ( + "testing" + "time" + + "github.com/moov-io/ach" + "github.com/moov-io/base" + "github.com/moov-io/base/log" +) + +var ( + testTTLDuration = 0 * time.Second // disable TTL expiry +) + +func TestRepositoryFiles(t *testing.T) { + r := NewRepositoryInMemory(testTTLDuration, nil) + + if v := len(r.FindAllFiles()); v != 0 { + t.Errorf("unexpected length: %d", v) + } + + header := mockFileHeader() + f := &ach.File{ + ID: base.ID(), + Header: *header, + } + if err := r.StoreFile(f); err != nil { + t.Errorf("unexpected error: %v", err) + } + + found, err := r.FindFile(f.ID) + if err != nil || found == nil { + t.Errorf("found=%v, err=%v", found, err) + } + + if v := len(r.FindAllFiles()); v != 1 { + t.Errorf("unexpected length: %d", v) + } + + if err := r.DeleteFile(f.ID); err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +func TestRepositoryBatches(t *testing.T) { + r := NewRepositoryInMemory(testTTLDuration, nil) + + // make sure our tests are setup + if v := len(r.FindAllFiles()); v != 0 { + t.Errorf("unexpected length: %d", v) + } + + header := mockFileHeader() + f := &ach.File{ + ID: base.ID(), + Header: *header, + } + if err := r.StoreFile(f); err != nil { + t.Errorf("unexpected error: %v", err) + } + + // batch tests + if v := len(r.FindAllBatches(f.ID)); v != 0 { + t.Errorf("unexpected length: %d", v) + } + + batch := mockBatchWEB() + b, err := r.FindBatch(f.ID, batch.ID()) + if err == nil || b != nil { + t.Errorf("b=%v, err=%v", b, err) + } + + if err := r.StoreBatch(f.ID, batch); err != nil { + t.Errorf("unexpected error: %v", err) + } + + if v := len(r.FindAllBatches(f.ID)); v != 1 { + t.Errorf("unexpected length: %d", v) + } + + if err := r.DeleteBatch(f.ID, batch.ID()); err != nil { + t.Errorf("unexpected error: %v", err) + } + + if v := len(r.FindAllBatches(f.ID)); v != 0 { + t.Errorf("unexpected length: %d", v) + } +} + +func TestRepository__cleanupOldFiles(t *testing.T) { + r := NewRepositoryInMemory(testTTLDuration, nil) + if repo, ok := r.(*repositoryInMemory); !ok { + t.Fatalf("unexpected repository: %T %#v", r, r) + } else { + // write a file and later verify it's cleaned up + file := ach.NewFile() + file.Header.FileCreationDate = time.Now().Add(-1 * 24 * time.Hour).Format("060102") // YYMMDD of 24hrs ago + repo.StoreFile(file) + if n := len(repo.FindAllFiles()); n != 1 { + t.Errorf("got %d ACH files", n) + } + repo.cleanupOldFiles() // make sure we don't panic + if n := len(repo.FindAllFiles()); n != 0 { + t.Errorf("got %d ACH files", n) + } + } + + // Create a repo with a logger + r = NewRepositoryInMemory(testTTLDuration, log.NewNopLogger()) + if repo, ok := r.(*repositoryInMemory); !ok { + t.Fatalf("unexpected repository: %T %#v", r, r) + } else { + repo.cleanupOldFiles() // make sure we don't panic + } +} diff --git a/server/routing.go b/server/routing.go new file mode 100644 index 000000000..f4bd844b0 --- /dev/null +++ b/server/routing.go @@ -0,0 +1,334 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package server + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "reflect" + "strconv" + "strings" + + "github.com/moov-io/base" + moovhttp "github.com/moov-io/base/http" + "github.com/moov-io/base/log" + + "github.com/go-kit/kit/endpoint" + httptransport "github.com/go-kit/kit/transport/http" + gokitlog "github.com/go-kit/log" + "github.com/gorilla/mux" +) + +var ( + bugReportHelp = "please report this as a bug -- https://github.com/moov-io/ach/issues/new" + + // ErrBadRouting is returned when an expected path variable is missing, which is always programmer error. + ErrBadRouting = fmt.Errorf("inconsistent mapping between route and handler, %s", bugReportHelp) + ErrFoundABug = fmt.Errorf("snuck into encodeError with err == nil, %s", bugReportHelp) + + errInvalidFile = errors.New("invalid ACH file") +) + +// contextKey is a unique (and compariable) type we use +// to store and retrieve additional information in the +// go-kit context. +var contextKey struct{} + +// saveCORSHeadersIntoContext saves CORS headers into the go-kit context. +// +// This is designed to be added as a ServerOption in our main http handler. +func saveCORSHeadersIntoContext() httptransport.RequestFunc { + return func(ctx context.Context, r *http.Request) context.Context { + origin := r.Header.Get("Origin") + return context.WithValue(ctx, contextKey, origin) + } +} + +// respondWithSavedCORSHeaders looks in the go-kit request context +// for our own CORS headers. (Stored with our context key in +// saveCORSHeadersIntoContext.) +// +// This is designed to be added as a ServerOption in our main http handler. +func respondWithSavedCORSHeaders() httptransport.ServerResponseFunc { + return func(ctx context.Context, w http.ResponseWriter) context.Context { + v, ok := ctx.Value(contextKey).(string) + if ok && v != "" { + moovhttp.SetAccessControlAllowHeaders(w, v) // set CORS headers + } + return ctx + } +} + +// preflightHandler captures Corss Origin Resource Sharing (CORS) requests +// by looking at all OPTIONS requests for the Origin header, parsing that +// and responding back with the other Access-Control-Allow-* headers. +// +// Docs: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS +func preflightHandler(options []httptransport.ServerOption) http.Handler { + return httptransport.NewServer( + endpoint.Nop, + httptransport.NopRequestDecoder, + func(_ context.Context, w http.ResponseWriter, _ interface{}) error { + if v := w.Header().Get("Content-Type"); v == "" { + w.Header().Set("Content-Type", "text/plain") + } + return nil + }, + options..., + ) +} + +func MakeHTTPHandler(s Service, repo Repository, kitlog gokitlog.Logger) http.Handler { + logger := log.NewLogger(kitlog) + + r := mux.NewRouter() + options := []httptransport.ServerOption{ + httptransport.ServerErrorLogger(kitlog), + httptransport.ServerErrorEncoder(encodeError), + httptransport.ServerBefore(saveCORSHeadersIntoContext()), + httptransport.ServerAfter(respondWithSavedCORSHeaders()), + } + + // HTTP Methods + r.Methods("OPTIONS").Handler(preflightHandler(options)) // CORS pre-flight handler + r.Methods("GET").Path("/ping").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + moovhttp.SetAccessControlAllowHeaders(w, r.Header.Get("Origin")) + w.Header().Set("Content-Type", "text/plain") + w.Write([]byte("PONG")) + }) + r.Methods("GET").Path("/files").Handler(httptransport.NewServer( + getFilesEndpoint(s), + decodeGetFilesRequest, + encodeResponse, + options..., + )) + r.Methods("GET").Path("/files/{id}/build").Handler(httptransport.NewServer( + buildFileEndpoint(s, repo, logger), + decodeBuildFileRequest, + encodeResponse, + options..., + )) + r.Methods("POST").Path("/files/{fileID}").Handler(httptransport.NewServer( + createFileEndpoint(s, repo, logger), + decodeCreateFileRequest, + encodeResponse, + options..., + )) + r.Methods("GET").Path("/files/{id}").Handler(httptransport.NewServer( + getFileEndpoint(s, logger), + decodeGetFileRequest, + encodeResponse, + options..., + )) + r.Methods("GET").Path("/files/{id}/contents").Handler(httptransport.NewServer( + getFileContentsEndpoint(s, logger), + decodeGetFileContentsRequest, + encodeTextResponse, + options..., + )) + r.Methods("GET").Path("/files/{id}/validate").Handler(httptransport.NewServer( + validateFileEndpoint(s, logger), + decodeValidateFileRequest, + encodeResponse, + options..., + )) + r.Methods("POST").Path("/files/{id}/validate").Handler(httptransport.NewServer( + validateFileEndpoint(s, logger), + decodeValidateFileRequest, + encodeResponse, + options..., + )) + r.Methods("DELETE").Path("/files/{id}").Handler(httptransport.NewServer( + deleteFileEndpoint(s, logger), + decodeDeleteFileRequest, + encodeResponse, + options..., + )) + r.Methods("POST").Path("/files/{fileID}/batches").Handler(httptransport.NewServer( + createBatchEndpoint(s, logger), + decodeCreateBatchRequest, + encodeResponse, + options..., + )) + r.Methods("GET").Path("/files/{fileID}/batches").Handler(httptransport.NewServer( + getBatchesEndpoint(s, logger), + decodeGetBatchesRequest, + encodeResponse, + options..., + )) + r.Methods("GET").Path("/files/{fileID}/batches/{batchID}").Handler(httptransport.NewServer( + getBatchEndpoint(s, logger), + decodeGetBatchRequest, + encodeResponse, + options..., + )) + r.Methods("DELETE").Path("/files/{fileID}/batches/{batchID}").Handler(httptransport.NewServer( + deleteBatchEndpoint(s, logger), + decodeDeleteBatchRequest, + encodeResponse, + options..., + )) + r.Methods("POST").Path("/files/{fileID}/balance").Handler(httptransport.NewServer( + balanceFileEndpoint(s, repo, logger), + decodeBalanceFileRequest, + encodeResponse, + options..., + )) + r.Methods("POST").Path("/files/{fileID}/segment").Handler(httptransport.NewServer( + segmentFileIDEndpoint(s, repo, logger), + decodeSegmentFileIDRequest, + encodeResponse, + options..., + )) + r.Methods("POST").Path("/segment").Handler(httptransport.NewServer( + segmentFileEndpoint(s, repo, logger), + decodeSegmentFileRequest, + encodeResponse, + options..., + )) + r.Methods("POST").Path("/files/{fileID}/flatten").Handler(httptransport.NewServer( + flattenBatchesEndpoint(s, repo, logger), + decodeFlattenBatchesRequest, + encodeResponse, + options..., + )) + return r +} + +// errorer is implemented by all concrete response types that may contain +// errors. There are a few well-known values which are used to change the +// HTTP response code without needing to trigger an endpoint (transport-level) +// error. +type errorer interface { + error() error +} + +// counter is implemented by any concrete response types that may contain +// some arbitrary count information. +type counter interface { + count() int +} + +// marshalStructWithError converts a struct into a JSON response with all fields of the struct +// with our expected error formats. +// +// There are a few reasons we need to do this. +// 1. base.ErrorList marshals to an object which breaks the string format our API declares +// and isn't caught when we pass around interface{} values. +// 2. We want to return additional fields of structs (such as in createFileEndpoint) +func marshalStructWithError(in interface{}, w http.ResponseWriter) error { + v := reflect.ValueOf(in) + out := make(map[string]interface{}, v.NumField()) + + for i := 0; i < v.NumField(); i++ { + name := v.Type().Field(i).Name + value := v.Field(i).Interface() + + if err, ok := value.(error); ok { + out["error"] = err.Error() + } else { + out[name] = value + } + } + + return json.NewEncoder(w).Encode(out) +} + +// encodeResponse is the common method to encode all response types to the +// client. I chose to do it this way because, since we're using JSON, there's no +// reason to provide anything more specific. It's certainly possible to +// specialize on a per-response (per-method) basis. +func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error { + if e, ok := response.(errorer); ok && e.error() != nil { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(codeFrom(e.error())) + return marshalStructWithError(response, w) + } + + // Used for pagination + if e, ok := response.(counter); ok { + w.Header().Set("X-Total-Count", strconv.Itoa(e.count())) + } + + // Don't overwrite a header (i.e. called from encodeTextResponse) + if v := w.Header().Get("Content-Type"); v == "" { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + // Only write json body if we're setting response as json + return json.NewEncoder(w).Encode(response) + } + return nil +} + +// encodeTextResponse will marshal response into the HTTP Response +// This method is designed text/plain content-types and expects response +// to be an io.Reader. +func encodeTextResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error { + if r, ok := response.(io.Reader); ok { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + _, err := io.Copy(w, r) + return err + } + return nil +} + +// encodeError JSON encodes the supplied error +func encodeError(_ context.Context, err error, w http.ResponseWriter) { + if err == nil { + err = ErrFoundABug + } + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(codeFrom(err)) + json.NewEncoder(w).Encode(map[string]interface{}{ + "error": err.Error(), + }) +} + +func codeFrom(err error) int { + if err == nil { + return http.StatusOK + } + + errString := fmt.Sprintf("%#v", err) + if el, ok := err.(base.ErrorList); ok { + errString = el.Error() + } + switch { + case + strings.Contains(errString, errInvalidFile.Error()), // This branch comes from validateFileEndpoint + strings.Contains(errString, "*ach.FieldError"), + strings.Contains(errString, "*ach.BatchError"), + strings.Contains(errString, "*ach.ErrFile"), + strings.Contains(errString, "ach.RecordWrongLengthErr"), + strings.Contains(errString, "FieldName"): // FileFromJSON + return http.StatusBadRequest + } + + switch err { + case ErrNotFound: + return http.StatusNotFound + case ErrAlreadyExists: + return http.StatusBadRequest + default: + return http.StatusInternalServerError + } +} diff --git a/server/routing_test.go b/server/routing_test.go new file mode 100644 index 000000000..46bb24b5e --- /dev/null +++ b/server/routing_test.go @@ -0,0 +1,230 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package server + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/moov-io/ach" + "github.com/moov-io/base/log" + + httptransport "github.com/go-kit/kit/transport/http" + kitlog "github.com/go-kit/log" +) + +func TestRouting_codeFrom(t *testing.T) { + if v := codeFrom(nil); v != http.StatusOK { + t.Errorf("HTTP status: %d", v) + } + if v := codeFrom(fmt.Errorf("%v: other", errInvalidFile)); v != http.StatusBadRequest { + t.Errorf("HTTP status: %d", v) + } + if v := codeFrom(ErrNotFound); v != http.StatusNotFound { + t.Errorf("HTTP status: %d", v) + } + if v := codeFrom(ErrAlreadyExists); v != http.StatusBadRequest { + t.Errorf("HTTP status: %d", v) + } + if v := codeFrom(errors.New("other")); v != http.StatusInternalServerError { + t.Errorf("HTTP status: %d", v) + } +} + +func TestRouting_ping(t *testing.T) { + logger := log.NewNopLogger() + r := NewRepositoryInMemory(1*time.Minute, logger) + svc := NewService(r) + router := MakeHTTPHandler(svc, r, kitlog.NewNopLogger()) + + req := httptest.NewRequest("GET", "/ping", nil) + req.Header.Set("Origin", "https://moov.io") + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("bogus HTTP status: %d", w.Code) + } + if v := w.Body.String(); v != "PONG" { + t.Errorf("body: %s", v) + } + + resp := w.Result() + defer resp.Body.Close() + if v := resp.Header.Get("Access-Control-Allow-Origin"); v != "https://moov.io" { + t.Errorf("Access-Control-Allow-Origin: %s", v) + } +} + +func TestEncodeResponse(t *testing.T) { + ctx := context.TODO() + w := httptest.NewRecorder() + if err := encodeResponse(ctx, w, "hi mom"); err != nil { + t.Fatal(err) + } + w.Flush() + + var resp string + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Error(err) + } + if resp != "hi mom" { + t.Errorf("got %q", resp) + } + + v := w.Header().Get("content-type") + if v != "application/json; charset=utf-8" { + t.Errorf("got %q", v) + } +} + +func TestEncodeTextResponse(t *testing.T) { + ctx := context.TODO() + w := httptest.NewRecorder() + if err := encodeTextResponse(ctx, w, strings.NewReader("hi mom")); err != nil { + t.Fatal(err) + } + if v := w.Body.String(); v != "hi mom" { + t.Errorf("got %q", v) + } + + if v := w.Header().Get("content-type"); v != "text/plain" { + t.Errorf("got %q", v) + } +} + +func TestFilesXTotalCountHeader(t *testing.T) { + counter := getFilesResponse{ + Files: []*ach.File{ach.NewFile()}, + Err: nil, + } + + w := httptest.NewRecorder() + encodeResponse(context.Background(), w, counter) + resp := w.Result() + defer resp.Body.Close() + + actual, ok := resp.Header["X-Total-Count"] + if !ok { + t.Fatal("should have count") + } + if actual[0] != "1" { + t.Errorf("should be 1, got %v", actual[0]) + } +} + +func TestBatchesXTotalCountHeader(t *testing.T) { + bh := mockBatchHeaderWeb() + entry := mockWEBEntryDetail() + // build the batch + batch := ach.NewBatchWEB(bh) + batch.SetID(batch.Header.ID) + batch.AddEntry(entry) + + counter := getBatchesResponse{ + Batches: []ach.Batcher{batch}, + Err: nil, + } + + w := httptest.NewRecorder() + encodeResponse(context.Background(), w, counter) + resp := w.Result() + defer resp.Body.Close() + + actual, ok := resp.Header["X-Total-Count"] + if !ok { + t.Fatal("should have count") + } + if actual[0] != "1" { + t.Errorf("should be 1, got %v", actual[0]) + } +} + +func TestRouting__CORSHeaders(t *testing.T) { + ctx := context.TODO() + req := httptest.NewRequest("GET", "/files/create", nil) + req.Header.Set("Origin", "https://api.moov.io") + + ctx = saveCORSHeadersIntoContext()(ctx, req) + + w := httptest.NewRecorder() + respondWithSavedCORSHeaders()(ctx, w) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("expected no status code, but got %d", w.Code) + } + if v := w.Header().Get("Content-Type"); v != "" { + t.Errorf("expected no Content-Type, but got %q", v) + } + + // check CORS headers + if v := w.Header().Get("Access-Control-Allow-Origin"); v != "https://api.moov.io" { + t.Errorf("got %q", v) + } + if v := w.Header().Get("Access-Control-Allow-Methods"); v == "" { + t.Error("missing Access-Control-Allow-Methods") + } + if v := w.Header().Get("Access-Control-Allow-Headers"); v == "" { + t.Error("missing Access-Control-Allow-Headers") + } + if v := w.Header().Get("Access-Control-Allow-Credentials"); v == "" { + t.Error("missing Access-Control-Allow-Credentials") + } +} + +func TestPreflightHandler(t *testing.T) { + options := []httptransport.ServerOption{ + httptransport.ServerBefore(saveCORSHeadersIntoContext()), + httptransport.ServerAfter(respondWithSavedCORSHeaders()), + } + + handler := preflightHandler(options) + + // Make our pre-flight request + w := httptest.NewRecorder() + r := httptest.NewRequest("OPTIONS", "/files/create", nil) + r.Header.Set("Origin", "https://moov.io") + + // Make the request + handler.ServeHTTP(w, r) + w.Flush() + + // Check response + if v := w.Header().Get("Access-Control-Allow-Origin"); v != "https://moov.io" { + t.Errorf("got %s", v) + } + if v := w.Header().Get("Access-Control-Allow-Methods"); v == "" { + t.Error("missing Access-Control-Allow-Methods") + } + if v := w.Header().Get("Access-Control-Allow-Headers"); v == "" { + t.Error("missing Access-Control-Allow-Headers") + } + if v := w.Header().Get("Access-Control-Allow-Credentials"); v == "" { + t.Error("missing Access-Control-Allow-Credentials") + } +} diff --git a/server/server_test.go b/server/server_test.go new file mode 100644 index 000000000..edb16b5fd --- /dev/null +++ b/server/server_test.go @@ -0,0 +1,144 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package server + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/moov-io/ach" +) + +// TestServer__CreateFileEndpoint creates JSON from existing ACH Files and submits them to our +// HTTP API. We do this to ensure all SEC codes can be submitted and created via the HTTP API. +func TestServer__CreateFileEndpoint(t *testing.T) { + files := getTestFiles() + if len(files) == 0 { + t.Fatal("got no test ACH files to process") + } + t.Logf("read %d test files", len(files)) + + for _, file := range files { + f, err := os.Open(file.ACHFilepath) + if err != nil { + log.Fatal(err) + } + + achFile, err := ach.NewReader(f).Read() + if err != nil { + fmt.Printf("Issue reading file: %+v \n", err) + } + + // ensure we have a validated file structure + if err := achFile.Validate(); err != nil { + t.Errorf("Could not validate entire read file: %v", err) + } + + // If you trust the file but it's formatting is off building will probably resolve the malformed file. + if err := achFile.Create(); err != nil { + t.Errorf("Could not build file with read properties: %v", err) + } + + if err := f.Close(); err != nil { + t.Errorf("Problem closing %s: %v", file.ACHFilepath, err) + } + + // ENR ACH Files does not have BatchHeader.EffectiveEntryDate, so setting this to Today +1 to be included + // in the JSON File. For this test after the ACH file is converted to JSON, the test validates the JSON by + // calling ach.FileFromJSON(bs) and it fails with an empty date time. + if file.SECCode == "ENR" { + for _, batch := range achFile.Batches { + batch.GetHeader().EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") + } + + } + + // Marshal the ach.File into JSON for HTTP API submission + bs, err := json.Marshal(achFile) + if err != nil { + t.Fatalf("Problem converting %s to JSON: %v", file.ACHFilepath, err) + } + + httpReq, err := http.NewRequest("POST", "/files/create", bytes.NewReader(bs)) + if err != nil { + t.Fatal(err) + } + httpReq.Header.Set("Content-Type", "application/json; charset=utf-8") + + createFileReq, err := decodeCreateFileRequest(context.TODO(), httpReq) + if err != nil { + t.Error(string(bs)) + t.Fatalf("file %s had error against HTTP decode: %v", file.ACHFilepath, err) + } + + repo := NewRepositoryInMemory(testTTLDuration, nil) + s := NewService(repo) + + endpoint := createFileEndpoint(s, repo, nil) // nil logger + + resp, err := endpoint(context.TODO(), createFileReq) + if err != nil { + t.Fatalf("%s couldn't be created against our HTTP API: %v", file.ACHFilepath, err) + } + if resp == nil { + t.Fatalf("resp == nil") + } + createFileResponse, ok := resp.(createFileResponse) + if !ok { + t.Fatalf("couldn't convert %#v to createFileResponse", resp) + } + if createFileResponse.ID == "" || createFileResponse.Err != nil { + t.Fatalf("%s failed HTTP API creation: %v", file.ACHFilepath, createFileResponse.Err) + } + } +} + +type testFile struct { + SECCode string + ACHFilepath string + Filename string +} + +func getTestFiles() []testFile { + matches, err := filepath.Glob(filepath.Join("..", "test", "ach-*-read", "*.ach")) + if err != nil { + return nil + } + + var testFiles []testFile + for i := range matches { + filename := filepath.Base(matches[i]) + + testFiles = append(testFiles, testFile{ + SECCode: strings.ToUpper(filename[:3]), + ACHFilepath: matches[i], + Filename: strings.TrimSuffix(filename, ".ach"), + }) + } + + return testFiles +} diff --git a/server/service.go b/server/service.go new file mode 100644 index 000000000..a7d611054 --- /dev/null +++ b/server/service.go @@ -0,0 +1,264 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package server + +import ( + "bytes" + "errors" + "fmt" + "io" + + "github.com/moov-io/ach" + "github.com/moov-io/base" +) + +var ( + ErrNotFound = errors.New("not found") + ErrAlreadyExists = errors.New("already exists") +) + +// Service is a REST interface for interacting with ACH file structures +// TODO: Add ctx to function parameters to pass the client security token +type Service interface { + // CreateFile creates a new ach file record and returns a resource ID + CreateFile(f *ach.FileHeader) (string, error) + // AddFile retrieves a file based on the File id + GetFile(id string) (*ach.File, error) + // GetFiles retrieves all files accessible from the client. + GetFiles() []*ach.File + // BuildFile tabulates file values according to the Nacha spec + BuildFile(id string) (*ach.File, error) + // DeleteFile takes a file resource ID and deletes it from the store + DeleteFile(id string) error + // GetFileContents creates a valid plaintext file in memory assuming it has a FileHeader and at least one Batch record. + GetFileContents(id string) (io.Reader, error) + // ValidateFile + ValidateFile(id string, opts *ach.ValidateOpts) error + // BalanceFile will apply a given offset record to the file + BalanceFile(fileID string, off *ach.Offset) (*ach.File, error) + // SegmentFileID segments an ach file + SegmentFileID(id string, opts *ach.SegmentFileConfiguration) (*ach.File, *ach.File, error) + // SegmentFile segments an ach file + SegmentFile(file *ach.File, opts *ach.SegmentFileConfiguration) (*ach.File, *ach.File, error) + // FlattenBatches will minimize the ach.Batch objects in a file by consolidating EntryDetails under distinct batch headers + FlattenBatches(id string) (*ach.File, error) + // CreateBatch creates a new batch within and ach file and returns its resource ID + CreateBatch(fileID string, bh ach.Batcher) (string, error) + // GetBatch retrieves a batch based oin the file id and batch id + GetBatch(fileID string, batchID string) (ach.Batcher, error) + // GetBatches retrieves all batches associated with the file id. + GetBatches(fileID string) []ach.Batcher + // DeleteBatch takes a fileID and BatchID and removes the batch from the file + DeleteBatch(fileID string, batchID string) error +} + +// service a concrete implementation of the service. +type service struct { + store Repository +} + +// NewService creates a new concrete service +func NewService(r Repository) Service { + return &service{ + store: r, + } +} + +// CreateFile add a file to storage +// TODO(adam): the HTTP endpoint accepts malformed bodies (and missing data) +func (s *service) CreateFile(fh *ach.FileHeader) (string, error) { + // create a new file + f := ach.NewFile() + f.SetHeader(*fh) + // set resource id's + if fh.ID == "" { + id := base.ID() + f.ID = id + f.Header.ID = id + f.Control.ID = id + } else { + f.ID = fh.ID + f.Control.ID = fh.ID + } + + if err := s.store.StoreFile(f); err != nil { + return "", err + } + return f.ID, nil +} + +// GetFile returns a files based on the supplied id +func (s *service) GetFile(id string) (*ach.File, error) { + f, err := s.store.FindFile(id) + if err != nil { + return nil, ErrNotFound + } + return f, nil +} + +func (s *service) GetFiles() []*ach.File { + return s.store.FindAllFiles() +} + +// BuildFile tabulates file values according to the Nacha spec +func (s *service) BuildFile(id string) (*ach.File, error) { + file, err := s.GetFile(id) + if err != nil { + return nil, fmt.Errorf("build file: error reading file %s: %v", id, err) + } + err = file.Create() + return file, err +} + +func (s *service) DeleteFile(id string) error { + return s.store.DeleteFile(id) +} + +func (s *service) GetFileContents(id string) (io.Reader, error) { + f, err := s.GetFile(id) + if err != nil { + return nil, fmt.Errorf("problem reading file %s: %v", id, err) + } + if err := f.Create(); err != nil { + return nil, fmt.Errorf("problem creating file %s: %v", id, err) + } + + var buf bytes.Buffer + w := ach.NewWriter(&buf) + if err := w.Write(f); err != nil { + return nil, fmt.Errorf("problem writing plaintext file %s: %v", id, err) + } + if err := w.Flush(); err != nil { + return nil, err + } + + if buf.Len() == 0 { + return nil, errors.New("empty ACH file contents") + } + + return &buf, nil +} + +func (s *service) ValidateFile(id string, opts *ach.ValidateOpts) error { + f, err := s.GetFile(id) + if err != nil { + return fmt.Errorf("problem reading file %s: %v", id, err) + } + return f.ValidateWith(opts) +} + +func (s *service) CreateBatch(fileID string, batch ach.Batcher) (string, error) { + if batch == nil { + return "", errors.New("no batch provided") + } + if batch.GetHeader().ID == "" { + id := base.ID() + batch.SetID(id) + batch.GetHeader().ID = id + batch.GetControl().ID = id + } else { + batch.SetID(batch.GetHeader().ID) + batch.GetControl().ID = batch.GetHeader().ID + } + if err := s.store.StoreBatch(fileID, batch); err != nil { + return "", err + } + return batch.ID(), nil +} + +func (s *service) GetBatch(fileID string, batchID string) (ach.Batcher, error) { + b, err := s.store.FindBatch(fileID, batchID) + if err != nil { + return nil, ErrNotFound + } + return b, nil +} + +func (s *service) GetBatches(fileID string) []ach.Batcher { + return s.store.FindAllBatches(fileID) +} + +func (s *service) DeleteBatch(fileID string, batchID string) error { + return s.store.DeleteBatch(fileID, batchID) +} + +func (s *service) BalanceFile(fileID string, off *ach.Offset) (*ach.File, error) { + f, err := s.GetFile(fileID) + if err != nil { + return nil, err + } + if err := f.Create(); err != nil { + return nil, err + } + // Apply the Offset to each Batch and then re-create (to tabulate new EntryDetail records) + for i := range f.Batches { + f.Batches[i].WithOffset(off) + if err := f.Batches[i].Create(); err != nil { + return nil, err + } + } + f.ID = base.ID() // overwrite the ID so it's new and unique + if err := f.Create(); err != nil { + return nil, err + } + // Save our new file + if err := s.store.StoreFile(f); err != nil { + return nil, err + } + return f, nil +} + +// SegmentFileID takes an ACH FileID and segments the files into a credit ACH File and debit ACH File and adds to in memory storage. +func (s *service) SegmentFileID(fileID string, opts *ach.SegmentFileConfiguration) (*ach.File, *ach.File, error) { + f, err := s.GetFile(fileID) + if err != nil { + return nil, nil, err + } + return s.SegmentFile(f, opts) +} + +// SegmentFile takes an ACH File and segments the files into a credit ACH File and debit ACH File and adds to in memory storage. +func (s *service) SegmentFile(file *ach.File, opts *ach.SegmentFileConfiguration) (*ach.File, *ach.File, error) { + // Build/tabulate file in the case it is malformed. + if err := file.Create(); err != nil { + return nil, nil, err + } + + creditFile, debitFile, err := file.SegmentFile(opts) + if err != nil { + return nil, nil, err + } + return creditFile, debitFile, nil +} + +// FlattenBatches consolidates batches that have the same BatchHeader +func (s *service) FlattenBatches(fileID string) (*ach.File, error) { + f, err := s.GetFile(fileID) + if err != nil { + return nil, err + } + // File Create in the case a file is malformed. + if err := f.Create(); err != nil { + return nil, err + } + ff, err := f.FlattenBatches() + if err != nil { + return nil, err + } + return ff, err +} diff --git a/server/service_test.go b/server/service_test.go new file mode 100644 index 000000000..667d62d0e --- /dev/null +++ b/server/service_test.go @@ -0,0 +1,681 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package server + +import ( + "io" + "log" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/moov-io/ach" + "github.com/moov-io/base" + + "github.com/stretchr/testify/require" +) + +// test mocks are in mock_test.go + +// CreateFile tests +func TestCreateFile(t *testing.T) { + s := mockServiceInMemory() + id, err := s.CreateFile(mockFileHeader()) + if err != nil { + t.Fatal(err.Error()) + } + if id != "12345" { + t.Errorf("expected %s received %s w/ error %s", "12345", id, err) + } +} +func TestCreateFileIDExists(t *testing.T) { + s := mockServiceInMemory() + h := ach.FileHeader{ID: "98765"} + id, err := s.CreateFile(&h) + if err != ErrAlreadyExists { + t.Errorf("expected %s received %s w/ error %s", "ErrAlreadyExists", id, err) + } +} + +func TestCreateFileNoID(t *testing.T) { + s := mockServiceInMemory() + h := ach.NewFileHeader() + id, err := s.CreateFile(&h) + if len(id) < 3 { + t.Errorf("expected %s received %s w/ error %s", "NextID", id, err) + } + if err != nil { + t.Fatal(err.Error()) + } +} + +// Service.GetFile tests + +func TestGetFile(t *testing.T) { + s := mockServiceInMemory() + f, err := s.GetFile("98765") + if err != nil { + t.Errorf("expected %s received %s w/ error %s", "98765", f.ID, err) + } +} + +func TestGetFileNotFound(t *testing.T) { + s := mockServiceInMemory() + f, err := s.GetFile("12345") + if err != ErrNotFound { + t.Errorf("expected %s received %s w/ error %s", "ErrNotFound", f.ID, err) + } +} + +// Service.GetFiles tests + +func TestGetFiles(t *testing.T) { + s := mockServiceInMemory() + files := s.GetFiles() + if len(files) != 1 { + t.Errorf("expected %s received %v", "1", len(files)) + } +} + +// Service.DeleteFile tests + +func TestDeleteFile(t *testing.T) { + s := mockServiceInMemory() + err := s.DeleteFile("98765") + if err != nil { + t.Errorf("expected %s received %s", "nil", err) + } + _, err = s.GetFile("98765") + if err != ErrNotFound { + t.Errorf("expected %s received %s", "ErrNotFound", err) + } +} + +// Service.GetFileContents tests + +func TestGetFileContents(t *testing.T) { + s := mockServiceInMemory() + id, err := s.CreateFile(mockFileHeader()) + if err != nil { + t.Fatal(err.Error()) + } + + // make the file valid + batch := mockBatchWEB() + s.CreateBatch(id, batch) + + // build file + r, err := s.GetFileContents(id) + if err != nil { + if !strings.Contains(err.Error(), "mandatory ") { + t.Fatal(err.Error()) + } + } + if r != nil { + bs, err := io.ReadAll(r) + if err != nil { + t.Fatal(err.Error()) + } + + if len(bs) == 0 { + t.Fatal("expected to read fil") + } + } +} + +// Service.ValidateFile tests + +func TestValidateFile(t *testing.T) { + s := mockServiceInMemory() + id, err := s.CreateFile(mockFileHeader()) + if err != nil { + t.Fatal(err.Error()) + } + if err := s.ValidateFile(id, nil); err != nil { + if !strings.Contains(err.Error(), "mandatory ") { + t.Fatal(err.Error()) + } + } +} + +func TestValidateFileMissing(t *testing.T) { + s := mockServiceInMemory() + err := s.ValidateFile("missing", nil) + if err == nil { + t.Fatal("expected error") + } +} + +func TestValidateFileBad(t *testing.T) { + s := mockServiceInMemory() + + fId, _ := s.CreateFile(mockFileHeader()) + + // setup batch + bh := mockBatchHeaderWeb() + bh.ID = "11111" + b, _ := ach.NewBatch(bh) + bId, e1 := s.CreateBatch(fId, b) + batch, e2 := s.GetBatch(fId, bId) + if batch == nil { + t.Fatalf("couldn't get batch, e1=%v, e2=%v", e1, e2) + } + + // setup file, add batch + f, err := s.GetFile(fId) + if f == nil { + t.Fatalf("couldn't get file: %v", err) + } + if len(f.AddBatch(batch)) == 0 { + t.Fatal("problem adding batch to file") + } + + // validate + if err := s.ValidateFile(fId, nil); err == nil { + t.Fatal("expected error") + } +} + +func TestValidateFileOpts(t *testing.T) { + s := mockServiceInMemory() + fh := mockFileHeader() + fh.ImmediateOrigin = "00000000" + id, err := s.CreateFile(fh) + if err != nil { + t.Fatal(err.Error()) + } + + if err := s.ValidateFile(id, &ach.ValidateOpts{RequireABAOrigin: false}); err != nil { + if !strings.Contains(err.Error(), "mandatory ") { + t.Fatal(err.Error()) + } + } +} + +// Service.CreateBatch tests + +// TestCreateBatch tests creating a new batch when file.ID exists and batch.id does not exist +func TestCreateBatch(t *testing.T) { + s := mockServiceInMemory() + bh := mockBatchHeaderWeb() + bh.ID = "11111" + b, _ := ach.NewBatch(bh) + id, err := s.CreateBatch("98765", b) + if err != nil { + t.Fatal(err.Error()) + } + if id != "11111" { + t.Errorf("expected %s received %s w/ error %v", "11111", id, err) + } +} + +// TestCreateBatchIDExists Create a new batch with batch.id already present. Should fail. +func TestCreateBatchIDExists(t *testing.T) { + s := mockServiceInMemory() + b, _ := ach.NewBatch(mockBatchHeaderWeb()) + id, err := s.CreateBatch("98765", b) + if err != ErrAlreadyExists { + t.Errorf("expected %s received %s w/ error %v", "ErrAlreadyExists", id, err) + } +} + +// TestCreateBatchFileIDExits create a batch when the file.id does not exist. Should fail. +func TestCreateBatchFileIDExits(t *testing.T) { + s := mockServiceInMemory() + b, _ := ach.NewBatch(mockBatchHeaderWeb()) + id, err := s.CreateBatch("55555", b) + if err != ErrNotFound { + t.Errorf("expected %s received %s w/ error %v", "ErrNotFound", id, err) + } +} + +// TestCreateBatchIDBank create a new batch when the batch.id is nil but file.id is valid. Should generate batch.id and save. +func TestCreateBatchIDBlank(t *testing.T) { + s := mockServiceInMemory() + bh := mockBatchHeaderWeb() + bh.ID = "" + b, _ := ach.NewBatch(bh) + id, err := s.CreateBatch("98765", b) + if len(id) < 3 { + t.Errorf("expected %s received %s w/ error %v", "NextID", id, err) + } + if err != nil { + t.Fatal(err.Error()) + } +} + +// Service.GetBatch + +// TestGetBatch return a batch for the existing file.id and batch.id +func TestGetBatch(t *testing.T) { + s := mockServiceInMemory() + b, err := s.GetBatch("98765", "54321") + if err != nil { + t.Errorf("problem getting batch: %v", err) + } + if b.ID() != "54321" { + t.Errorf("expected %s received %s w/ error %v", "54321", b.ID(), err) + } +} + +// TestGetBatchNotFound return a failure if the batch.id is not found +func TestGetBatchNotFound(t *testing.T) { + s := mockServiceInMemory() + b, err := s.GetBatch("98765", "55555") + if err != ErrNotFound { + t.Errorf("expected %s received %#v w/ error %v", "ErrNotFound", b, err) + } +} + +// Service.GetBatches + +// TestGetBatches return a list of batches for the supplied file.id +func TestGetBatches(t *testing.T) { + s := mockServiceInMemory() + batches := s.GetBatches("98765") + if len(batches) != 1 { + t.Errorf("expected %s received %v", "1", len(batches)) + } +} + +// Service.DeleteBatch + +// TestDeleteBatch removes a batch with existing file and batch id. +func TestDeleteBatch(t *testing.T) { + s := mockServiceInMemory() + err := s.DeleteBatch("98765", "54321") + if err != nil { + t.Errorf("expected %s received error %v", "nil", err) + } +} + +func TestBalanceFile(t *testing.T) { + s := mockServiceInMemory() + + // store a file in the Service and balance it + fd, err := os.Open(filepath.Join("..", "test", "testdata", "ppd-debit.ach")) + if err != nil { + t.Fatal(err) + } + file, err := ach.NewReader(fd).Read() + if err != nil { + t.Fatal(err) + } + if bs, es := len(file.Batches), len(file.Batches[0].GetEntries()); bs != 1 || es != 1 { + t.Errorf("got %d batches and %d entries", bs, es) + } + + // save our file + fileID, err := s.CreateFile(&file.Header) + if err != nil { + t.Fatal(err) + } + if _, err := s.CreateBatch(fileID, file.Batches[0]); err != nil { + t.Fatal(err) + } + + balancedFile, err := s.BalanceFile(fileID, &ach.Offset{ + RoutingNumber: "987654320", + AccountNumber: "28198241", + AccountType: ach.OffsetChecking, + Description: "OFFSET", + }) + if err != nil { + t.Fatal(err) + } + + if fileID == balancedFile.ID { + t.Errorf("fileID=%s balancedFile.ID=%s", fileID, balancedFile.ID) + } + + if bs, es := len(balancedFile.Batches), len(balancedFile.Batches[0].GetEntries()); bs != 1 || es != 2 { + t.Errorf("got %d batches and %d entries", bs, es) + } + if ed := balancedFile.Batches[0].GetEntries()[1]; ed.IndividualName != "OFFSET" { + t.Errorf("ed.IndividualName=%s", ed.IndividualName) + } + + require.NoError(t, balancedFile.Validate()) +} + +func TestBalanceFileErrors(t *testing.T) { + s := mockServiceInMemory() + if file, err := s.BalanceFile(base.ID(), &ach.Offset{}); err == nil { + t.Errorf("expected error file=%#v", file) + } + + fh := ach.NewFileHeader() + fileID, err := s.CreateFile(&fh) + if err != nil { + t.Fatal(err) + } + if file, err := s.BalanceFile(fileID, &ach.Offset{}); err == nil { + t.Errorf("expected error file=%#v", file) + } +} + +// TestSegmentFile creates a Segmented File from an existing ACH File +func TestSegmentFileID(t *testing.T) { + s := mockServiceInMemory() + + fh := ach.NewFileHeader() + fh.ID = "333339" + fh.ImmediateDestination = "231380104" + fh.ImmediateOrigin = "121042882" + fh.FileCreationDate = time.Now().Format("060102") + fh.FileCreationTime = time.Now().AddDate(0, 0, 1).Format("1504") // HHmm + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.MixedDebitsAndCredits + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.PPD + bh.CompanyEntryDescription = "REG.SALARY" + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") + bh.ODFIIdentification = "121042882" + bh.ID = "433333" + b, _ := ach.NewBatch(bh) + + entryOne := ach.NewEntryDetail() + entryOne.TransactionCode = ach.CheckingDebit + entryOne.SetRDFI("231380104") + entryOne.DFIAccountNumber = "123456789" + entryOne.Amount = 200000000 + entryOne.SetTraceNumber(bh.ODFIIdentification, 1) + entryOne.IndividualName = "Debit Account" + + entryTwo := ach.NewEntryDetail() + entryTwo.TransactionCode = ach.CheckingCredit + entryTwo.SetRDFI("231380104") + entryTwo.DFIAccountNumber = "987654321" + entryTwo.Amount = 100000000 + entryTwo.SetTraceNumber(bh.ODFIIdentification, 2) + entryTwo.IndividualName = "Credit Account 1" + + entryThree := ach.NewEntryDetail() + entryThree.TransactionCode = ach.CheckingCredit + entryThree.SetRDFI("231380104") + entryThree.DFIAccountNumber = "837098765" + entryThree.Amount = 100000000 + entryThree.SetTraceNumber(bh.ODFIIdentification, 3) + entryThree.IndividualName = "Credit Account 2" + + b.AddEntry(entryOne) + b.AddEntry(entryTwo) + b.AddEntry(entryThree) + if err := b.Create(); err != nil { + t.Fatalf("Unexpected error building batch: %s\n", err) + } + + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(b) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fileID, err := s.CreateFile(&fh) + if err != nil { + t.Fatal(err.Error()) + } + + batchID, err := s.CreateBatch("333339", b) + if err != nil { + t.Fatal(err.Error()) + } + + if batchID == "" { + t.Fatal("No Batch ID") + } + + creditFile, debitFile, err := s.SegmentFileID(fileID, nil) + + if err != nil { + t.Fatalf("could not segment file w/ error %v", err) + } + + if creditFile == nil { + t.Fatal("No credit File") + } + + if debitFile == nil { + t.Fatal("No debit File") + } +} + +// TestSegmentFile_FileValidateError return an error on file Validation +func TestSegmentFileError(t *testing.T) { + s := mockServiceInMemory() + _, _, err := s.SegmentFileID("98765", nil) + + if err != nil { + if !base.Match(err, ach.ErrConstructor) { + t.Errorf("%T: %s", err, err) + } + } +} + +// TestSegmentFileDebitsOnly creates a Segmented File from an existing ACH File +func TestSegmentFileDebitsOnly(t *testing.T) { + s := mockServiceInMemory() + + fh := ach.NewFileHeader() + fh.ID = "333339" + fh.ImmediateDestination = "231380104" + fh.ImmediateOrigin = "121042882" + fh.FileCreationDate = time.Now().Format("060102") + fh.FileCreationTime = time.Now().AddDate(0, 0, 1).Format("1504") // HHmm + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.PPD + bh.CompanyEntryDescription = "REG.SALARY" + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") + bh.ODFIIdentification = "121042882" + bh.ID = "433333" + b, _ := ach.NewBatch(bh) + + entryOne := ach.NewEntryDetail() + entryOne.TransactionCode = ach.CheckingDebit + entryOne.SetRDFI("231380104") + entryOne.DFIAccountNumber = "123456789" + entryOne.Amount = 200000000 + entryOne.SetTraceNumber(bh.ODFIIdentification, 1) + entryOne.IndividualName = "Debit Account" + + b.AddEntry(entryOne) + if err := b.Create(); err != nil { + t.Fatalf("Unexpected error building batch: %s\n", err) + } + + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(b) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fileID, err := s.CreateFile(&fh) + if err != nil { + t.Fatal(err.Error()) + } + + batchID, err := s.CreateBatch("333339", b) + if err != nil { + t.Fatal(err.Error()) + } + + if batchID == "" { + t.Fatal("No Batch ID") + } + + creditFile, debitFile, err := s.SegmentFileID(fileID, nil) + + if err != nil { + t.Fatalf("could not segment file w/ error %v", err) + } + + if len(creditFile.Batches) != 0 { + t.Fatal("Credit File should not have batches") + } + + if len(debitFile.Batches) < 1 { + t.Fatal("Debit file should have batches") + } + +} + +// TestSegmentFileDebitsOnlyBatchID creates a Segmented File from an existing ACH File +func TestSegmentFileDebitsOnlyBatchID(t *testing.T) { + s := mockServiceInMemory() + + fh := ach.NewFileHeader() + fh.ID = "333339" + fh.ImmediateDestination = "231380104" + fh.ImmediateOrigin = "121042882" + fh.FileCreationDate = time.Now().Format("060102") + fh.FileCreationTime = time.Now().AddDate(0, 0, 1).Format("1504") // HHmm + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.PPD + bh.CompanyEntryDescription = "REG.SALARY" + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") + bh.ODFIIdentification = "121042882" + bh.ID = "433333" + b, _ := ach.NewBatch(bh) + + entryOne := ach.NewEntryDetail() + entryOne.TransactionCode = ach.CheckingDebit + entryOne.SetRDFI("231380104") + entryOne.DFIAccountNumber = "123456789" + entryOne.Amount = 200000000 + entryOne.SetTraceNumber(bh.ODFIIdentification, 1) + entryOne.IndividualName = "Debit Account" + + b.AddEntry(entryOne) + if err := b.Create(); err != nil { + t.Fatalf("Unexpected error building batch: %s\n", err) + } + + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(b) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + fileID, err := s.CreateFile(&fh) + if err != nil { + t.Fatal(err.Error()) + } + + batchID, err := s.CreateBatch("333339", b) + if err != nil { + t.Fatal(err.Error()) + } + + if batchID == "" { + t.Fatal("No Batch ID") + } + + _, debitFile, err := s.SegmentFileID(fileID, nil) + + if err != nil { + t.Fatalf("could not segment file w/ error %v", err) + } + + if debitFile.Batches[0].ID() == "" { + t.Fatal("No Batch ID") + } +} + +func TestFlattenBatches(t *testing.T) { + s := mockServiceInMemory() + + f, err := os.Open(filepath.Join("..", "test", "testdata", "flattenBatchesMultipleBatchHeaders.ach")) + + if err != nil { + t.Fatal(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + t.Fatalf("Issue reading file: %+v \n", err) + } + + fileID, err := s.CreateFile(&achFile.Header) + if err != nil { + t.Fatal(err.Error()) + } + + for _, b := range achFile.Batches { + batchID, err := s.CreateBatch(fileID, b) + if err != nil { + t.Fatal(err.Error()) + } + if batchID == "" { + t.Fatal("No Batch ID") + } + } + + ff, err := s.FlattenBatches(fileID) + + if err != nil { + t.Fatalf("Could not flatten the file: %+v \n", err) + } + + if err := ff.Validate(); err != nil { + t.Fatalf("Flatten file did not validate: %+v \n", err) + } +} + +func TestSegmentFile_NoFileID(t *testing.T) { + s := mockServiceInMemory() + fileID := "" + _, err := s.FlattenBatches(fileID) + + if err != nil { + if !strings.Contains(err.Error(), "not found") { + t.Fatal(err.Error()) + } + } +} + +func TestFlattenBatches_NoFileID(t *testing.T) { + s := mockServiceInMemory() + _, _, err := s.SegmentFileID("", nil) + + if err != nil { + if !strings.Contains(err.Error(), "not found") { + t.Fatal(err.Error()) + } + } +} diff --git a/server/test/issue403/issue403-addenda02.json b/server/test/issue403/issue403-addenda02.json new file mode 100644 index 000000000..287fd26bd --- /dev/null +++ b/server/test/issue403/issue403-addenda02.json @@ -0,0 +1,35 @@ +{ + "batchHeader": { + "serviceClassCode": 220, + "companyName": "Company Name", + "ODFIIdentification": "121042882", + "id": "", + "companyDiscretionaryData": "", + "standardEntryClassCode": "PPD", + "companyEntryDescription": "PAYROLL", + "companyDescriptiveDate": "122018", + "effectiveEntryDate": "2018-12-14T12:52:21Z", + "originatorStatusCode": 1, + "batchNumber": 1 + }, + "entryDetails": [{ + "id": "", + "transactionCode": 22, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "123456789", + "amount": 200000, + "individualName": "Edward James", + "identificationNumber": "26", + "discretionaryData": "", + "addendaRecordIndicator": 0, + "traceNumber": "", + "addenda02": "", + "addenda05": [ + { + "paymentRelatedInformation": "December Payroll" + } + ], + "category": "Forward" + }] +} diff --git a/server/test/issue403/issue403-amount.json b/server/test/issue403/issue403-amount.json new file mode 100644 index 000000000..005c974a2 --- /dev/null +++ b/server/test/issue403/issue403-amount.json @@ -0,0 +1,35 @@ +{ + "batchHeader": { + "serviceClassCode": 220, + "companyName": "Company Name", + "ODFIIdentification": "121042882", + "id": "", + "companyDiscretionaryData": "", + "standardEntryClassCode": "PPD", + "companyEntryDescription": "PAYROLL", + "companyDescriptiveDate": "122018", + "effectiveEntryDate": "2018-12-14T12:52:34Z", + "originatorStatusCode": 1, + "batchNumber": 1 + }, + "entryDetails": [{ + "id": "", + "transactionCode": 22, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "123456789", + "amount": "200000", + "individualName": "Edward James", + "identificationNumber": "26", + "discretionaryData": "", + "addendaRecordIndicator": 0, + "traceNumber": "", + "addenda02": "", + "addenda05": [ + { + "paymentRelatedInformation": "December Payroll" + } + ], + "category": "Forward" + }] +} diff --git a/server/test/issue403/issue403_test.go b/server/test/issue403/issue403_test.go new file mode 100644 index 000000000..3184625d3 --- /dev/null +++ b/server/test/issue403/issue403_test.go @@ -0,0 +1,54 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package test + +import ( + "encoding/json" + "os" + "strings" + "testing" + + "github.com/moov-io/ach" +) + +// Testissue403 attempts to parse a few JSON files as ach.Batch objects, but +// each JSON object is malformed and we're matching on the error returned. +// +// See: https://github.com/moov-io/ach/issues/403 +func TestIssue403(t *testing.T) { + expectError := func(path string, msg string) { + t.Helper() + + fd, err := os.Open(path) + if err != nil { + t.Fatal(err) + } + var batch ach.Batch + if err := json.NewDecoder(fd).Decode(&batch); err != nil { + if !strings.Contains(err.Error(), msg) { + t.Errorf("(file: %s) %q doesn't contain expected %q", path, err.Error(), msg) + } + } else { + t.Error("expected error, but got none") + } + } + + // test cases + expectError("issue403-addenda02.json", "addenda02") + expectError("issue403-amount.json", "amount") +} diff --git a/server/test/issue786/batch_test.go b/server/test/issue786/batch_test.go new file mode 100644 index 000000000..cffc2c195 --- /dev/null +++ b/server/test/issue786/batch_test.go @@ -0,0 +1,102 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package test + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + "time" + + "github.com/moov-io/ach" + "github.com/moov-io/ach/server" + "github.com/moov-io/base/log" + + kitlog "github.com/go-kit/log" +) + +func TestIssue786(t *testing.T) { + repo := server.NewRepositoryInMemory(0*time.Second, log.NewNopLogger()) + svc := server.NewService(repo) + handler := server.MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + // create the file + fd, err := os.Open(filepath.Join("testdata", "1-create.json")) + if err != nil { + t.Fatal(err) + } + defer fd.Close() + + w := httptest.NewRecorder() + req := httptest.NewRequest("POST", "/files/create", fd) + req.Header.Set("Content-Type", "application/json") + handler.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("bogus HTTP status: %d", w.Code) + t.Fatalf("body: %v", w.Body.String()) + } + + var file struct { + ID string `json:"id"` + } + if err := json.NewDecoder(w.Body).Decode(&file); err != nil || file.ID == "" { + t.Fatal(err) + } + + // file created, so add the batch now + fd, err = os.Open(filepath.Join("testdata", "2-add-batch.json")) + if err != nil { + t.Fatal(err) + } + defer fd.Close() + + w = httptest.NewRecorder() + req = httptest.NewRequest("POST", fmt.Sprintf("/files/%s/batches", file.ID), fd) + req.Header.Set("Content-Type", "application/json") + handler.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("bogus HTTP status: %d", w.Code) + t.Fatalf("body: %v", w.Body.String()) + } + + // read the file and verify + w = httptest.NewRecorder() + req = httptest.NewRequest("GET", fmt.Sprintf("/files/%s/contents", file.ID), nil) + handler.ServeHTTP(w, req) + w.Flush() + + if w.Code != http.StatusOK { + t.Errorf("bogus HTTP status: %d", w.Code) + } + + created, err := ach.NewReader(w.Body).Read() + if err != nil { + t.Fatal(err) + } + if len(created.Batches) != 2 { + t.Errorf("got %d batches", len(created.Batches)) + } +} diff --git a/server/test/issue786/testdata/1-create.json b/server/test/issue786/testdata/1-create.json new file mode 100644 index 000000000..60870131f --- /dev/null +++ b/server/test/issue786/testdata/1-create.json @@ -0,0 +1,44 @@ +{ + "batches": [ + { + "entryDetails": [ + { + "traceNumber": "121042887841793", + "addendaRecordIndicator": 1, + "discretionaryData": "ap", + "individualName": "Wrong debit", + "identificationNumber": "7813fde9df675a0", + "amount": 2500, + "DFIAccountNumber": "000000099", + "checkDigit": "2", + "RDFIIdentification": "12104288", + "transactionCode": 37, + "id": "" + } + ], + "batchHeader": { + "batchNumber": 1, + "ODFIIdentification": "12104288", + "originatorStatusCode": 1, + "effectiveEntryDate": "200218", + "companyDescriptiveDate": "200217", + "companyEntryDescription": "test xfer", + "standardEntryClassCode": "PPD", + "companyIdentification": "123456789", + "companyName": "Friendly Corp", + "serviceClassCode": 200, + "id": "" + } + } + ], + "fileHeader": { + "immediateOriginName": "Moov Bank", + "immediateDestinationName": "Moov Bank", + "fileIDModifier": "1", + "fileCreationTime": "0437", + "fileCreationDate": "200217", + "immediateOrigin": "121042882", + "immediateDestination": "121042882", + "id": "" + } +} diff --git a/server/test/issue786/testdata/2-add-batch.json b/server/test/issue786/testdata/2-add-batch.json new file mode 100644 index 000000000..10862e277 --- /dev/null +++ b/server/test/issue786/testdata/2-add-batch.json @@ -0,0 +1,20 @@ +{ + "entryDetails": [ + { + "individualName": "Taylor Swift", + "amount": 1235, + "DFIAccountNumber": "54321", + "checkDigit": "0", + "RDFIIdentification": "12345678", + "transactionCode": 22 + } + ], + "batchHeader": { + "ODFIIdentification": "12345678", + "companyIdentification": "1", + "companyName": "Acme Corp", + "serviceClassCode": 220, + "standardEntryClassCode": "PPD", + "companyEntryDescription": "test" + } +} diff --git a/tagged-release.sh b/tagged-release.sh new file mode 100644 index 000000000..0ac3eba74 --- /dev/null +++ b/tagged-release.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +[ $# -eq 0 ] && { echo "Usage: $0 version_tag"; exit 1; } + +# releases should be created from master branch +if [ "$(git branch --show-current)" != "master" ] +then + echo "switching to branch 'master'" + git checkout master > /dev/null 2>&1 +fi + +status=$(git status --porcelain) + +# only 2 files should be changed +if [ "$(echo "$status" | wc -l)" -ne 2 ] +then + echo "Only version.go and CHANGELOG.md should be updated!" + if [ -n "$status" ] + then + printf "Pending changes:\n%s\n" "$status" + fi + exit +elif ! echo "$status" | grep -q "CHANGELOG.md" && ! echo "$status" | grep -q "version.go" +then + echo "version.go and changelog.md must be updated to proceed" + exit +fi + +# make sure this is a new tag +if git show-ref --tags | grep -q "$1"; then + echo "$1 already exists!" + exit +fi + +firstLine=$(head -n 1 CHANGELOG.md) +if ! echo "$firstLine" | grep -q "$1"; then + echo "new tag ($1) doesn't match CHANGELOG ($firstLine)" + exit +fi + +expectedHeader=$(printf "## $1 (Released %s)" "$(date +"%Y-%m-%d")") +if [ "$firstLine" != "$expectedHeader" ] +then + echo "Did you update the CHANGELOG's header? Expected \"$expectedHeader\", found \"$firstLine\"" + exit +fi + +git add CHANGELOG.md version.go +git commit -m "release $1" +git tag "$1" +git push origin master +git push origin "$1" \ No newline at end of file diff --git a/test/ach-ack-read/ack-read.ach b/test/ach-ack-read/ack-read.ach new file mode 100644 index 000000000..7f41972dd --- /dev/null +++ b/test/ach-ack-read/ack-read.ach @@ -0,0 +1,10 @@ +101 031300012 2313801041810290000A094101Federal Reserve Bank My Bank Name +5220Name on Account 231380104 ACKVndr Pay 181030 1231380100000001 +624031300012744-5678-99 0000000000031300010000001Best. #1 0231380100000001 +624031300012744-5678-99 0000000000031300010000002Best. #1 0231380100000002 +82200000020006260002000000000000000000000000231380104 231380100000001 +9000001000001000000020006260002000000000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/ach-ack-read/main.go b/test/ach-ack-read/main.go new file mode 100644 index 000000000..e302554f2 --- /dev/null +++ b/test/ach-ack-read/main.go @@ -0,0 +1,53 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("ack-read.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Credit Total Amount: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("Batch Credit Total Amount: %d\n", achFile.Batches[0].GetControl().TotalCreditEntryDollarAmount) + fmt.Printf("Total Amount: %d\n", achFile.Batches[0].GetEntries()[0].Amount) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Original Trace Number: %s\n", achFile.Batches[0].GetEntries()[0].OriginalTraceNumberField()) +} diff --git a/test/ach-ack-read/main_test.go b/test/ach-ack-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-ack-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-ack-write/main.go b/test/ach-ack-write/main.go new file mode 100644 index 000000000..d9b0d4be0 --- /dev/null +++ b/test/ach-ack-write/main.go @@ -0,0 +1,94 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH ACK file acknowledging a credit + // Important: All financial institutions are different and will require registration and exact field values. + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "031300012" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "231380104" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.CreditsOnly + bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.ACK + bh.CompanyEntryDescription = "Vndr Pay" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "23138010" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingZeroDollarRemittanceCredit + entry.SetRDFI("031300012") // Receiver's bank transit routing number + entry.DFIAccountNumber = "744-5678-99" // Receiver's bank account number + entry.Amount = 0 // Amount of transaction with no decimal. Zero Dollar Amount + entry.SetOriginalTraceNumber("031300010000001") + entry.SetReceivingCompany("Best. #1") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + + entryOne := ach.NewEntryDetail() // Fee Entry + entryOne.TransactionCode = ach.CheckingZeroDollarRemittanceCredit + entryOne.SetRDFI("031300012") // Receiver's bank transit routing number + entryOne.DFIAccountNumber = "744-5678-99" // Receiver's bank account number + entryOne.Amount = 0 // Amount of transaction with no decimal. Zero Dollar Amount + entryOne.SetOriginalTraceNumber("031300010000002") + entryOne.SetReceivingCompany("Best. #1") + entryOne.SetTraceNumber(bh.ODFIIdentification, 2) + + // build the batch + batch := ach.NewBatchACK(bh) + batch.AddEntry(entry) + batch.AddEntry(entryOne) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-ack-write/main_test.go b/test/ach-ack-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-ack-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-adv-read/adv-read.ach b/test/ach-adv-read/adv-read.ach new file mode 100644 index 000000000..02f7b55f7 --- /dev/null +++ b/test/ach-adv-read/adv-read.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821811130000A094101Federal Reserve Bank My Bank Name +5280Company Name, In 121042882 ADVAccounting 181114 0121042880000001 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500001 +682231380104744-5678-99 00000025000012104288211139 Name 0011000010500002 +828000000200462760200000000000000025000000000000000000050000Company Name, Inc 121042880000001 +90000010000010000000200462760200000000000000025000000000000000000050000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/ach-adv-read/main.go b/test/ach-adv-read/main.go new file mode 100644 index 000000000..227b4a0f9 --- /dev/null +++ b/test/ach-adv-read/main.go @@ -0,0 +1,58 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("adv-read.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Credit Total Amount: %d\n", achFile.ADVControl.TotalCreditEntryDollarAmountInFile) + fmt.Printf("Debit Total Amount: %d\n", achFile.ADVControl.TotalDebitEntryDollarAmountInFile) + fmt.Printf("OriginatorStatusCode: %d\n", achFile.Batches[0].GetHeader().OriginatorStatusCode) + fmt.Printf("Batch Credit Total Amount: %d\n", achFile.Batches[0].GetADVControl().TotalCreditEntryDollarAmount) + fmt.Printf("Batch Debit Total Amount: %d\n", achFile.Batches[0].GetADVControl().TotalDebitEntryDollarAmount) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Entry Amount: %d\n", achFile.Batches[0].GetADVEntries()[0].Amount) + fmt.Printf("Sequence Number: %d\n", achFile.Batches[0].GetADVEntries()[0].SequenceNumber) + fmt.Printf("EntryOne Amount: %d\n", achFile.Batches[0].GetADVEntries()[1].Amount) + fmt.Printf("EntryOne Sequence Number: %d\n", achFile.Batches[0].GetADVEntries()[1].SequenceNumber) +} diff --git a/test/ach-adv-read/main_test.go b/test/ach-adv-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-adv-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-adv-write-multipleBatches/main.go b/test/ach-adv-write-multipleBatches/main.go new file mode 100644 index 000000000..5095d90ab --- /dev/null +++ b/test/ach-adv-write-multipleBatches/main.go @@ -0,0 +1,88 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" + fh.ImmediateOrigin = "121042882" + fh.FileCreationDate = time.Now().Format("060102") + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + file := ach.NewFile() + file.SetHeader(fh) + + for i := 0; i < 4; i++ { + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.AutomatedAccountingAdvices + bh.CompanyName = "Company Name, Inc" + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.ADV + bh.CompanyEntryDescription = "Accounting" + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") + bh.ODFIIdentification = "121042882" + bh.OriginatorStatusCode = 0 + + batch, _ := ach.NewBatch(bh) + + // Create Entry + entrySeq := 0 + for i := 0; i < 3; i++ { + entry := ach.NewADVEntryDetail() + entry.TransactionCode = ach.CreditForDebitsOriginated + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 50000 + entry.AdviceRoutingNumber = "121042882" + entry.FileIdentification = "11131" + entry.ACHOperatorData = "" + entry.IndividualName = "Name" + entry.DiscretionaryData = "" + entry.AddendaRecordIndicator = 0 + entry.ACHOperatorRoutingNumber = "01100001" + entry.JulianDay = 50 + entry.SequenceNumber = entrySeq + + batch.AddADVEntry(entry) + } + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + file.AddBatch(batch) + } + + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-adv-write-multipleBatches/main_test.go b/test/ach-adv-write-multipleBatches/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-adv-write-multipleBatches/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-adv-write/main.go b/test/ach-adv-write/main.go new file mode 100644 index 000000000..0efb63b40 --- /dev/null +++ b/test/ach-adv-write/main.go @@ -0,0 +1,106 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH ADV file to debit an external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "121042882" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.AutomatedAccountingAdvices + bh.CompanyName = "Company Name, Inc" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.ADV + bh.CompanyEntryDescription = "Accounting" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" // Originating Routing Number + bh.OriginatorStatusCode = 0 + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewADVEntryDetail() + // Credit for ACH debits originated + entry.TransactionCode = ach.CreditForDebitsOriginated // + entry.SetRDFI("231380104") // Receiver's bank transit routing number + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 50000 + entry.AdviceRoutingNumber = "121042882" + entry.FileIdentification = "11131" + entry.ACHOperatorData = "" + entry.IndividualName = "Name" + entry.DiscretionaryData = "" + entry.AddendaRecordIndicator = 0 + entry.ACHOperatorRoutingNumber = "01100001" + entry.JulianDay = 50 + entry.SequenceNumber = 1 + + entryOne := ach.NewADVEntryDetail() + // Debit for ACH credits originated + entryOne.TransactionCode = ach.DebitForCreditsOriginated + entryOne.SetRDFI("231380104") // Receiver's bank transit routing number + entryOne.DFIAccountNumber = "744-5678-99" + entryOne.Amount = 250000 + entryOne.AdviceRoutingNumber = "121042882" + entryOne.FileIdentification = "11139" + entryOne.ACHOperatorData = "" + entryOne.IndividualName = "Name" + entryOne.DiscretionaryData = "" + entryOne.AddendaRecordIndicator = 0 + entryOne.ACHOperatorRoutingNumber = "01100001" + entryOne.JulianDay = 50 + entryOne.SequenceNumber = 2 + + // build the batch + batch := ach.NewBatchADV(bh) + batch.AddADVEntry(entry) + batch.AddADVEntry(entryOne) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-adv-write/main_test.go b/test/ach-adv-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-adv-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-arc-read/arc-debit.ach b/test/ach-arc-read/arc-debit.ach new file mode 100644 index 000000000..f654f82eb --- /dev/null +++ b/test/ach-arc-read/arc-debit.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821806070000A094101Federal Reserve Bank My Bank Name +5225Payee Name 121042882 ARCACH ARC 180608 1121042880000001 +62723138010412345678 0000250000123879654 ABC Company 0121042880000001 +82250000010023138010000000250000000000000000121042882 121042880000001 +9000001000001000000010023138010000000250000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/ach-arc-read/main.go b/test/ach-arc-read/main.go new file mode 100644 index 000000000..0daf76547 --- /dev/null +++ b/test/ach-arc-read/main.go @@ -0,0 +1,51 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("arc-debit.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Check Serial Number: %s\n", achFile.Batches[0].GetEntries()[0].CheckSerialNumberField()) +} diff --git a/test/ach-arc-read/main_test.go b/test/ach-arc-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-arc-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-arc-write/main.go b/test/ach-arc-write/main.go new file mode 100644 index 000000000..8ee241b34 --- /dev/null +++ b/test/ach-arc-write/main.go @@ -0,0 +1,82 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH ARC file to debit an external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "121042882" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Payee Name" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.ARC + bh.CompanyEntryDescription = "ACH ARC" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingDebit // Code 27: Debit (withdrawal) from checking account + entry.SetRDFI("231380104") // Receiver's bank transit routing number + entry.DFIAccountNumber = "12345678" // Receiver's bank account number + entry.Amount = 250000 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetCheckSerialNumber("123879654") + entry.SetReceivingCompany("ABC Company") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + + // build the batch + batch := ach.NewBatchARC(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-arc-write/main_test.go b/test/ach-arc-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-arc-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-atx-read/atx-read.ach b/test/ach-atx-read/atx-read.ach new file mode 100644 index 000000000..3eee21216 --- /dev/null +++ b/test/ach-atx-read/atx-read.ach @@ -0,0 +1,10 @@ +101 031300012 2313801041810290000A094101Federal Reserve Bank My Bank Name +5220Name on Account 231380104 ATXVndr Pay 181030 1231380100000001 +624031300012744-5678-99 00000000000313000100000010002Receiver Company 011231380100000001 +705Credit account 1 for service 00010000001 +705Credit account 2 for service 00020000001 +624031300012744-5678-99 00000000000313000100000020002Receiver Company 011231380100000002 +705Credit account 1 for leadership 00010000002 +705Credit account 2 for leadership 00020000002 +82200000060006260002000000000000000000000000231380104 231380100000001 +9000001000001000000060006260002000000000000000000000000 \ No newline at end of file diff --git a/test/ach-atx-read/main.go b/test/ach-atx-read/main.go new file mode 100644 index 000000000..ab20cd8ba --- /dev/null +++ b/test/ach-atx-read/main.go @@ -0,0 +1,59 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("atx-read.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("Total Amount Credit: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Total Amount: %d\n", achFile.Batches[0].GetEntries()[0].Amount) + fmt.Printf("Original Trace Number: %s\n", achFile.Batches[0].GetEntries()[0].OriginalTraceNumberField()) + fmt.Printf("Addenda1: %s\n", achFile.Batches[0].GetEntries()[0].Addenda05[0].String()) + fmt.Printf("Addenda2: %s\n", achFile.Batches[0].GetEntries()[0].Addenda05[1].String()) + fmt.Printf("Total Amount: %d\n", achFile.Batches[0].GetEntries()[1].Amount) + fmt.Printf("Original Trace Number: %s\n", achFile.Batches[0].GetEntries()[1].OriginalTraceNumberField()) + fmt.Printf("Addenda1: %s\n", achFile.Batches[0].GetEntries()[1].Addenda05[0].String()) + fmt.Printf("Addenda2: %s\n", achFile.Batches[0].GetEntries()[1].Addenda05[1].String()) +} diff --git a/test/ach-atx-read/main_test.go b/test/ach-atx-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-atx-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-atx-write/main.go b/test/ach-atx-write/main.go new file mode 100644 index 000000000..041ddaa1a --- /dev/null +++ b/test/ach-atx-write/main.go @@ -0,0 +1,124 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH AXT file acknowledging a CTX credit + // Important: All financial institutions are different and will require registration and exact field values. + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "031300012" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "231380104" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.CreditsOnly + bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.ATX + bh.CompanyEntryDescription = "Vndr Pay" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "23138010" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingZeroDollarRemittanceCredit // Code 22: Demand Debit(deposit) to checking account + entry.SetRDFI("031300012") // Receiver's bank transit routing number + entry.DFIAccountNumber = "744-5678-99" // Receiver's bank account number + entry.Amount = 0 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetOriginalTraceNumber("031300010000001") + entry.SetCATXAddendaRecords(2) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.AddendaRecordIndicator = 1 + + entryOne := ach.NewEntryDetail() // Fee Entry + entryOne.TransactionCode = ach.CheckingZeroDollarRemittanceCredit + entryOne.SetRDFI("031300012") // Receiver's bank transit routing number + entryOne.DFIAccountNumber = "744-5678-99" // Receiver's bank account number + entryOne.Amount = 0 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entryOne.SetOriginalTraceNumber("031300010000002") + entryOne.SetCATXAddendaRecords(2) + entryOne.SetCATXReceivingCompany("Receiver Company") + entryOne.SetTraceNumber(bh.ODFIIdentification, 2) + entryOne.DiscretionaryData = "01" + entryOne.AddendaRecordIndicator = 1 + + entryAd1 := ach.NewAddenda05() + entryAd1.PaymentRelatedInformation = "Credit account 1 for service" + entryAd1.SequenceNumber = 1 + entryAd1.EntryDetailSequenceNumber = 0000001 + + entryAd2 := ach.NewAddenda05() + entryAd2.PaymentRelatedInformation = "Credit account 2 for service" + entryAd2.SequenceNumber = 2 + entryAd2.EntryDetailSequenceNumber = 0000001 + + entryOneAd1 := ach.NewAddenda05() + entryOneAd1.PaymentRelatedInformation = "Credit account 1 for leadership" + entryOneAd1.SequenceNumber = 1 + entryOneAd1.EntryDetailSequenceNumber = 0000002 + + entryOneAd2 := ach.NewAddenda05() + entryOneAd2.PaymentRelatedInformation = "Credit account 2 for leadership" + entryOneAd2.SequenceNumber = 2 + entryOneAd2.EntryDetailSequenceNumber = 0000002 + + // build the batch + batch := ach.NewBatchATX(bh) + batch.AddEntry(entry) + batch.GetEntries()[0].AddAddenda05(entryAd1) + batch.GetEntries()[0].AddAddenda05(entryAd2) + batch.AddEntry(entryOne) + batch.GetEntries()[1].AddAddenda05(entryOneAd1) + batch.GetEntries()[1].AddAddenda05(entryOneAd2) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-atx-write/main_test.go b/test/ach-atx-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-atx-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-boc-read/boc-debit.ach b/test/ach-boc-read/boc-debit.ach new file mode 100644 index 000000000..256e9120e --- /dev/null +++ b/test/ach-boc-read/boc-debit.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821806070000A094101Federal Reserve Bank My Bank Name +5225Payee Name 121042882 BOCACH BOC 180608 1121042880000001 +62723138010412345678 0000250000123879654 ABC Company 0121042880000001 +82250000010023138010000000250000000000000000121042882 121042880000001 +9000001000001000000010023138010000000250000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/ach-boc-read/main.go b/test/ach-boc-read/main.go new file mode 100644 index 000000000..ca59d00b1 --- /dev/null +++ b/test/ach-boc-read/main.go @@ -0,0 +1,51 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("boc-debit.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Check Serial Number: %s\n", achFile.Batches[0].GetEntries()[0].CheckSerialNumberField()) +} diff --git a/test/ach-boc-read/main_test.go b/test/ach-boc-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-boc-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-boc-write/main.go b/test/ach-boc-write/main.go new file mode 100644 index 000000000..b748a7bb3 --- /dev/null +++ b/test/ach-boc-write/main.go @@ -0,0 +1,82 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH BOC file to debit an external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "121042882" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Payee Name" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.BOC + bh.CompanyEntryDescription = "ACH BOC" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingDebit // Code 27: Debit (withdrawal) from checking account + entry.SetRDFI("231380104") // Receiver's bank transit routing number + entry.DFIAccountNumber = "12345678" // Receiver's bank account number + entry.Amount = 250000 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetCheckSerialNumber("123879654") + entry.SetReceivingCompany("ABC Company") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + + // build the batch + batch := ach.NewBatchBOC(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-boc-write/main_test.go b/test/ach-boc-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-boc-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-ccd-read/ccd-debit.ach b/test/ach-ccd-read/ccd-debit.ach new file mode 100644 index 000000000..d64f2d0c5 --- /dev/null +++ b/test/ach-ccd-read/ccd-debit.ach @@ -0,0 +1,10 @@ +101 231380104 0313000121811260000A094101Federal Reserve Bank My Bank Name +5225Name on Account 031300012 CCDVndr Pay 181127 1031300010000001 +627231380104744-5678-99 0000500000location #1 Best Co. #1 S 0031300010000001 +627231380104744-5678-99 0000000125Fee #1 Best Co. #1 S 0031300010000002 +82250000020046276020000000500125000000000000031300012 031300010000001 +9000001000001000000020046276020000000500125000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/ach-ccd-read/main.go b/test/ach-ccd-read/main.go new file mode 100644 index 000000000..8cbf13d93 --- /dev/null +++ b/test/ach-ccd-read/main.go @@ -0,0 +1,59 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("ccd-debit.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("Total Amount Credit: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("CCD Entry Discretionary Data: %s\n", achFile.Batches[0].GetEntries()[0].DiscretionaryDataField()) + fmt.Printf("CCD Entry Identification Number: %s\n", achFile.Batches[0].GetEntries()[0].IdentificationNumberField()) + fmt.Printf("CCD Entry Receiving Company: %s\n", achFile.Batches[0].GetEntries()[0].ReceivingCompanyField()) + fmt.Printf("CCD Entry Trace Number: %s\n", achFile.Batches[0].GetEntries()[0].TraceNumberField()) + fmt.Printf("CCD Fee Discretionary Data: %s\n", achFile.Batches[0].GetEntries()[1].DiscretionaryDataField()) + fmt.Printf("CCD Fee Identification Number: %s\n", achFile.Batches[0].GetEntries()[1].IdentificationNumberField()) + fmt.Printf("CCD Fee Receiving Company: %s\n", achFile.Batches[0].GetEntries()[1].ReceivingCompanyField()) + fmt.Printf("CCD Fee Trace Number: %s\n", achFile.Batches[0].GetEntries()[1].TraceNumberField()) +} diff --git a/test/ach-ccd-read/main_test.go b/test/ach-ccd-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-ccd-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-ccd-write/main.go b/test/ach-ccd-write/main.go new file mode 100644 index 000000000..1e88fa6f8 --- /dev/null +++ b/test/ach-ccd-write/main.go @@ -0,0 +1,96 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH CCD file to send/credit a external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "031300012" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.CCD + bh.CompanyEntryDescription = "Vndr Pay" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "031300012" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingDebit // Code 22: Demand Debit(deposit) to checking account + entry.SetRDFI("231380104") // Receiver's bank transit routing number + entry.DFIAccountNumber = "744-5678-99" // Receiver's bank account number + entry.Amount = 500000 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.IdentificationNumber = "location #1" + entry.SetReceivingCompany("Best Co. #1") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.DiscretionaryData = "S" + + entryOne := ach.NewEntryDetail() // Fee Entry + entryOne.TransactionCode = ach.CheckingDebit // Code 22: Demand Debit(deposit) to checking account + entryOne.SetRDFI("231380104") // Receiver's bank transit routing number + entryOne.DFIAccountNumber = "744-5678-99" // Receiver's bank account number + entryOne.Amount = 125 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entryOne.IdentificationNumber = "Fee #1" + entryOne.SetReceivingCompany("Best Co. #1") + entryOne.SetTraceNumber(bh.ODFIIdentification, 2) + entryOne.DiscretionaryData = "S" + + // build the batch + batch := ach.NewBatchCCD(bh) + batch.AddEntry(entry) + batch.AddEntry(entryOne) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-ccd-write/main_test.go b/test/ach-ccd-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-ccd-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-cie-read/cie-credit.ach b/test/ach-cie-read/cie-credit.ach new file mode 100644 index 000000000..f994aa266 --- /dev/null +++ b/test/ach-cie-read/cie-credit.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821806200000A094101Federal Reserve Bank My Bank Name +5220Name on Account 121042882 CIEPayment 180621 1121042880000001 +62223138010412345678 0100000000 Receiver Account Name 011121042880000001 +705Credit Store Account 00010000001 +82200000020023138010000000000000000100000000121042882 121042880000001 +9000001000001000000020023138010000000000000000100000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/ach-cie-read/main.go b/test/ach-cie-read/main.go new file mode 100644 index 000000000..ddcec817a --- /dev/null +++ b/test/ach-cie-read/main.go @@ -0,0 +1,52 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("cie-credit.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("Total Amount Credit: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Addenda05: %s\n", achFile.Batches[0].GetEntries()[0].Addenda05[0].String()) +} diff --git a/test/ach-cie-read/main_test.go b/test/ach-cie-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-cie-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-cie-write/main.go b/test/ach-cie-write/main.go new file mode 100644 index 000000000..2ee0b43cc --- /dev/null +++ b/test/ach-cie-write/main.go @@ -0,0 +1,92 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH CIE file to send/credit a external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "121042882" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.CreditsOnly + bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.CIE + bh.CompanyEntryDescription = "Payment" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingCredit + entry.SetRDFI("231380104") // Receiver's bank transit routing number + entry.DFIAccountNumber = "12345678" // Receiver's bank account number + entry.Amount = 100000000 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.IndividualName = "Receiver Account Name" // Identifies the receiver of the transaction + entry.DiscretionaryData = "01" + entry.AddendaRecordIndicator = 1 + entry.AddendaRecordIndicator = 1 + + addenda05 := ach.NewAddenda05() + addenda05.PaymentRelatedInformation = "Credit Store Account" + addenda05.SequenceNumber = 1 + addenda05.EntryDetailSequenceNumber = 0000001 + + // build the batch + batch := ach.NewBatchCIE(bh) + batch.AddEntry(entry) + batch.GetEntries()[0].AddAddenda05(addenda05) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-cie-write/main_test.go b/test/ach-cie-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-cie-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-cor-read/cor-read.ach b/test/ach-cor-read/cor-read.ach new file mode 100644 index 000000000..17e8ba0ee --- /dev/null +++ b/test/ach-cor-read/cor-read.ach @@ -0,0 +1,10 @@ +101 231380104 1210428822102251048A094101Federal Reserve Bank My Bank Name +5220Your Company, in 121042882 CORVendor Pay 210412 1121042880000001 +621231380104744-5678-99 0000000000location #23 Best Co. #23 1121042880000001 +798C01121042880000001 121042881918171614 091012980000088 +82200000020023138010000000000000000000000000121042882 121042880000001 +9000001000001000000020023138010000000000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/ach-cor-read/main.go b/test/ach-cor-read/main.go new file mode 100644 index 000000000..d6e57b594 --- /dev/null +++ b/test/ach-cor-read/main.go @@ -0,0 +1,53 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("cor-read.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("Total Amount Credit: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Entry Detail: %s\n", achFile.Batches[0].GetEntries()[0].String()) + fmt.Printf("Addenda98: %s\n", achFile.Batches[0].GetEntries()[0].Addenda98.String()) +} diff --git a/test/ach-cor-read/main_test.go b/test/ach-cor-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-cor-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-cor-write/main.go b/test/ach-cor-write/main.go new file mode 100644 index 000000000..acf792ad2 --- /dev/null +++ b/test/ach-cor-write/main.go @@ -0,0 +1,95 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write a COR File + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "121042882" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.CreditsOnly + bh.StandardEntryClassCode = ach.COR + bh.CompanyName = "Your Company, inc" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "Vendor Pay" + bh.ODFIIdentification = "121042882" // Originating Routing Number + bh.EffectiveEntryDate = "210412" + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + + entry.TransactionCode = ach.CheckingReturnNOCCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.IdentificationNumber = "location #23" + entry.SetReceivingCompany("Best Co. #23") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.AddendaRecordIndicator = 1 + + addenda98 := ach.NewAddenda98() + addenda98.ChangeCode = "C01" + addenda98.OriginalTrace = "121042880000001" + addenda98.OriginalDFI = "121042882" + addenda98.CorrectedData = "1918171614" + addenda98.TraceNumber = "91012980000088" + + entry.Addenda98 = addenda98 + entry.Category = ach.CategoryNOC + + // build the batch + batch := ach.NewBatchCOR(bh) + batch.AddEntry(entry) + + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-cor-write/main_test.go b/test/ach-cor-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-cor-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-ctx-read/ctx-debit.ach b/test/ach-ctx-read/ctx-debit.ach new file mode 100644 index 000000000..1efd7c503 --- /dev/null +++ b/test/ach-ctx-read/ctx-debit.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821811260000A094101Federal Reserve Bank My Bank Name +5225Name on Account 121042882 CTXACH CTX 181127 1121042880000001 +62723138010412345678 010000000045689033 0002Receiver Company 011121042880000001 +705Debit First Account 00010000001 +705Debit Second Account 00020000001 +82250000030023138010000100000000000000000000121042882 121042880000001 +9000001000001000000030023138010000100000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/ach-ctx-read/main.go b/test/ach-ctx-read/main.go new file mode 100644 index 000000000..f5c41359b --- /dev/null +++ b/test/ach-ctx-read/main.go @@ -0,0 +1,53 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("ctx-debit.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("Total Amount Credit: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Addenda1: %s\n", achFile.Batches[0].GetEntries()[0].Addenda05[0].String()) + fmt.Printf("Addenda2: %s\n", achFile.Batches[0].GetEntries()[0].Addenda05[1].String()) +} diff --git a/test/ach-ctx-read/main_test.go b/test/ach-ctx-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-ctx-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-ctx-write/main.go b/test/ach-ctx-write/main.go new file mode 100644 index 000000000..1bc54ce35 --- /dev/null +++ b/test/ach-ctx-write/main.go @@ -0,0 +1,99 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH CTX file to send/credit a external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "121042882" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.CTX + bh.CompanyEntryDescription = "ACH CTX" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingDebit // Code 27: Debit checking account + entry.SetRDFI("231380104") // Receiver's bank transit routing number + entry.DFIAccountNumber = "12345678" // Receiver's bank account number + entry.Amount = 100000000 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(2) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.DiscretionaryData = "01" + + addenda1 := ach.NewAddenda05() + addenda1.PaymentRelatedInformation = "Debit First Account" + addenda1.SequenceNumber = 1 + addenda1.EntryDetailSequenceNumber = 0000001 + + addenda2 := ach.NewAddenda05() + addenda2.PaymentRelatedInformation = "Debit Second Account" + addenda2.SequenceNumber = 2 + addenda2.EntryDetailSequenceNumber = 0000001 + + // build the batch + batch := ach.NewBatchCTX(bh) + batch.AddEntry(entry) + batch.Entries[0].AddendaRecordIndicator = 1 + batch.GetEntries()[0].AddAddenda05(addenda1) + batch.GetEntries()[0].AddAddenda05(addenda2) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-ctx-write/main_test.go b/test/ach-ctx-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-ctx-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-dne-read/dne-read.ach b/test/ach-dne-read/dne-read.ach new file mode 100644 index 000000000..10150c532 --- /dev/null +++ b/test/ach-dne-read/dne-read.ach @@ -0,0 +1,10 @@ +101 031300012 2313801041811020000A094101Federal Reserve Bank My Bank Name +5220Name on Account 231380104 DNEDeath 181103 2231380100000001 +621031300012744-5678-99 0000000000031300010000001Best. #1 1231380100000001 +705 DATE OF DEATH*010218*CUSTOMERSSN*#########*AMOUNT*$$$$.cc\ 00010000001 +82200000020003130001000000000000000000000000231380104 231380100000001 +9000001000001000000020003130001000000000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/ach-dne-read/main.go b/test/ach-dne-read/main.go new file mode 100644 index 000000000..61a1fbc1e --- /dev/null +++ b/test/ach-dne-read/main.go @@ -0,0 +1,51 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("dne-read.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount: %d\n", achFile.Batches[0].GetEntries()[0].Amount) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Payment Related Information: %s\n", achFile.Batches[0].GetEntries()[0].Addenda05[0].PaymentRelatedInformation) +} diff --git a/test/ach-dne-read/main_test.go b/test/ach-dne-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-dne-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-dne-write/main.go b/test/ach-dne-write/main.go new file mode 100644 index 000000000..92e0f0d8f --- /dev/null +++ b/test/ach-dne-write/main.go @@ -0,0 +1,90 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH DNE file acknowledging a credit + // Important: All financial institutions are different and will require registration and exact field values. + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "031300012" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "231380104" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.CreditsOnly + bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.DNE + bh.CompanyEntryDescription = "Death" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "23138010" // Originating Routing Number + bh.OriginatorStatusCode = 2 + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingReturnNOCCredit + entry.SetRDFI("031300012") // Receiver's bank transit routing number + entry.DFIAccountNumber = "744-5678-99" // Receiver's bank account number + entry.Amount = 0 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetOriginalTraceNumber("031300010000001") + entry.SetReceivingCompany("Best. #1") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + + addenda := ach.NewAddenda05() + addenda.PaymentRelatedInformation = ` DATE OF DEATH*010218*CUSTOMERSSN*#########*AMOUNT*$$$$.cc\` // From NACHA 2013 Official Rules + entry.AddAddenda05(addenda) + entry.AddendaRecordIndicator = 1 + + // build the batch + batch := ach.NewBatchDNE(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-dne-write/main_test.go b/test/ach-dne-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-dne-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-enr-read/enr-read.ach b/test/ach-enr-read/enr-read.ach new file mode 100644 index 000000000..7a89299c1 --- /dev/null +++ b/test/ach-enr-read/enr-read.ach @@ -0,0 +1,10 @@ +101 031300012 2313801041811260000A094101Federal Reserve Bank My Bank Name +5225Name on Account 231380104 ENRAUTOENROLL 1231380100000001 +627031300012744-5678-99 0000000000031300010000001Best. #1 1231380100000001 +70522*12200004*3*123987654321*777777777*DOE*JOHN*1\ 00010000001 +82250000020003130001000000000000000000000000231380104 231380100000001 +9000001000001000000020003130001000000000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/ach-enr-read/main.go b/test/ach-enr-read/main.go new file mode 100644 index 000000000..8d85232f0 --- /dev/null +++ b/test/ach-enr-read/main.go @@ -0,0 +1,63 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("enr-read.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount: %d\n", achFile.Batches[0].GetEntries()[0].Amount) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + + batch, ok := achFile.Batches[0].(*ach.BatchENR) + if !ok { + log.Fatalf("Batch not ENR, got %T %#v\n", achFile.Batches[0], achFile.Batches[0]) + } + add := batch.GetEntries()[0].Addenda05[0] + + fmt.Printf("Payment Related Information: %s\n", add.PaymentRelatedInformation) + info, err := batch.ParsePaymentInformation(add) + if err != nil { + log.Fatalf("Problem Parsing ENR Addenda05 PaymentRelatedInformation: %v\n", err) + } + fmt.Println(info.String()) +} diff --git a/test/ach-enr-read/main_test.go b/test/ach-enr-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-enr-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-enr-write/main.go b/test/ach-enr-write/main.go new file mode 100644 index 000000000..ba609fe0e --- /dev/null +++ b/test/ach-enr-write/main.go @@ -0,0 +1,88 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH ENR file acknowledging a credit + // Important: All financial institutions are different and will require registration and exact field values. + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "031300012" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "231380104" // Routing Number of the ACH Operator or sending point that is sending the file + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + fh.FileCreationDate = time.Now().Format("060102") + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.ENR + bh.CompanyEntryDescription = "AUTOENROLL" + bh.ODFIIdentification = "23138010" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("031300012") // Receiver's bank transit routing number + entry.DFIAccountNumber = "744-5678-99" // Receiver's bank account number + entry.Amount = 0 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetOriginalTraceNumber("031300010000001") + entry.SetReceivingCompany("Best. #1") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + + addenda05 := ach.NewAddenda05() + addenda05.PaymentRelatedInformation = `22*12200004*3*123987654321*777777777*DOE*JOHN*1\` // From NACHA 2013 Official Rules + entry.AddAddenda05(addenda05) + entry.AddendaRecordIndicator = 1 + + // build the batch + batch := ach.NewBatchENR(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-enr-write/main_test.go b/test/ach-enr-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-enr-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-iat-read/iat-credit.ach b/test/ach-iat-read/iat-credit.ach new file mode 100644 index 000000000..bd9e4115e --- /dev/null +++ b/test/ach-iat-read/iat-credit.ach @@ -0,0 +1,20 @@ +101 121042882 2313801041812180000A094101Bank My Bank Name +5220 FF3 US123456789 IATTRADEPAYMTCADUSD181219 1231380100000001 +6221210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +718Bank of France 01456456456987987 FR 00010000001 +82200000100012104288000000000000000000100000 231380100000001 +9000001000002000000100012104288000000000000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/ach-iat-read/main.go b/test/ach-iat-read/main.go new file mode 100644 index 000000000..ff814a2e8 --- /dev/null +++ b/test/ach-iat-read/main.go @@ -0,0 +1,63 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("iat-credit.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total File Amount Credit: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("Total Batch Amount Credit: %d\n", achFile.IATBatches[0].Control.TotalCreditEntryDollarAmount) + fmt.Printf("SEC Code: %s\n", achFile.IATBatches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Entry: %s\n", achFile.IATBatches[0].GetEntries()[0]) + fmt.Printf("Entry Amount: %d\n", achFile.IATBatches[0].GetEntries()[0].Amount) + fmt.Printf("Addenda Record Indicator: %d\n", achFile.IATBatches[0].GetEntries()[0].AddendaRecordIndicator) + fmt.Printf("Addenda10: %s\n", achFile.IATBatches[0].GetEntries()[0].Addenda10) + fmt.Printf("Addenda11: %s\n", achFile.IATBatches[0].GetEntries()[0].Addenda11) + fmt.Printf("Addenda12: %s\n", achFile.IATBatches[0].GetEntries()[0].Addenda12) + fmt.Printf("Addenda13: %s\n", achFile.IATBatches[0].GetEntries()[0].Addenda13) + fmt.Printf("Addenda14: %s\n", achFile.IATBatches[0].GetEntries()[0].Addenda14) + fmt.Printf("Addenda15: %s\n", achFile.IATBatches[0].GetEntries()[0].Addenda15) + fmt.Printf("Addenda16: %s\n", achFile.IATBatches[0].GetEntries()[0].Addenda16) + fmt.Printf("Addenda17: %s\n", achFile.IATBatches[0].GetEntries()[0].Addenda17[0].String()) + fmt.Printf("Addenda18: %s\n", achFile.IATBatches[0].GetEntries()[0].Addenda18[0].String()) +} diff --git a/test/ach-iat-read/main_test.go b/test/ach-iat-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-iat-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-iat-write-mutipleBatches/main.go b/test/ach-iat-write-mutipleBatches/main.go new file mode 100644 index 000000000..5760a0b5b --- /dev/null +++ b/test/ach-iat-write-mutipleBatches/main.go @@ -0,0 +1,148 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH IAT file to debit a external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "121042882" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "231380104" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Bank" + fh.ImmediateOriginName = "My Bank Name" + + file := ach.NewFile() + file.SetHeader(fh) + + for i := 0; i < 4; i++ { + bh := ach.NewIATBatchHeader() + bh.ServiceClassCode = ach.MixedDebitsAndCredits + bh.ForeignExchangeIndicator = "FF" + bh.ForeignExchangeReferenceIndicator = 3 + bh.ISODestinationCountryCode = "US" + bh.OriginatorIdentification = "123456789" + bh.StandardEntryClassCode = ach.IAT + bh.CompanyEntryDescription = "TRADEPAYMT" + bh.ISOOriginatingCurrencyCode = "CAD" + bh.ISODestinationCurrencyCode = "USD" + bh.ODFIIdentification = "23138010" + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + + batch := ach.NewIATBatch(bh) + + // Create Entry + entrySeq := 0 + for i := 0; i < 3; i++ { + entrySeq = entrySeq + 1 + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewIATEntryDetail() + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("121042882") + entry.AddendaRecords = 007 + entry.DFIAccountNumber = "123456789" + entry.Amount = 100000 // 1000.00 + entry.SetTraceNumber("23138010", entrySeq) + entry.Category = ach.CategoryForward + + addenda10 := ach.NewAddenda10() + addenda10.TransactionTypeCode = "ANN" + addenda10.ForeignPaymentAmount = 100000 + addenda10.ForeignTraceNumber = "928383-23938" + addenda10.Name = "BEK Enterprises" + entry.Addenda10 = addenda10 + + addenda11 := ach.NewAddenda11() + addenda11.OriginatorName = "BEK Solutions" + addenda11.OriginatorStreetAddress = "15 West Place Street" + entry.Addenda11 = addenda11 + + addenda12 := ach.NewAddenda12() + addenda12.OriginatorCityStateProvince = "JacobsTown*PA\\" + addenda12.OriginatorCountryPostalCode = "US*19305\\" + entry.Addenda12 = addenda12 + + addenda13 := ach.NewAddenda13() + addenda13.ODFIName = "Wells Fargo" + addenda13.ODFIIDNumberQualifier = "01" + addenda13.ODFIIdentification = "231380104" + addenda13.ODFIBranchCountryCode = "US" + entry.Addenda13 = addenda13 + + addenda14 := ach.NewAddenda14() + addenda14.RDFIName = "Citadel Bank" + addenda14.RDFIIDNumberQualifier = "01" + addenda14.RDFIIdentification = "121042882" + addenda14.RDFIBranchCountryCode = "CA" + entry.Addenda14 = addenda14 + + addenda15 := ach.NewAddenda15() + addenda15.ReceiverIDNumber = "987465493213987" + addenda15.ReceiverStreetAddress = "2121 Front Street" + entry.Addenda15 = addenda15 + + addenda16 := ach.NewAddenda16() + addenda16.ReceiverCityStateProvince = "LetterTown*AB\\" + addenda16.ReceiverCountryPostalCode = "CA*80014\\" + entry.Addenda16 = addenda16 + + addenda17 := ach.NewAddenda17() + addenda17.PaymentRelatedInformation = "This is an international payment" + addenda17.SequenceNumber = 1 + entry.AddAddenda17(addenda17) + + addenda18 := ach.NewAddenda18() + addenda18.ForeignCorrespondentBankName = "Bank of France" + addenda18.ForeignCorrespondentBankIDNumberQualifier = "01" + addenda18.ForeignCorrespondentBankIDNumber = "456456456987987" + addenda18.ForeignCorrespondentBankBranchCountryCode = "FR" + addenda18.SequenceNumber = 3 + entry.AddAddenda18(addenda18) + + batch.AddEntry(entry) + } + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + file.AddIATBatch(batch) + } + + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-iat-write-mutipleBatches/main_test.go b/test/ach-iat-write-mutipleBatches/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-iat-write-mutipleBatches/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-iat-write/main.go b/test/ach-iat-write/main.go new file mode 100644 index 000000000..7b95b9654 --- /dev/null +++ b/test/ach-iat-write/main.go @@ -0,0 +1,229 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH IAT file to debit a external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "121042882" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "231380104" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewIATBatchHeader() + bh.ServiceClassCode = ach.MixedDebitsAndCredits + bh.ForeignExchangeIndicator = "FF" + bh.ForeignExchangeReferenceIndicator = 3 + bh.ISODestinationCountryCode = "US" + bh.OriginatorIdentification = "123456789" + bh.StandardEntryClassCode = ach.IAT + bh.CompanyEntryDescription = "TRADEPAYMT" + bh.ISOOriginatingCurrencyCode = "CAD" + bh.ISODestinationCurrencyCode = "USD" + bh.ODFIIdentification = "23138010" + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewIATEntryDetail() + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("121042882") + entry.AddendaRecords = 007 + entry.DFIAccountNumber = "123456789" + entry.Amount = 100000 // 1000.00 + entry.SetTraceNumber("23138010", 1) + entry.Category = ach.CategoryForward + + //addenda + + addenda10 := ach.NewAddenda10() + addenda10.TransactionTypeCode = "ANN" + addenda10.ForeignPaymentAmount = 100000 + addenda10.ForeignTraceNumber = "928383-23938" + addenda10.Name = "BEK Enterprises" + addenda10.EntryDetailSequenceNumber = 00000001 + entry.Addenda10 = addenda10 + + addenda11 := ach.NewAddenda11() + addenda11.OriginatorName = "BEK Solutions" + addenda11.OriginatorStreetAddress = "15 West Place Street" + addenda11.EntryDetailSequenceNumber = 00000001 + entry.Addenda11 = addenda11 + + addenda12 := ach.NewAddenda12() + addenda12.OriginatorCityStateProvince = "JacobsTown*PA\\" + addenda12.OriginatorCountryPostalCode = "US*19305\\" + addenda12.EntryDetailSequenceNumber = 00000001 + entry.Addenda12 = addenda12 + + addenda13 := ach.NewAddenda13() + addenda13.ODFIName = "Wells Fargo" + addenda13.ODFIIDNumberQualifier = "01" + addenda13.ODFIIdentification = "231380104" + addenda13.ODFIBranchCountryCode = "US" + addenda13.EntryDetailSequenceNumber = 00000001 + entry.Addenda13 = addenda13 + + addenda14 := ach.NewAddenda14() + addenda14.RDFIName = "Citadel Bank" + addenda14.RDFIIDNumberQualifier = "01" + addenda14.RDFIIdentification = "121042882" + addenda14.RDFIBranchCountryCode = "CA" + addenda14.EntryDetailSequenceNumber = 00000001 + entry.Addenda14 = addenda14 + + addenda15 := ach.NewAddenda15() + addenda15.ReceiverIDNumber = "987465493213987" + addenda15.ReceiverStreetAddress = "2121 Front Street" + addenda15.EntryDetailSequenceNumber = 00000001 + entry.Addenda15 = addenda15 + + addenda16 := ach.NewAddenda16() + addenda16.ReceiverCityStateProvince = "LetterTown*AB\\" + addenda16.ReceiverCountryPostalCode = "CA*80014\\" + addenda16.EntryDetailSequenceNumber = 00000001 + entry.Addenda16 = addenda16 + + addenda17 := ach.NewAddenda17() + addenda17.PaymentRelatedInformation = "This is an international payment" + addenda17.SequenceNumber = 1 + addenda17.EntryDetailSequenceNumber = 0000001 + entry.AddAddenda17(addenda17) + + addenda18 := ach.NewAddenda18() + addenda18.ForeignCorrespondentBankName = "Bank of France" + addenda18.ForeignCorrespondentBankIDNumberQualifier = "01" + addenda18.ForeignCorrespondentBankIDNumber = "456456456987987" + addenda18.ForeignCorrespondentBankBranchCountryCode = "FR" + addenda18.SequenceNumber = 3 + addenda18.EntryDetailSequenceNumber = 0000001 + entry.AddAddenda18(addenda18) + + // Identifies the receiver's account information + // can be multiple entries per batch + entryTwo := ach.NewIATEntryDetail() + entryTwo.TransactionCode = ach.CheckingCredit + entryTwo.SetRDFI("121042882") + entryTwo.AddendaRecords = 007 + entryTwo.DFIAccountNumber = "123456789" + entryTwo.Amount = 100000 // 1000.00 + entryTwo.SetTraceNumber("23138010", 2) + entryTwo.Category = ach.CategoryForward + + //addenda + + addenda10Two := ach.NewAddenda10() + addenda10Two.TransactionTypeCode = "ANN" + addenda10Two.ForeignPaymentAmount = 100000 + addenda10Two.ForeignTraceNumber = "928383-23938" + addenda10Two.Name = "ADCAF Enterprises" + addenda10Two.EntryDetailSequenceNumber = 00000002 + entryTwo.Addenda10 = addenda10Two + + addenda11Two := ach.NewAddenda11() + addenda11Two.OriginatorName = "ADCAF Solutions" + addenda11Two.OriginatorStreetAddress = "15 West Place Street" + addenda11Two.EntryDetailSequenceNumber = 00000002 + entryTwo.Addenda11 = addenda11Two + + addenda12Two := ach.NewAddenda12() + addenda12Two.OriginatorCityStateProvince = "JacobsTown*PA\\" + addenda12Two.OriginatorCountryPostalCode = "US*19305\\" + addenda12Two.EntryDetailSequenceNumber = 00000002 + entryTwo.Addenda12 = addenda12Two + + addenda13Two := ach.NewAddenda13() + addenda13Two.ODFIName = "Wells Fargo" + addenda13Two.ODFIIDNumberQualifier = "01" + addenda13Two.ODFIIdentification = "231380104" + addenda13Two.ODFIBranchCountryCode = "US" + addenda13Two.EntryDetailSequenceNumber = 00000002 + entryTwo.Addenda13 = addenda13Two + + addenda14Two := ach.NewAddenda14() + addenda14Two.RDFIName = "Citadel Bank" + addenda14Two.RDFIIDNumberQualifier = "01" + addenda14Two.RDFIIdentification = "121042882" + addenda14Two.RDFIBranchCountryCode = "CA" + addenda14Two.EntryDetailSequenceNumber = 00000002 + entryTwo.Addenda14 = addenda14Two + + addenda15Two := ach.NewAddenda15() + addenda15Two.ReceiverIDNumber = "987465493213987" + addenda15Two.ReceiverStreetAddress = "18 Fifth Street" + addenda15Two.EntryDetailSequenceNumber = 00000002 + entryTwo.Addenda15 = addenda15Two + + addenda16Two := ach.NewAddenda16() + addenda16Two.ReceiverCityStateProvince = "LetterTown*AB\\" + addenda16Two.ReceiverCountryPostalCode = "CA*80014\\" + addenda16Two.EntryDetailSequenceNumber = 00000002 + entryTwo.Addenda16 = addenda16Two + + addenda17Two := ach.NewAddenda17() + addenda17Two.PaymentRelatedInformation = "This is an international payment" + addenda17Two.SequenceNumber = 1 + addenda17Two.EntryDetailSequenceNumber = 0000002 + entry.AddAddenda17(addenda17) + + addenda18Two := ach.NewAddenda18() + addenda18Two.ForeignCorrespondentBankName = "Bank of France" + addenda18Two.ForeignCorrespondentBankIDNumberQualifier = "01" + addenda18Two.ForeignCorrespondentBankIDNumber = "456456456987987" + addenda18Two.ForeignCorrespondentBankBranchCountryCode = "FR" + addenda18Two.SequenceNumber = 3 + addenda18Two.EntryDetailSequenceNumber = 0000002 + entryTwo.AddAddenda18(addenda18Two) + + // build the batch + batch := ach.NewIATBatch(bh) + batch.AddEntry(entry) + batch.AddEntry(entryTwo) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddIATBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-iat-write/main_test.go b/test/ach-iat-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-iat-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-keep-trace-number/main.go b/test/ach-keep-trace-number/main.go new file mode 100644 index 000000000..c973c84e9 --- /dev/null +++ b/test/ach-keep-trace-number/main.go @@ -0,0 +1,156 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // To create a file + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" + fh.ImmediateOrigin = "121042882" + fh.FileCreationDate = time.Now().Format("060102") + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + file := ach.NewFile() + file.SetHeader(fh) + + // To create a batch. + // Errors only if payment type is not supported. + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.MixedDebitsAndCredits + bh.CompanyName = "Your Company" + bh.CompanyIdentification = file.Header.ImmediateOrigin + bh.StandardEntryClassCode = ach.PPD + bh.CompanyEntryDescription = "Trans. Description" + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" + + batch, err := ach.NewBatch(bh) + if err != nil { + log.Fatalf("%T: %v", err, err) + } + batch.SetValidation(&ach.ValidateOpts{ + BypassOriginValidation: true, + }) + + // To create an entry + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "81967038518" + entry.Amount = 1000000 + entry.IndividualName = "Wade Arnold" + entry.SetTraceNumber("12345678", 1) + entry.IdentificationNumber = "ABC##jvkdjfuiwn" + entry.Category = ach.CategoryForward + entry.AddendaRecordIndicator = 1 + + // To add one or more optional addenda records for an entry + addenda := ach.NewAddenda05() + addenda.PaymentRelatedInformation = "bonus pay for amazing work on #OSS" + entry.AddAddenda05(addenda) + + // Entries are added to batches like so: + batch.AddEntry(entry) + + // When all of the Entries are added to the batch we must create it. + if err := batch.Create(); err != nil { + log.Fatalf("%T: %v", err, err) + } + + // And batches are added to files much the same way: + file.AddBatch(batch) + + // Now add a new batch for accepting payments on the web + bh2 := ach.NewBatchHeader() + bh2.ServiceClassCode = ach.CreditsOnly + bh2.CompanyName = "Your Company" + bh2.CompanyIdentification = file.Header.ImmediateOrigin + bh2.StandardEntryClassCode = ach.WEB + bh2.CompanyEntryDescription = "Subscribe" + bh2.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh2.ODFIIdentification = "121042882" + + batch2, err := ach.NewBatch(bh2) + if err != nil { + log.Fatalf("%T: %v", err, err) + } + batch2.SetValidation(&ach.ValidateOpts{ + BypassOriginValidation: true, + }) + + // Add an entry and define if it is a single or recurring payment + // The following is a recurring payment for $7.99 + entry2 := ach.NewEntryDetail() + entry2.TransactionCode = ach.CheckingCredit + entry2.SetRDFI("231380104") + entry2.DFIAccountNumber = "81967038518" + entry2.Amount = 799 + entry2.IndividualName = "Wade Arnold" + entry2.SetTraceNumber("87654321", 2) + entry2.IdentificationNumber = "#123456" + entry2.DiscretionaryData = "R" + entry2.Category = ach.CategoryForward + entry2.AddendaRecordIndicator = 1 + + // Add one or more optional addenda records for an entry + addenda2 := ach.NewAddenda05() + addenda2.PaymentRelatedInformation = "Monthly Membership Subscription" + entry2.AddAddenda05(addenda2) + + // Add the entry to the batch + batch2.AddEntry(entry2) + + // Create and add the second batch + if err := batch2.Create(); err != nil { + fmt.Printf("%T: %v", err, err) + } + file.AddBatch(batch2) + + // Once we've added all our batches we must create the file + if err := file.Create(); err != nil { + fmt.Printf("%T: %v", err, err) + } + // Check if the trace number was kept + batch1Entries := file.Batches[0].GetEntries() + + if batch1Entries[0].TraceNumber != "123456780000001" { + log.Fatal("TraceNumber was not kept " + batch1Entries[0].TraceNumber) + } + + batch2Entries := file.Batches[1].GetEntries() + + if batch2Entries[0].TraceNumber != "876543210000002" { + log.Fatal("TraceNumber was not kept " + batch2Entries[0].TraceNumber) + } + + // Finally we want to write the file to an io.Writer + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + fmt.Printf("%T: %v", err, err) + } + w.Flush() +} diff --git a/test/ach-keep-trace-number/main_test.go b/test/ach-keep-trace-number/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-keep-trace-number/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-mte-read/main.go b/test/ach-mte-read/main.go new file mode 100644 index 000000000..cc9787c0c --- /dev/null +++ b/test/ach-mte-read/main.go @@ -0,0 +1,58 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("mte-read.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount: %d\n", achFile.Batches[0].GetEntries()[0].Amount) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Println("Terminal:") + addenda02 := achFile.Batches[0].GetEntries()[0].Addenda02 + fmt.Printf(" IdentificationCode: %s\n", addenda02.TerminalIdentificationCode) + fmt.Printf(" Location: %s\n", addenda02.TerminalLocation) + fmt.Printf(" City: %s\n", addenda02.TerminalCity) + fmt.Printf(" State: %s\n", addenda02.TerminalState) + fmt.Printf(" TransactionSerialNumber: %s\n", addenda02.TransactionSerialNumber) + fmt.Printf(" TransactionDate: %s\n", addenda02.TransactionDate) +} diff --git a/test/ach-mte-read/main_test.go b/test/ach-mte-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-mte-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-mte-read/mte-read.ach b/test/ach-mte-read/mte-read.ach new file mode 100644 index 000000000..e30ac9bbb --- /dev/null +++ b/test/ach-mte-read/mte-read.ach @@ -0,0 +1,10 @@ +101 031300012 2313801041812180000A094101Federal Reserve Bank My Bank Name +5225Merchant with AT 231380104 MTECASH WITHD 181219 1231380100000001 +627031300012744-5678-99 0000010000031300010000001JANE DOE 1231380100000001 +702 2005091234561224 321 East Market Street ANYTOWN VA231380100000001 +82250000020003130001000000010000000000000000231380104 231380100000001 +9000001000001000000020003130001000000010000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/ach-mte-write/main.go b/test/ach-mte-write/main.go new file mode 100644 index 000000000..9714243ab --- /dev/null +++ b/test/ach-mte-write/main.go @@ -0,0 +1,97 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH MTE file acknowledging a credit + // Important: All financial institutions are different and will require registration and exact field values. + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "031300012" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "231380104" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Merchant with ATM" // Merchant with the ATM + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.MTE + bh.CompanyEntryDescription = "CASH WITHDRAW" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD // Date physical money was received + bh.ODFIIdentification = "23138010" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("031300012") // Receiver's bank transit routing number + entry.DFIAccountNumber = "744-5678-99" // Receiver's bank account number + entry.Amount = 10000 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetOriginalTraceNumber("031300010000001") + entry.SetReceivingCompany("JANE DOE") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + + addenda02 := ach.NewAddenda02() + // NACHA rules example: 200509*321 East Market Street*Anytown*VA\ + addenda02.TerminalIdentificationCode = "200509" + addenda02.TerminalLocation = "321 East Market Street" + addenda02.TerminalCity = "ANYTOWN" + addenda02.TerminalState = "VA" + + addenda02.TransactionSerialNumber = "123456" // Generated by Terminal, used for audits + addenda02.TransactionDate = "1224" + addenda02.TraceNumber = entry.TraceNumber + entry.Addenda02 = addenda02 + entry.AddendaRecordIndicator = 1 + + // build the batch + batch := ach.NewBatchMTE(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-mte-write/main_test.go b/test/ach-mte-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-mte-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-pop-read/main.go b/test/ach-pop-read/main.go new file mode 100644 index 000000000..af974df5a --- /dev/null +++ b/test/ach-pop-read/main.go @@ -0,0 +1,53 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("pop-debit.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("POP Check Serial Number: %s\n", achFile.Batches[0].GetEntries()[0].POPCheckSerialNumberField()) + fmt.Printf("POP Terminal City: %s\n", achFile.Batches[0].GetEntries()[0].POPTerminalCityField()) + fmt.Printf("POP Terminal State: %s\n", achFile.Batches[0].GetEntries()[0].POPTerminalStateField()) +} diff --git a/test/ach-pop-read/main_test.go b/test/ach-pop-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-pop-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-pop-read/pop-debit.ach b/test/ach-pop-read/pop-debit.ach new file mode 100644 index 000000000..3e817f577 --- /dev/null +++ b/test/ach-pop-read/pop-debit.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821806070000A094101Federal Reserve Bank My Bank Name +5225Originator 121042882 POPACH POP 180608 1121042880000001 +62723138010412345678 0000250500123456 PHILPAWade Arnold 0121042880000001 +82250000010023138010000000250500000000000000121042882 121042880000001 +9000001000001000000010023138010000000250500000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/ach-pop-write/main.go b/test/ach-pop-write/main.go new file mode 100644 index 000000000..be19b6ce7 --- /dev/null +++ b/test/ach-pop-write/main.go @@ -0,0 +1,84 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH POP file to debit a external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "121042882" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Originator" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.POP + bh.CompanyEntryDescription = "ACH POP" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingDebit // Code 27: Debit (withdrawal) from checking account + entry.SetRDFI("231380104") // Receiver's bank transit routing number + entry.DFIAccountNumber = "12345678" // Receiver's bank account number + entry.Amount = 250500 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.IndividualName = "Wade Arnold" // Identifies the receiver of the transaction + entry.SetPOPCheckSerialNumber("123456") + entry.SetPOPTerminalCity("PHIL") + entry.SetPOPTerminalState("PA") + + // build the batch + batch := ach.NewBatchPOP(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-pop-write/main_test.go b/test/ach-pop-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-pop-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-pos-read/main.go b/test/ach-pos-read/main.go new file mode 100644 index 000000000..b5cc8d703 --- /dev/null +++ b/test/ach-pos-read/main.go @@ -0,0 +1,52 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("pos-debit.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("POS Card Transaction Type: %s\n", achFile.Batches[0].GetEntries()[0].DiscretionaryDataField()) + fmt.Printf("POS Trace Number: %s\n", achFile.Batches[0].GetEntries()[0].TraceNumberField()) +} diff --git a/test/ach-pos-read/main_test.go b/test/ach-pos-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-pos-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-pos-read/pos-debit.ach b/test/ach-pos-read/pos-debit.ach new file mode 100644 index 000000000..b62960f0d --- /dev/null +++ b/test/ach-pos-read/pos-debit.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821806140000A094101Federal Reserve Bank My Bank Name +5225Name on Account 121042882 POSREG.SALARY 180615 1121042880000001 +62723138010412345678 0100000000 Receiver Account Name 011121042880000001 +702REFONEAREFTERM021000490614123456Target Store 0049 PHILADELPHIA PA121042880000001 +82250000020023138010000100000000000000000000121042882 121042880000001 +9000001000001000000020023138010000100000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/ach-pos-write/main.go b/test/ach-pos-write/main.go new file mode 100644 index 000000000..ff25f06cc --- /dev/null +++ b/test/ach-pos-write/main.go @@ -0,0 +1,98 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH POS file to send/credit a external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "121042882" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.POS + bh.CompanyEntryDescription = "Sale" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingDebit // Code 27: Debit (withdrawal) from checking account + entry.SetRDFI("231380104") // Receiver's bank transit routing number + entry.DFIAccountNumber = "12345678" // Receiver's bank account number + entry.Amount = 100000000 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.IndividualName = "Receiver Account Name" // Identifies the receiver of the transaction + entry.DiscretionaryData = "01" + entry.AddendaRecordIndicator = 1 + + addenda02 := ach.NewAddenda02() + addenda02.ReferenceInformationOne = "REFONEA" + addenda02.ReferenceInformationTwo = "REF" + addenda02.TerminalIdentificationCode = "TERM02" + addenda02.TransactionSerialNumber = "100049" + addenda02.TransactionDate = "0614" + addenda02.AuthorizationCodeOrExpireDate = "123456" + addenda02.TerminalLocation = "Target Store 0049" + addenda02.TerminalCity = "PHILADELPHIA" + addenda02.TerminalState = "PA" + addenda02.TraceNumber = "121042880000001" + + // build the batch + batch := ach.NewBatchPOS(bh) + batch.AddEntry(entry) + batch.GetEntries()[0].Addenda02 = addenda02 + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-pos-write/main_test.go b/test/ach-pos-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-pos-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-ppd-read/main.go b/test/ach-ppd-read/main.go new file mode 100644 index 000000000..6b41ec518 --- /dev/null +++ b/test/ach-ppd-read/main.go @@ -0,0 +1,74 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + fCredit, err := os.Open("ppd-credit.ach") + if err != nil { + log.Fatalln(err) + } + rCredit := ach.NewReader(fCredit) + achFileCredit, err := rCredit.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFileCredit.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFileCredit.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("File Name: %s\n\n", fCredit.Name()) + fmt.Printf("Total Credit Amount: %d\n", achFileCredit.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n\n", achFileCredit.Batches[0].GetHeader().StandardEntryClassCode) + + // Open a file for reading, any io.Reader can be used + fDebit, err := os.Open("ppd-debit.ach") + if err != nil { + log.Fatalln(err) + } + rDebit := ach.NewReader(fDebit) + achFileDebit, err := rDebit.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFileDebit.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFileDebit.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("File Name: %s\n\n", fDebit.Name()) + fmt.Printf("Total Debit Amount: %d\n", achFileDebit.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFileDebit.Batches[0].GetHeader().StandardEntryClassCode) +} diff --git a/test/ach-ppd-read/main_test.go b/test/ach-ppd-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-ppd-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-ppd-read/ppd-credit.ach b/test/ach-ppd-read/ppd-credit.ach new file mode 100644 index 000000000..0009e15b2 --- /dev/null +++ b/test/ach-ppd-read/ppd-credit.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821812060000A094101Federal Reserve Bank My Bank Name +5220Name on Account 121042882 PPDREG.SALARY 181207 1121042880000001 +62223138010412345678 0100000000 Receiver Account Name 0121042880000001 +82200000010023138010000000000000000100000000121042882 121042880000001 +9000001000001000000010023138010000000000000000100000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/ach-ppd-read/ppd-debit.ach b/test/ach-ppd-read/ppd-debit.ach new file mode 100644 index 000000000..9b86093fc --- /dev/null +++ b/test/ach-ppd-read/ppd-debit.ach @@ -0,0 +1,10 @@ +101 23138010401210428821906240000A094101Federal Reserve Bank My Bank Name +5225Name on Account 121042882 PPDREG.SALARY 190625 1121042880000001 +62723138010412345678 0100000000 Receiver Account Name 0121042880000001 +82250000010023138010000100000000000000000000121042882 121042880000001 +9000001000001000000010023138010000100000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/ach-ppd-write/main.go b/test/ach-ppd-write/main.go new file mode 100644 index 000000000..b5717c605 --- /dev/null +++ b/test/ach-ppd-write/main.go @@ -0,0 +1,83 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH PPD file to send/credit a external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "121042882" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.PPD + bh.CompanyEntryDescription = "REG.SALARY" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("231380104") // Receiver's bank transit routing number + entry.DFIAccountNumber = "12345678" // Receiver's bank account number + entry.Amount = 100000000 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.IndividualName = "Receiver Account Name" // Identifies the receiver of the transaction + + // build the batch + batch := ach.NewBatchPPD(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-ppd-write/main_test.go b/test/ach-ppd-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-ppd-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-rck-read/main.go b/test/ach-rck-read/main.go new file mode 100644 index 000000000..cd627b701 --- /dev/null +++ b/test/ach-rck-read/main.go @@ -0,0 +1,51 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("rck-debit.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Check Serial Number: %s\n", achFile.Batches[0].GetEntries()[0].CheckSerialNumberField()) +} diff --git a/test/ach-rck-read/main_test.go b/test/ach-rck-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-rck-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-rck-read/rck-debit.ach b/test/ach-rck-read/rck-debit.ach new file mode 100644 index 000000000..584bd9e89 --- /dev/null +++ b/test/ach-rck-read/rck-debit.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821805310000A094101Federal Reserve Bank My Bank Name +5225Name on Account 121042882 RCKREDEPCHECK 180601 1121042880000001 +62723138010412345678 0000002400123123123 Wade Arnold 0121042880000001 +82250000010023138010000000002400000000000000121042882 121042880000001 +9000001000001000000010023138010000000002400000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/ach-rck-write/main.go b/test/ach-rck-write/main.go new file mode 100644 index 000000000..31fb513dc --- /dev/null +++ b/test/ach-rck-write/main.go @@ -0,0 +1,82 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH RCK file to debit a external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "121042882" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.RCK + bh.CompanyEntryDescription = "REDEPCHECK" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingDebit // Code 27: Debit (withdrawal) from checking account + entry.SetRDFI("231380104") // Receiver's bank transit routing number + entry.DFIAccountNumber = "12345678" // Receiver's bank account number + entry.Amount = 2400 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.IndividualName = "Wade Arnold" // Identifies the receiver of the transaction + entry.SetCheckSerialNumber("123123123") + + // build the batch + batch := ach.NewBatchRCK(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-rck-write/main_test.go b/test/ach-rck-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-rck-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-segment-keep-trace-number/segment_test.go b/test/ach-segment-keep-trace-number/segment_test.go new file mode 100644 index 000000000..d7e166d67 --- /dev/null +++ b/test/ach-segment-keep-trace-number/segment_test.go @@ -0,0 +1,176 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "testing" + "time" + + "github.com/moov-io/ach" +) + +func TestSegmentKeepTraceNumber(t *testing.T) { + // To create a file + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" + fh.ImmediateOrigin = "121042882" + fh.FileCreationDate = time.Now().Format("060102") + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + file := ach.NewFile() + file.SetHeader(fh) + + // To create a batch. + // Errors only if payment type is not supported. + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.MixedDebitsAndCredits + bh.CompanyName = "Your Company" + bh.CompanyIdentification = file.Header.ImmediateOrigin + bh.StandardEntryClassCode = ach.PPD + bh.CompanyEntryDescription = "Trans. Description" + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" + + batch, err := ach.NewBatch(bh) + if err != nil { + t.Fatalf("%T: %v", err, err) + } + batch.SetValidation(&ach.ValidateOpts{ + BypassOriginValidation: true, + }) + + // To create an entry + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "81967038518" + entry.Amount = 1000000 + entry.IndividualName = "Wade Arnold" + entry.SetTraceNumber("12345678", 1) + entry.IdentificationNumber = "ABC##jvkdjfuiwn" + entry.Category = ach.CategoryForward + entry.AddendaRecordIndicator = 1 + + // Add one or more optional addenda records for an entry + addenda := ach.NewAddenda05() + addenda.PaymentRelatedInformation = "bonus pay for amazing work on #OSS" + entry.AddAddenda05(addenda) + + // Entries are added to batches like so: + batch.AddEntry(entry) + + // When all of the Entries are added to the batch we must create it. + if err := batch.Create(); err != nil { + t.Fatalf("%T: %v", err, err) + } + + // And batches are added to files much the same way: + file.AddBatch(batch) + + // Now add a new batch for accepting payments on the web + bh2 := ach.NewBatchHeader() + bh2.ServiceClassCode = ach.DebitsOnly + bh2.CompanyName = "Your Company" + bh2.CompanyIdentification = file.Header.ImmediateOrigin + bh2.StandardEntryClassCode = ach.WEB + bh2.CompanyEntryDescription = "Subscribe" + bh2.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh2.ODFIIdentification = "121042882" + + batch2, err := ach.NewBatch(bh2) + if err != nil { + t.Fatalf("%T: %v", err, err) + } + batch2.SetValidation(&ach.ValidateOpts{ + BypassOriginValidation: true, + }) + + // Add an entry and define if it is a single or recurring payment + // The following is a recurring payment for $7.99 + entry2 := ach.NewEntryDetail() + entry2.TransactionCode = ach.CheckingDebit + entry2.SetRDFI("231380104") + entry2.DFIAccountNumber = "81967038518" + entry2.Amount = 799 + entry2.IndividualName = "Wade Arnold" + entry2.SetTraceNumber("87654321", 2) + entry2.IdentificationNumber = "#123456" + entry2.DiscretionaryData = "R" + entry2.Category = ach.CategoryForward + entry2.AddendaRecordIndicator = 1 + + // To add one or more optional addenda records for an entry + addenda2 := ach.NewAddenda05() + addenda2.PaymentRelatedInformation = "Monthly Membership Subscription" + entry2.AddAddenda05(addenda2) + + // Add the entry to the batch + batch2.AddEntry(entry2) + + // Create and add the second batch + if err := batch2.Create(); err != nil { + t.Fatalf("%T: %v", err, err) + } + file.AddBatch(batch2) + + // Once we've added all our batches we must create the file + if err := file.Create(); err != nil { + t.Fatalf("%T: %v", err, err) + } + + creditFile, debitFile, err := file.SegmentFile(nil) + if err != nil { + t.Fatal(err) + } + + if entries := creditFile.Batches[0].GetEntries(); entries[0].TraceNumber != entry.TraceNumber { + t.Fatalf("TraceNumber before=%v after=%v", entry.TraceNumber, entries[0].TraceNumber) + } + if entries := debitFile.Batches[0].GetEntries(); entries[0].TraceNumber != entry2.TraceNumber { + t.Fatalf("TraceNumber before=%v after=%v", entry.TraceNumber, entries[0].TraceNumber) + } + + // Blank out TraceNumbers and let sequential ones be created + batch.SetValidation(nil) + entry.TraceNumber = "" + if err := batch.Create(); err != nil { + t.Fatal(err) + } + + entry2.TraceNumber = "" + batch2.SetValidation(nil) + if err := batch2.Create(); err != nil { + t.Fatal(err) + } + + if err := file.Create(); err != nil { + t.Fatalf("%T: %v", err, err) + } + + creditFile, debitFile, err = file.SegmentFile(nil) + if err != nil { + t.Fatalf("%T: %v", err, err) + } + + if entries := creditFile.Batches[0].GetEntries(); entries[0].TraceNumber == "" || entries[0].TraceNumber != "121042880000001" { + t.Fatalf("TraceNumber before=%v after=%v", entry.TraceNumber, entries[0].TraceNumber) + } + if entries := debitFile.Batches[0].GetEntries(); entries[0].TraceNumber == "" || entries[0].TraceNumber != "121042880000001" { + t.Fatalf("TraceNumber before=%v after=%v", entry.TraceNumber, entries[0].TraceNumber) + } +} diff --git a/test/ach-shr-read/main.go b/test/ach-shr-read/main.go new file mode 100644 index 000000000..45de6a214 --- /dev/null +++ b/test/ach-shr-read/main.go @@ -0,0 +1,53 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("shr-debit.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("SHR Card Expiration Date: %s\n", achFile.Batches[0].GetEntries()[0].SHRCardExpirationDateField()) + fmt.Printf("SHR Document Reference Number: %s\n", achFile.Batches[0].GetEntries()[0].SHRDocumentReferenceNumberField()) + fmt.Printf("SHR Individual Card Account Number: %s\n", achFile.Batches[0].GetEntries()[0].SHRIndividualCardAccountNumberField()) +} diff --git a/test/ach-shr-read/main_test.go b/test/ach-shr-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-shr-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-shr-read/shr-debit.ach b/test/ach-shr-read/shr-debit.ach new file mode 100644 index 000000000..3a2800b8a --- /dev/null +++ b/test/ach-shr-read/shr-debit.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821806190000A094101Federal Reserve Bank My Bank Name +5225Name on Account 121042882 SHRPayment 180620 1121042880000001 +62723138010412345678 01000000000722123456789100001234567891123456789011121042880000001 +702REFONEAREFTERM021000490614123456Target Store 0049 PHILADELPHIA PA121042880000001 +82250000020023138010000100000000000000000000121042882 121042880000001 +9000001000001000000020023138010000100000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/ach-shr-write/main.go b/test/ach-shr-write/main.go new file mode 100644 index 000000000..3d497ad1c --- /dev/null +++ b/test/ach-shr-write/main.go @@ -0,0 +1,100 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH SHR file to send/credit a external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "121042882" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.SHR + bh.CompanyEntryDescription = "Payment" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingDebit // Code 22: Debit (withdrawal) from checking account + entry.SetRDFI("231380104") // Receiver's bank transit routing number + entry.DFIAccountNumber = "12345678" // Receiver's bank account number + entry.Amount = 100000000 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.SetSHRCardExpirationDate("0722") + entry.SetSHRDocumentReferenceNumber("12345678910") + entry.SetSHRIndividualCardAccountNumber("1234567891123456789") + entry.DiscretionaryData = "01" + entry.AddendaRecordIndicator = 1 + + addenda02 := ach.NewAddenda02() + addenda02.ReferenceInformationOne = "REFONEA" + addenda02.ReferenceInformationTwo = "REF" + addenda02.TerminalIdentificationCode = "TERM02" + addenda02.TransactionSerialNumber = "100049" + addenda02.TransactionDate = "0614" + addenda02.AuthorizationCodeOrExpireDate = "123456" + addenda02.TerminalLocation = "Target Store 0049" + addenda02.TerminalCity = "PHILADELPHIA" + addenda02.TerminalState = "PA" + addenda02.TraceNumber = "121042880000001" + + // build the batch + batch := ach.NewBatchSHR(bh) + batch.AddEntry(entry) + batch.GetEntries()[0].Addenda02 = addenda02 + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-shr-write/main_test.go b/test/ach-shr-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-shr-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-tel-read/main.go b/test/ach-tel-read/main.go new file mode 100644 index 000000000..5945cffc9 --- /dev/null +++ b/test/ach-tel-read/main.go @@ -0,0 +1,50 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("tel-debit.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) +} diff --git a/test/ach-tel-read/main_test.go b/test/ach-tel-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-tel-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-tel-read/tel-debit.ach b/test/ach-tel-read/tel-debit.ach new file mode 100644 index 000000000..d7f8db66d --- /dev/null +++ b/test/ach-tel-read/tel-debit.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821812060000A094101Receiver Bank Name My Bank Name +5225Name on Account 121042882 TELPayment 181207 1121042880000001 +62723138010412345678 0000050000 Receiver Account Name 0121042880000001 +82250000010023138010000000050000000000000000121042882 121042880000001 +9000001000001000000010023138010000000050000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/ach-tel-write/main.go b/test/ach-tel-write/main.go new file mode 100644 index 000000000..d06e55dc2 --- /dev/null +++ b/test/ach-tel-write/main.go @@ -0,0 +1,83 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH TEL file to send/credit a external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "121042882" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Receiver Bank Name" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.TEL + bh.CompanyEntryDescription = "Payment" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingDebit + entry.SetRDFI("231380104") // Receiver's bank transit routing number + entry.DFIAccountNumber = "12345678" // Receiver's bank account number + entry.Amount = 50000 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.IndividualName = "Receiver Account Name" // Identifies the receiver of the transaction + + // build the batch + batch := ach.NewBatchTEL(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-tel-write/main_test.go b/test/ach-tel-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-tel-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-trc-read/main.go b/test/ach-trc-read/main.go new file mode 100644 index 000000000..a6502465e --- /dev/null +++ b/test/ach-trc-read/main.go @@ -0,0 +1,54 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("trc-debit.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Check Serial Number: %s\n", achFile.Batches[0].GetEntries()[0].CheckSerialNumberField()) + fmt.Printf("Process Control Field: %s\n", achFile.Batches[0].GetEntries()[0].ProcessControlField()) + fmt.Printf("Item Research Number: %s\n", achFile.Batches[0].GetEntries()[0].ItemResearchNumber()) + fmt.Printf("Item Type Indicator: %s\n", achFile.Batches[0].GetEntries()[0].ItemTypeIndicator()) +} diff --git a/test/ach-trc-read/main_test.go b/test/ach-trc-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-trc-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-trc-read/trc-debit.ach b/test/ach-trc-read/trc-debit.ach new file mode 100644 index 000000000..499aff41e --- /dev/null +++ b/test/ach-trc-read/trc-debit.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821811140000A094101Federal Reserve Bank My Bank Name +5225Payee Name 121042882 TRCACH TRC 181115 1121042880000001 +62723138010412345678 0000250000123879654 CHECK1182726 010121042880000001 +82250000010023138010000000250000000000000000121042882 121042880000001 +9000001000001000000010023138010000000250000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/ach-trc-write/main.go b/test/ach-trc-write/main.go new file mode 100644 index 000000000..fa44bbe8c --- /dev/null +++ b/test/ach-trc-write/main.go @@ -0,0 +1,84 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH TRC file to debit an external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "121042882" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Payee Name" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.TRC + bh.CompanyEntryDescription = "ACH TRC" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingDebit // Code 27: Debit (withdrawal) from checking account + entry.SetRDFI("231380104") // Receiver's bank transit routing number + entry.DFIAccountNumber = "12345678" // Receiver's bank account number + entry.Amount = 250000 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetCheckSerialNumber("123879654") + entry.SetProcessControlField("CHECK1") + entry.SetItemResearchNumber("182726") + entry.SetItemTypeIndicator("01") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + + // build the batch + batch := ach.NewBatchTRC(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-trc-write/main_test.go b/test/ach-trc-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-trc-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-trx-read/main.go b/test/ach-trx-read/main.go new file mode 100644 index 000000000..bd090a18a --- /dev/null +++ b/test/ach-trx-read/main.go @@ -0,0 +1,54 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("trx-debit.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("Total Amount Credit: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Item Type Indicator: %s\n", achFile.Batches[0].GetEntries()[0].ItemTypeIndicator()) + fmt.Printf("Addenda1: %s\n", achFile.Batches[0].GetEntries()[0].Addenda05[0].String()) + fmt.Printf("Addenda2: %s\n", achFile.Batches[0].GetEntries()[0].Addenda05[1].String()) +} diff --git a/test/ach-trx-read/main_test.go b/test/ach-trx-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-trx-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-trx-read/trx-debit.ach b/test/ach-trx-read/trx-debit.ach new file mode 100644 index 000000000..f6db9ac5a --- /dev/null +++ b/test/ach-trx-read/trx-debit.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821811140000A094101Federal Reserve Bank My Bank Name +5225Name on Account 121042882 TRXACH TRX 181115 1121042880000001 +62723138010412345678 000025000045689033 0002Receiver Company 011121042880000001 +705Debit First Account 00010000001 +705Debit Second Account 00020000001 +82250000030023138010000000250000000000000000121042882 121042880000001 +9000001000001000000030023138010000000250000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/ach-trx-write/main.go b/test/ach-trx-write/main.go new file mode 100644 index 000000000..c665a6de1 --- /dev/null +++ b/test/ach-trx-write/main.go @@ -0,0 +1,99 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH TRX file to send/credit a external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "121042882" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.TRX + bh.CompanyEntryDescription = "ACH TRX" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingDebit // Code 27: Debit checking account + entry.SetRDFI("231380104") // Receiver's bank transit routing number + entry.DFIAccountNumber = "12345678" // Receiver's bank account number + entry.Amount = 250000 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(2) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.SetItemTypeIndicator("01") + + addenda1 := ach.NewAddenda05() + addenda1.PaymentRelatedInformation = "Debit First Account" + addenda1.SequenceNumber = 1 + addenda1.EntryDetailSequenceNumber = 0000001 + + addenda2 := ach.NewAddenda05() + addenda2.PaymentRelatedInformation = "Debit Second Account" + addenda2.SequenceNumber = 2 + addenda2.EntryDetailSequenceNumber = 0000001 + + // build the batch + batch := ach.NewBatchTRX(bh) + batch.AddEntry(entry) + batch.Entries[0].AddendaRecordIndicator = 1 + batch.GetEntries()[0].AddAddenda05(addenda1) + batch.GetEntries()[0].AddAddenda05(addenda2) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-trx-write/main_test.go b/test/ach-trx-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-trx-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-web-read/main.go b/test/ach-web-read/main.go new file mode 100644 index 000000000..5133505c9 --- /dev/null +++ b/test/ach-web-read/main.go @@ -0,0 +1,66 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("web-credit.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Credit: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Entry One : %s\n", achFile.Batches[0].GetEntries()[0]) + fmt.Printf("Entry One Addenda Record Indicator: %d\n", achFile.Batches[0].GetEntries()[0].AddendaRecordIndicator) + fmt.Printf("Entry One Addenda: %s\n", achFile.Batches[0].GetEntries()[0].Addenda05[0]) + eadOne := new(ach.Addenda05) + EntryOneAddenda := achFile.Batches[0].GetEntries()[0].Addenda05[0].String() + eadOne.Parse(EntryOneAddenda) + fmt.Printf("Entry One Addenda Type Code: %s\n", string(eadOne.TypeCode)) + fmt.Printf("Entry One Addenda Payment Related Information: %s\n", eadOne.PaymentRelatedInformation) + fmt.Printf("Entry Two: %s\n", achFile.Batches[0].GetEntries()[1]) + fmt.Printf("Entry Two Addenda Record Indicator: %d\n", achFile.Batches[0].GetEntries()[1].AddendaRecordIndicator) + fmt.Printf("Entry Two Addenda: %s\n", achFile.Batches[0].GetEntries()[1].Addenda05[0]) + eadTwo := new(ach.Addenda05) + EntryTwoAddenda := achFile.Batches[0].GetEntries()[1].Addenda05[0].String() + eadTwo.Parse(EntryTwoAddenda) + fmt.Printf("Entry Two Addenda Payment Related Information: %s\n", eadTwo.PaymentRelatedInformation) + fmt.Printf("Entry One Addenda Type Code: %s\n", string(eadTwo.TypeCode)) +} diff --git a/test/ach-web-read/main_test.go b/test/ach-web-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-web-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-web-read/web-credit.ach b/test/ach-web-read/web-credit.ach new file mode 100644 index 000000000..95761809a --- /dev/null +++ b/test/ach-web-read/web-credit.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821810110000A094101Federal Reserve Bank My Bank Name +5220Name on Account 121042882 WEBSubscribe 181012 1121042880000001 +62223138010412345678 0000010000#789654 Wade Arnold S 1121042880000001 +705PAY-GATE payment 00010000001 +62223138010481967038518 0000000799#123456 Wade Arnold R 1121042880000002 +705Monthly Membership Subscription 00010000002 +82200000040046276020000000000000000000010799121042882 121042880000001 +9000001000001000000040046276020000000000000000000010799 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/ach-web-write/main.go b/test/ach-web-write/main.go new file mode 100644 index 000000000..7e24bd86c --- /dev/null +++ b/test/ach-web-write/main.go @@ -0,0 +1,107 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH WEB file to send/credit a external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "121042882" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.CreditsOnly + bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.WEB + bh.CompanyEntryDescription = "Subscribe" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" // Originating Routing Number + + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "12345678" + entry.Amount = 10000 // 100.00 + entry.IndividualName = "Wade Arnold" + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.IdentificationNumber = "#789654" + entry.DiscretionaryData = "S" + entry.Category = ach.CategoryForward + entry.AddendaRecordIndicator = 1 + + // To add one or more optional addenda records for an entry + addenda1 := ach.NewAddenda05() + addenda1.PaymentRelatedInformation = "PAY-GATE payment" + entry.AddAddenda05(addenda1) + + entry2 := ach.NewEntryDetail() + entry2.TransactionCode = ach.CheckingCredit + entry2.SetRDFI("231380104") + entry2.DFIAccountNumber = "81967038518" + entry2.Amount = 799 // 7.99 + entry2.IndividualName = "Wade Arnold" + entry2.SetTraceNumber(bh.ODFIIdentification, 2) + entry2.IdentificationNumber = "#123456" + entry2.DiscretionaryData = "R" + entry2.Category = ach.CategoryForward + entry2.AddendaRecordIndicator = 1 + + // To add one or more optional addenda records for an entry + addenda2 := ach.NewAddenda05() + addenda2.PaymentRelatedInformation = "Monthly Membership Subscription" + entry2.AddAddenda05(addenda2) + + // build the batch + batch := ach.NewBatchWEB(bh) + batch.AddEntry(entry) + batch.AddEntry(entry2) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-web-write/main_test.go b/test/ach-web-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-web-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-xck-read/main.go b/test/ach-xck-read/main.go new file mode 100644 index 000000000..057477583 --- /dev/null +++ b/test/ach-xck-read/main.go @@ -0,0 +1,54 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("xck-debit.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("Total Amount Debit: %d\n", achFile.Control.TotalDebitEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Check Serial Number: %s\n", achFile.Batches[0].GetEntries()[0].CheckSerialNumberField()) + fmt.Printf("Process Control Field: %s\n", achFile.Batches[0].GetEntries()[0].ProcessControlField()) + fmt.Printf("Item Research Number: %s\n", achFile.Batches[0].GetEntries()[0].ItemResearchNumber()) + fmt.Printf("Discretionary Data: %s\n", achFile.Batches[0].GetEntries()[0].DiscretionaryData) +} diff --git a/test/ach-xck-read/main_test.go b/test/ach-xck-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-xck-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-xck-read/xck-debit.ach b/test/ach-xck-read/xck-debit.ach new file mode 100644 index 000000000..bdf6989fb --- /dev/null +++ b/test/ach-xck-read/xck-debit.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821811140000A094101Federal Reserve Bank My Bank Name +5225Payee Name 121042882 XCKACH XCK 181115 1121042880000001 +62723138010412345678 0000250000123879654 CHECK1182726 0121042880000001 +82250000010023138010000000250000000000000000121042882 121042880000001 +9000001000001000000010023138010000000250000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/ach-xck-write/main.go b/test/ach-xck-write/main.go new file mode 100644 index 000000000..2eea61fa6 --- /dev/null +++ b/test/ach-xck-write/main.go @@ -0,0 +1,83 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH XCK file to debit an external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "121042882" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.DebitsOnly + bh.CompanyName = "Payee Name" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.XCK // Consumer destination vs Company CCD + bh.CompanyEntryDescription = "ACH XCK" // will be on receiving account's statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingDebit // Code 27: Debit (withdrawal) from checking account + entry.SetRDFI("231380104") // Receiver's bank transit routing number + entry.DFIAccountNumber = "12345678" // Receiver's bank account number + entry.Amount = 250000 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetCheckSerialNumber("123879654") + entry.SetProcessControlField("CHECK1") + entry.SetItemResearchNumber("182726") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + + // build the batch + batch := ach.NewBatchXCK(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-xck-write/main_test.go b/test/ach-xck-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/ach-xck-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/fuzz-reader/README.md b/test/fuzz-reader/README.md new file mode 100644 index 000000000..354ce4e57 --- /dev/null +++ b/test/fuzz-reader/README.md @@ -0,0 +1,57 @@ +## reader fuzzing + +Fuzzing is a technique for sending arbitrary input into functions to see what happens. Typically this is done to weed out crashers/panics from software, or to detect bugs. In some cases all possible inputs are ran into the program (i.e. if a function accepts 16-bit integers it's trivial to try them all). + +In this directory we are fuzzing `reader.go` from the root level -- In specific the `Reader.Read()` method. + +### Running + +If you need to setup `go-fuzz`, run `make install`. + +Otherwise, run `make` and watch the output. Fix any crashes that happen, thanks! + +See the `go-fuzz` project for more docs: https://github.com/dvyukov/go-fuzz + +### Corpus + +Right now our corpus exists mostly of test files. As a machine runs go-fuzz files are written to the `corpus/` directory. + +To load all test files we read run: `ln -s ../../../test/ach-*-read/*.ach .` from the `corpus/` directory. + +### Downloading crashers from Kubernetes cluster + +If the [`moov/achfuzz`](https://hub.docker.com/r/moov/achfuzz) Docker image is running in a Kubernetes cluster you can download the crashers from a mounted volume by executing the following commands. + +``` +# Get current pod name +$ kubectl get pods -n apps | grep achfuzz +achfuzz-6bbdc574f5-pl2zm 1/1 Running 0 1h +``` + +Then using the [volume's mount path](https://github.com/moov-io/infra/blob/master/lib/apps/10-achfuzz.yml#L43) select any crasher files. + +``` +$ kubectl exec -n apps achfuzz-6bbdc574f5-pl2zm -- ls -la /go/src/github.com/moov-io/ach/test/fuzz-reader/crashers/ +total 28 +drwxr-xr-x 3 root root 4096 Jan 30 00:26 . +drwxr-xr-x 1 root root 4096 Jan 14 17:30 .. + +# Download files, replace with a crasher file +$ kubectl cp 'apps/achfuzz-6bbdc574f5-pl2zm:/go/src/github.com/moov-io/ach/test/fuzz-reader/crashers/' ./ +``` + +### fuzzit integration + +[fuzzit](https://fuzzit.dev/) is a free SaaS for automated fuzzing. They offer free fuzzing for OSS projects so we've setup achfuzz for their service. After creating a target in the web UI we copied our corpus up (`tar cf achfuzz.tar *.ach` in `test/fuzz-reader/corpus/` then `gzip achfuzz.tar`). + +We need to then copy down their bash script (`fuzzit completion > fuzzit.sh && chmod +x ./fuzzit.sh`) and create our job: + +``` +# In test/fuzz-reader/ +$ fuzzit create job --type=fuzzing achfuzz fuzzit.sh +2019/08/19 10:38:59 Creating job... +2019/08/19 10:38:59 Uploading fuzzer... +2019/08/19 10:39:01 Starting job +2019/08/19 10:39:02 Job p55FXmV2MsHSsBsNLGrT started succesfully +2019/08/19 10:39:02 Job created successfully +``` diff --git a/test/fuzz-reader/corpus/20110729A.ach b/test/fuzz-reader/corpus/20110729A.ach new file mode 120000 index 000000000..27f5a8abd --- /dev/null +++ b/test/fuzz-reader/corpus/20110729A.ach @@ -0,0 +1 @@ +../../../test/testdata/20110729A.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/20110805A.ach b/test/fuzz-reader/corpus/20110805A.ach new file mode 120000 index 000000000..f1b48c5b2 --- /dev/null +++ b/test/fuzz-reader/corpus/20110805A.ach @@ -0,0 +1 @@ +../../../test/testdata/20110805A.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/20180713-IAT.ach b/test/fuzz-reader/corpus/20180713-IAT.ach new file mode 120000 index 000000000..33d34eedb --- /dev/null +++ b/test/fuzz-reader/corpus/20180713-IAT.ach @@ -0,0 +1 @@ +../../../test/testdata/20180713-IAT.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/20180716-IAT-A17-A18.ach b/test/fuzz-reader/corpus/20180716-IAT-A17-A18.ach new file mode 120000 index 000000000..18d239a16 --- /dev/null +++ b/test/fuzz-reader/corpus/20180716-IAT-A17-A18.ach @@ -0,0 +1 @@ +../../../test/testdata/20180716-IAT-A17-A18.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/20180716-IAT-A17.ach b/test/fuzz-reader/corpus/20180716-IAT-A17.ach new file mode 120000 index 000000000..337a77ef0 --- /dev/null +++ b/test/fuzz-reader/corpus/20180716-IAT-A17.ach @@ -0,0 +1 @@ +../../../test/testdata/20180716-IAT-A17.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/IAT-BatchHeaderErr.ach b/test/fuzz-reader/corpus/IAT-BatchHeaderErr.ach new file mode 120000 index 000000000..2461d6ef1 --- /dev/null +++ b/test/fuzz-reader/corpus/IAT-BatchHeaderErr.ach @@ -0,0 +1 @@ +../../../test/testdata/iat-batchHeaderErr.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/IAT-InvalidAddenda10.ach b/test/fuzz-reader/corpus/IAT-InvalidAddenda10.ach new file mode 120000 index 000000000..98d9cc323 --- /dev/null +++ b/test/fuzz-reader/corpus/IAT-InvalidAddenda10.ach @@ -0,0 +1 @@ +../../../test/testdata/iat-invalidAddenda10.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/IAT-InvalidBatchControl.ach b/test/fuzz-reader/corpus/IAT-InvalidBatchControl.ach new file mode 120000 index 000000000..bb6401240 --- /dev/null +++ b/test/fuzz-reader/corpus/IAT-InvalidBatchControl.ach @@ -0,0 +1 @@ +../../../test/testdata/iat-invalidBatchControl.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/IAT-InvalidBatchHeader.ach b/test/fuzz-reader/corpus/IAT-InvalidBatchHeader.ach new file mode 120000 index 000000000..d6c7e7796 --- /dev/null +++ b/test/fuzz-reader/corpus/IAT-InvalidBatchHeader.ach @@ -0,0 +1 @@ +../../../test/testdata/iat-invalidBatchHeader.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/IAT-InvalidEntryDetail.ach b/test/fuzz-reader/corpus/IAT-InvalidEntryDetail.ach new file mode 120000 index 000000000..38d71d0dd --- /dev/null +++ b/test/fuzz-reader/corpus/IAT-InvalidEntryDetail.ach @@ -0,0 +1 @@ +../../../test/testdata/iat-invalidEntryDetail.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/Iat-invalidAddenda13.ach b/test/fuzz-reader/corpus/Iat-invalidAddenda13.ach new file mode 120000 index 000000000..e24ab408b --- /dev/null +++ b/test/fuzz-reader/corpus/Iat-invalidAddenda13.ach @@ -0,0 +1 @@ +../../../test/testdata/Iat-invalidAddenda13.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/ack-read.ach b/test/fuzz-reader/corpus/ack-read.ach new file mode 120000 index 000000000..2c2618220 --- /dev/null +++ b/test/fuzz-reader/corpus/ack-read.ach @@ -0,0 +1 @@ +../../../test/ach-ack-read/ack-read.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/adv-invalidBatchEntries.ach b/test/fuzz-reader/corpus/adv-invalidBatchEntries.ach new file mode 120000 index 000000000..fb5eb6422 --- /dev/null +++ b/test/fuzz-reader/corpus/adv-invalidBatchEntries.ach @@ -0,0 +1 @@ +../../../test/testdata/adv-invalidBatchEntries.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/adv-invalidFileControl.ach b/test/fuzz-reader/corpus/adv-invalidFileControl.ach new file mode 120000 index 000000000..2186c85ba --- /dev/null +++ b/test/fuzz-reader/corpus/adv-invalidFileControl.ach @@ -0,0 +1 @@ +../../../test/testdata/adv-invalidFileControl.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/adv-noFileControl.ach b/test/fuzz-reader/corpus/adv-noFileControl.ach new file mode 120000 index 000000000..2c60ef1ac --- /dev/null +++ b/test/fuzz-reader/corpus/adv-noFileControl.ach @@ -0,0 +1 @@ +../../../test/testdata/adv-noFileControl.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/adv-read.ach b/test/fuzz-reader/corpus/adv-read.ach new file mode 120000 index 000000000..6f20501eb --- /dev/null +++ b/test/fuzz-reader/corpus/adv-read.ach @@ -0,0 +1 @@ +../../../test/ach-adv-read/adv-read.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/arc-debit.ach b/test/fuzz-reader/corpus/arc-debit.ach new file mode 120000 index 000000000..4ab47a756 --- /dev/null +++ b/test/fuzz-reader/corpus/arc-debit.ach @@ -0,0 +1 @@ +../../../test/ach-arc-read/arc-debit.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/atx-read.ach b/test/fuzz-reader/corpus/atx-read.ach new file mode 120000 index 000000000..a09fd9c8b --- /dev/null +++ b/test/fuzz-reader/corpus/atx-read.ach @@ -0,0 +1 @@ +../../../test/ach-atx-read/atx-read.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/boc-debit.ach b/test/fuzz-reader/corpus/boc-debit.ach new file mode 120000 index 000000000..33f5b40ea --- /dev/null +++ b/test/fuzz-reader/corpus/boc-debit.ach @@ -0,0 +1 @@ +../../../test/ach-boc-read/boc-debit.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/ccd-debit.ach b/test/fuzz-reader/corpus/ccd-debit.ach new file mode 120000 index 000000000..0ae1fd80f --- /dev/null +++ b/test/fuzz-reader/corpus/ccd-debit.ach @@ -0,0 +1 @@ +../../../test/ach-ccd-read/ccd-debit.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/cie-credit.ach b/test/fuzz-reader/corpus/cie-credit.ach new file mode 120000 index 000000000..e5045123a --- /dev/null +++ b/test/fuzz-reader/corpus/cie-credit.ach @@ -0,0 +1 @@ +../../../test/ach-cie-read/cie-credit.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/ctx-debit.ach b/test/fuzz-reader/corpus/ctx-debit.ach new file mode 120000 index 000000000..fcc313380 --- /dev/null +++ b/test/fuzz-reader/corpus/ctx-debit.ach @@ -0,0 +1 @@ +../../../test/ach-ctx-read/ctx-debit.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/dne-read.ach b/test/fuzz-reader/corpus/dne-read.ach new file mode 120000 index 000000000..928131aac --- /dev/null +++ b/test/fuzz-reader/corpus/dne-read.ach @@ -0,0 +1 @@ +../../../test/ach-dne-read/dne-read.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/enr-read.ach b/test/fuzz-reader/corpus/enr-read.ach new file mode 120000 index 000000000..eb5492390 --- /dev/null +++ b/test/fuzz-reader/corpus/enr-read.ach @@ -0,0 +1 @@ +../../../test/ach-enr-read/enr-read.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/iat-credit.ach b/test/fuzz-reader/corpus/iat-credit.ach new file mode 120000 index 000000000..4840dd695 --- /dev/null +++ b/test/fuzz-reader/corpus/iat-credit.ach @@ -0,0 +1 @@ +../../../test/ach-iat-read/iat-credit.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/iat-invalidAddenda11.ach b/test/fuzz-reader/corpus/iat-invalidAddenda11.ach new file mode 120000 index 000000000..11db95096 --- /dev/null +++ b/test/fuzz-reader/corpus/iat-invalidAddenda11.ach @@ -0,0 +1 @@ +../../../test/testdata/iat-invalidAddenda11.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/iat-invalidAddenda12.ach b/test/fuzz-reader/corpus/iat-invalidAddenda12.ach new file mode 120000 index 000000000..96a49eab4 --- /dev/null +++ b/test/fuzz-reader/corpus/iat-invalidAddenda12.ach @@ -0,0 +1 @@ +../../../test/testdata/iat-invalidAddenda12.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/iat-invalidAddenda14.ach b/test/fuzz-reader/corpus/iat-invalidAddenda14.ach new file mode 120000 index 000000000..1e0acc281 --- /dev/null +++ b/test/fuzz-reader/corpus/iat-invalidAddenda14.ach @@ -0,0 +1 @@ +../../../test/testdata/iat-invalidAddenda14.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/iat-invalidAddenda15.ach b/test/fuzz-reader/corpus/iat-invalidAddenda15.ach new file mode 120000 index 000000000..428be3b9c --- /dev/null +++ b/test/fuzz-reader/corpus/iat-invalidAddenda15.ach @@ -0,0 +1 @@ +../../../test/testdata/iat-invalidAddenda15.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/iat-invalidAddenda16.ach b/test/fuzz-reader/corpus/iat-invalidAddenda16.ach new file mode 120000 index 000000000..5a6bcd5bf --- /dev/null +++ b/test/fuzz-reader/corpus/iat-invalidAddenda16.ach @@ -0,0 +1 @@ +../../../test/testdata/iat-invalidAddenda16.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/iat-invalidAddenda17.ach b/test/fuzz-reader/corpus/iat-invalidAddenda17.ach new file mode 120000 index 000000000..b50e4c886 --- /dev/null +++ b/test/fuzz-reader/corpus/iat-invalidAddenda17.ach @@ -0,0 +1 @@ +../../../test/testdata/iat-invalidAddenda17.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/iat-invalidAddenda18.ach b/test/fuzz-reader/corpus/iat-invalidAddenda18.ach new file mode 120000 index 000000000..24efd8632 --- /dev/null +++ b/test/fuzz-reader/corpus/iat-invalidAddenda18.ach @@ -0,0 +1 @@ +../../../test/testdata/iat-invalidAddenda18.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/iat-invalidAddenda98.ach b/test/fuzz-reader/corpus/iat-invalidAddenda98.ach new file mode 120000 index 000000000..d675aaf50 --- /dev/null +++ b/test/fuzz-reader/corpus/iat-invalidAddenda98.ach @@ -0,0 +1 @@ +../../../test/testdata/iat-invalidAddenda98.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/iat-invalidAddenda99.ach b/test/fuzz-reader/corpus/iat-invalidAddenda99.ach new file mode 120000 index 000000000..2b52cbcb5 --- /dev/null +++ b/test/fuzz-reader/corpus/iat-invalidAddenda99.ach @@ -0,0 +1 @@ +../../../test/testdata/iat-invalidAddenda99.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/iat-invalidAddendaRecordIndicator.ach b/test/fuzz-reader/corpus/iat-invalidAddendaRecordIndicator.ach new file mode 120000 index 000000000..4cde09244 --- /dev/null +++ b/test/fuzz-reader/corpus/iat-invalidAddendaRecordIndicator.ach @@ -0,0 +1 @@ +../../../test/testdata/iat-invalidAddendaRecordIndicator.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/mte-read.ach b/test/fuzz-reader/corpus/mte-read.ach new file mode 120000 index 000000000..249de9223 --- /dev/null +++ b/test/fuzz-reader/corpus/mte-read.ach @@ -0,0 +1 @@ +../../../test/ach-mte-read/mte-read.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/pop-debit.ach b/test/fuzz-reader/corpus/pop-debit.ach new file mode 120000 index 000000000..ff1ca7ab5 --- /dev/null +++ b/test/fuzz-reader/corpus/pop-debit.ach @@ -0,0 +1 @@ +../../../test/ach-pop-read/pop-debit.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/pos-debit.ach b/test/fuzz-reader/corpus/pos-debit.ach new file mode 120000 index 000000000..8b73f3c67 --- /dev/null +++ b/test/fuzz-reader/corpus/pos-debit.ach @@ -0,0 +1 @@ +../../../test/ach-pos-read/pos-debit.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/pos-invalidEntryDetail.ach b/test/fuzz-reader/corpus/pos-invalidEntryDetail.ach new file mode 120000 index 000000000..83ff03b56 --- /dev/null +++ b/test/fuzz-reader/corpus/pos-invalidEntryDetail.ach @@ -0,0 +1 @@ +../../../test/testdata/pos-invalidEntryDetail.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/pos-invalidReturnFile.ach b/test/fuzz-reader/corpus/pos-invalidReturnFile.ach new file mode 120000 index 000000000..ab140d8a6 --- /dev/null +++ b/test/fuzz-reader/corpus/pos-invalidReturnFile.ach @@ -0,0 +1 @@ +../../../test/testdata/pos-invalidReturnFile.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/ppd-credit.ach b/test/fuzz-reader/corpus/ppd-credit.ach new file mode 120000 index 000000000..76c6b425d --- /dev/null +++ b/test/fuzz-reader/corpus/ppd-credit.ach @@ -0,0 +1 @@ +../../../test/ach-ppd-read/ppd-credit.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/ppd-debit-fixedLength.ach b/test/fuzz-reader/corpus/ppd-debit-fixedLength.ach new file mode 120000 index 000000000..e2aa9d504 --- /dev/null +++ b/test/fuzz-reader/corpus/ppd-debit-fixedLength.ach @@ -0,0 +1 @@ +../../../test/testdata/ppd-debit-fixedLength.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/ppd-debit.ach b/test/fuzz-reader/corpus/ppd-debit.ach new file mode 120000 index 000000000..95c00ed84 --- /dev/null +++ b/test/fuzz-reader/corpus/ppd-debit.ach @@ -0,0 +1 @@ +../../../test/ach-ppd-read/ppd-debit.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/rck-debit.ach b/test/fuzz-reader/corpus/rck-debit.ach new file mode 120000 index 000000000..bedef20b6 --- /dev/null +++ b/test/fuzz-reader/corpus/rck-debit.ach @@ -0,0 +1 @@ +../../../test/ach-rck-read/rck-debit.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/rck.ach b/test/fuzz-reader/corpus/rck.ach new file mode 120000 index 000000000..fd013e920 --- /dev/null +++ b/test/fuzz-reader/corpus/rck.ach @@ -0,0 +1 @@ +../../../test/testdata/rck.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/return-WEB.ach b/test/fuzz-reader/corpus/return-WEB.ach new file mode 120000 index 000000000..6edb8b876 --- /dev/null +++ b/test/fuzz-reader/corpus/return-WEB.ach @@ -0,0 +1 @@ +../../../test/testdata/return-WEB.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/shr-debit.ach b/test/fuzz-reader/corpus/shr-debit.ach new file mode 120000 index 000000000..8718c1bac --- /dev/null +++ b/test/fuzz-reader/corpus/shr-debit.ach @@ -0,0 +1 @@ +../../../test/ach-shr-read/shr-debit.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/tel-debit.ach b/test/fuzz-reader/corpus/tel-debit.ach new file mode 120000 index 000000000..d9c483227 --- /dev/null +++ b/test/fuzz-reader/corpus/tel-debit.ach @@ -0,0 +1 @@ +../../../test/ach-tel-read/tel-debit.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/trc-debit.ach b/test/fuzz-reader/corpus/trc-debit.ach new file mode 120000 index 000000000..4873a182e --- /dev/null +++ b/test/fuzz-reader/corpus/trc-debit.ach @@ -0,0 +1 @@ +../../../test/ach-trc-read/trc-debit.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/trx-debit.ach b/test/fuzz-reader/corpus/trx-debit.ach new file mode 120000 index 000000000..22b9ed4e8 --- /dev/null +++ b/test/fuzz-reader/corpus/trx-debit.ach @@ -0,0 +1 @@ +../../../test/ach-trx-read/trx-debit.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/web-credit.ach b/test/fuzz-reader/corpus/web-credit.ach new file mode 120000 index 000000000..f43fbd60b --- /dev/null +++ b/test/fuzz-reader/corpus/web-credit.ach @@ -0,0 +1 @@ +../../../test/ach-web-read/web-credit.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/web-debit.ach b/test/fuzz-reader/corpus/web-debit.ach new file mode 120000 index 000000000..5cffc296f --- /dev/null +++ b/test/fuzz-reader/corpus/web-debit.ach @@ -0,0 +1 @@ +../../../test/testdata/web-debit.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/web-invalidNOCFile.ach b/test/fuzz-reader/corpus/web-invalidNOCFile.ach new file mode 120000 index 000000000..ea04838b0 --- /dev/null +++ b/test/fuzz-reader/corpus/web-invalidNOCFile.ach @@ -0,0 +1 @@ +../../../test/testdata/web-invalidNOCFile.ach \ No newline at end of file diff --git a/test/fuzz-reader/corpus/xck-debit.ach b/test/fuzz-reader/corpus/xck-debit.ach new file mode 120000 index 000000000..36f6720d2 --- /dev/null +++ b/test/fuzz-reader/corpus/xck-debit.ach @@ -0,0 +1 @@ +../../../test/ach-xck-read/xck-debit.ach \ No newline at end of file diff --git a/test/fuzz-reader/fuzzit.sh b/test/fuzz-reader/fuzzit.sh new file mode 100644 index 000000000..4a90d7049 --- /dev/null +++ b/test/fuzz-reader/fuzzit.sh @@ -0,0 +1,493 @@ +# bash completion for fuzzit -*- shell-script -*- + +__fuzzit_debug() +{ + if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then + echo "$*" >> "${BASH_COMP_DEBUG_FILE}" + fi +} + +# Homebrew on Macs have version 1.3 of bash-completion which doesn't include +# _init_completion. This is a very minimal version of that function. +__fuzzit_init_completion() +{ + COMPREPLY=() + _get_comp_words_by_ref "$@" cur prev words cword +} + +__fuzzit_index_of_word() +{ + local w word=$1 + shift + index=0 + for w in "$@"; do + [[ $w = "$word" ]] && return + index=$((index+1)) + done + index=-1 +} + +__fuzzit_contains_word() +{ + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done + return 1 +} + +__fuzzit_handle_reply() +{ + __fuzzit_debug "${FUNCNAME[0]}" + case $cur in + -*) + if [[ $(type -t compopt) = "builtin" ]]; then + compopt -o nospace + fi + local allflags + if [ ${#must_have_one_flag[@]} -ne 0 ]; then + allflags=("${must_have_one_flag[@]}") + else + allflags=("${flags[*]} ${two_word_flags[*]}") + fi + COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") ) + if [[ $(type -t compopt) = "builtin" ]]; then + [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace + fi + + # complete after --flag=abc + if [[ $cur == *=* ]]; then + if [[ $(type -t compopt) = "builtin" ]]; then + compopt +o nospace + fi + + local index flag + flag="${cur%=*}" + __fuzzit_index_of_word "${flag}" "${flags_with_completion[@]}" + COMPREPLY=() + if [[ ${index} -ge 0 ]]; then + PREFIX="" + cur="${cur#*=}" + ${flags_completion[${index}]} + if [ -n "${ZSH_VERSION}" ]; then + # zsh completion needs --flag= prefix + eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )" + fi + fi + fi + return 0; + ;; + esac + + # check if we are handling a flag with special work handling + local index + __fuzzit_index_of_word "${prev}" "${flags_with_completion[@]}" + if [[ ${index} -ge 0 ]]; then + ${flags_completion[${index}]} + return + fi + + # we are parsing a flag and don't have a special handler, no completion + if [[ ${cur} != "${words[cword]}" ]]; then + return + fi + + local completions + completions=("${commands[@]}") + if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then + completions=("${must_have_one_noun[@]}") + fi + if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then + completions+=("${must_have_one_flag[@]}") + fi + COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) + + if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then + COMPREPLY=( $(compgen -W "${noun_aliases[*]}" -- "$cur") ) + fi + + if [[ ${#COMPREPLY[@]} -eq 0 ]]; then + if declare -F __fuzzit_custom_func >/dev/null; then + # try command name qualified custom func + __fuzzit_custom_func + else + # otherwise fall back to unqualified for compatibility + declare -F __custom_func >/dev/null && __custom_func + fi + fi + + # available in bash-completion >= 2, not always present on macOS + if declare -F __ltrim_colon_completions >/dev/null; then + __ltrim_colon_completions "$cur" + fi + + # If there is only 1 completion and it is a flag with an = it will be completed + # but we don't want a space after the = + if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then + compopt -o nospace + fi +} + +# The arguments should be in the form "ext1|ext2|extn" +__fuzzit_handle_filename_extension_flag() +{ + local ext="$1" + _filedir "@(${ext})" +} + +__fuzzit_handle_subdirs_in_dir_flag() +{ + local dir="$1" + pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 +} + +__fuzzit_handle_flag() +{ + __fuzzit_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + # if a command required a flag, and we found it, unset must_have_one_flag() + local flagname=${words[c]} + local flagvalue + # if the word contained an = + if [[ ${words[c]} == *"="* ]]; then + flagvalue=${flagname#*=} # take in as flagvalue after the = + flagname=${flagname%=*} # strip everything after the = + flagname="${flagname}=" # but put the = back + fi + __fuzzit_debug "${FUNCNAME[0]}: looking for ${flagname}" + if __fuzzit_contains_word "${flagname}" "${must_have_one_flag[@]}"; then + must_have_one_flag=() + fi + + # if you set a flag which only applies to this command, don't show subcommands + if __fuzzit_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then + commands=() + fi + + # keep flag value with flagname as flaghash + # flaghash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then + if [ -n "${flagvalue}" ] ; then + flaghash[${flagname}]=${flagvalue} + elif [ -n "${words[ $((c+1)) ]}" ] ; then + flaghash[${flagname}]=${words[ $((c+1)) ]} + else + flaghash[${flagname}]="true" # pad "true" for bool flag + fi + fi + + # skip the argument to a two word flag + if [[ ${words[c]} != *"="* ]] && __fuzzit_contains_word "${words[c]}" "${two_word_flags[@]}"; then + __fuzzit_debug "${FUNCNAME[0]}: found a flag ${words[c]}, skip the next argument" + c=$((c+1)) + # if we are looking for a flags value, don't show commands + if [[ $c -eq $cword ]]; then + commands=() + fi + fi + + c=$((c+1)) + +} + +__fuzzit_handle_noun() +{ + __fuzzit_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + if __fuzzit_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then + must_have_one_noun=() + elif __fuzzit_contains_word "${words[c]}" "${noun_aliases[@]}"; then + must_have_one_noun=() + fi + + nouns+=("${words[c]}") + c=$((c+1)) +} + +__fuzzit_handle_command() +{ + __fuzzit_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + local next_command + if [[ -n ${last_command} ]]; then + next_command="_${last_command}_${words[c]//:/__}" + else + if [[ $c -eq 0 ]]; then + next_command="_fuzzit_root_command" + else + next_command="_${words[c]//:/__}" + fi + fi + c=$((c+1)) + __fuzzit_debug "${FUNCNAME[0]}: looking for ${next_command}" + declare -F "$next_command" >/dev/null && $next_command +} + +__fuzzit_handle_word() +{ + if [[ $c -ge $cword ]]; then + __fuzzit_handle_reply + return + fi + __fuzzit_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + if [[ "${words[c]}" == -* ]]; then + __fuzzit_handle_flag + elif __fuzzit_contains_word "${words[c]}" "${commands[@]}"; then + __fuzzit_handle_command + elif [[ $c -eq 0 ]]; then + __fuzzit_handle_command + elif __fuzzit_contains_word "${words[c]}" "${command_aliases[@]}"; then + # aliashash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then + words[c]=${aliashash[${words[c]}]} + __fuzzit_handle_command + else + __fuzzit_handle_noun + fi + else + __fuzzit_handle_noun + fi + __fuzzit_handle_word +} + +_fuzzit_auth() +{ + last_command="fuzzit_auth" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--api-key=") + two_word_flags+=("--api-key") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_fuzzit_completion() +{ + last_command="fuzzit_completion" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--help") + flags+=("-h") + local_nonpersistent_flags+=("--help") + flags+=("--api-key=") + two_word_flags+=("--api-key") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_fuzzit_create_job() +{ + last_command="fuzzit_create_job" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--args=") + two_word_flags+=("--args") + local_nonpersistent_flags+=("--args=") + flags+=("--branch=") + two_word_flags+=("--branch") + local_nonpersistent_flags+=("--branch=") + flags+=("--cpus=") + two_word_flags+=("--cpus") + local_nonpersistent_flags+=("--cpus=") + flags+=("--environment=") + two_word_flags+=("--environment") + two_word_flags+=("-e") + local_nonpersistent_flags+=("--environment=") + flags+=("--revision=") + two_word_flags+=("--revision") + local_nonpersistent_flags+=("--revision=") + flags+=("--type=") + two_word_flags+=("--type") + local_nonpersistent_flags+=("--type=") + flags+=("--api-key=") + two_word_flags+=("--api-key") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_fuzzit_create_target() +{ + last_command="fuzzit_create_target" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--seed=") + two_word_flags+=("--seed") + local_nonpersistent_flags+=("--seed=") + flags+=("--api-key=") + two_word_flags+=("--api-key") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_fuzzit_create() +{ + last_command="fuzzit_create" + + command_aliases=() + + commands=() + commands+=("job") + commands+=("target") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--api-key=") + two_word_flags+=("--api-key") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_fuzzit_download() +{ + last_command="fuzzit_download" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--api-key=") + two_word_flags+=("--api-key") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_fuzzit_get() +{ + last_command="fuzzit_get" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--api-key=") + two_word_flags+=("--api-key") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_fuzzit_root_command() +{ + last_command="fuzzit" + + command_aliases=() + + commands=() + commands+=("auth") + commands+=("completion") + commands+=("create") + commands+=("download") + commands+=("get") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--api-key=") + two_word_flags+=("--api-key") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +__start_fuzzit() +{ + local cur prev words cword + declare -A flaghash 2>/dev/null || : + declare -A aliashash 2>/dev/null || : + if declare -F _init_completion >/dev/null 2>&1; then + _init_completion -s || return + else + __fuzzit_init_completion -n "=" || return + fi + + local c=0 + local flags=() + local two_word_flags=() + local local_nonpersistent_flags=() + local flags_with_completion=() + local flags_completion=() + local commands=("fuzzit") + local must_have_one_flag=() + local must_have_one_noun=() + local last_command + local nouns=() + + __fuzzit_handle_word +} + +if [[ $(type -t compopt) = "builtin" ]]; then + complete -o default -F __start_fuzzit fuzzit +else + complete -o default -o nospace -F __start_fuzzit fuzzit +fi + +# ex: ts=4 sw=4 et filetype=sh diff --git a/test/fuzz-reader/makefile b/test/fuzz-reader/makefile new file mode 100644 index 000000000..823a0d35b --- /dev/null +++ b/test/fuzz-reader/makefile @@ -0,0 +1,12 @@ +.PHONY: fuzz + +fuzz: fuzz-build fuzz-run + +fuzz-build: + CGO_ENABLED=1 GO111MODULE=off go-fuzz-build github.com/moov-io/ach/test/fuzz-reader + +fuzz-run: + CGO_ENABLED=1 GO111MODULE=off go-fuzz -bin=./fuzzreader-fuzz.zip -workdir=$(shell pwd) + +install: + CGO_ENABLED=1 GO111MODULE=off go get -u github.com/dvyukov/go-fuzz/... diff --git a/test/fuzz-reader/reader.go b/test/fuzz-reader/reader.go new file mode 100644 index 000000000..22eaa047b --- /dev/null +++ b/test/fuzz-reader/reader.go @@ -0,0 +1,120 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package fuzzreader + +import ( + "bytes" + + "github.com/moov-io/ach" +) + +// Return codes (from go-fuzz docs) +// +// The function must return 1 if the fuzzer should increase priority +// of the given input during subsequent fuzzing (for example, the input is +// lexically correct and was parsed successfully); -1 if the input must not be +// added to corpus even if gives new coverage; and 0 otherwise; other values are +// reserved for future use. +func Fuzz(data []byte) int { + r := ach.NewReader(bytes.NewReader(data)) + f, err := r.Read() + if err != nil { + // if f != nil { + // panic(fmt.Sprintf("f != nil on err != nil: %v", f)) + // } + return 0 + } + + // Check f (as ach.File) + if f.ID != "" { + return 1 + } + + // FileHeader + if n := checkFileHeader(&f); n > 0 { + return n + } + + // Batches + if n := checkFileBatches(&f); n > 0 { + return n + } + + // FileControl + if n := checkFileControl(&f); n > 0 { + return n + } + + // Changes / Returns + if len(f.NotificationOfChange) > 0 { + return 1 + } + if len(f.ReturnEntries) > 0 { + return 1 + } + + return 0 // increase priority of input +} + +func checkFileHeader(f *ach.File) int { + if f.Header.ID != "" { + return 1 + } + if f.Header.ImmediateDestination != "" || f.Header.ImmediateOrigin != "" { + return 1 + } + if f.Header.FileCreationDate != "" || f.Header.FileCreationTime != "" { + return 1 + } + if f.Header.FileIDModifier != "" { + return 1 + } + if f.Header.ImmediateDestinationName != "" || f.Header.ImmediateOriginName != "" { + return 1 + } + if f.Header.ReferenceCode != "" { + return 1 + } + return -2 +} + +func checkFileBatches(f *ach.File) int { + if len(f.Batches) > 0 || len(f.IATBatches) > 0 { + return 1 + } + return -2 +} + +func checkFileControl(f *ach.File) int { + if f.Control.ID != "" { + return 1 + } + if f.Control.BatchCount > 0 || f.Control.BlockCount > 0 { + return 1 + } + if f.Control.EntryAddendaCount > 0 || f.Control.EntryHash > 0 { + return 1 + } + if f.Control.TotalDebitEntryDollarAmountInFile > 0 { + return 1 + } + if f.Control.TotalCreditEntryDollarAmountInFile > 0 { + return 1 + } + return -2 +} diff --git a/test/fuzz-reader/reader_test.go b/test/fuzz-reader/reader_test.go new file mode 100644 index 000000000..e1cf6e234 --- /dev/null +++ b/test/fuzz-reader/reader_test.go @@ -0,0 +1,53 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package fuzzreader + +import ( + "os" + "path/filepath" + "runtime" + "testing" +) + +func TestCorpusSymlinks(t *testing.T) { + // avoid symbolic link error on windows + if runtime.GOOS == "windows" { + t.Skip() + } + fds, err := os.ReadDir("corpus") + if err != nil { + t.Fatal(err) + } + if len(fds) == 0 { + t.Fatal("no file descriptors found in corpus/") + } + + for i := range fds { + if fds[i].Type()&os.ModeSymlink != 0 { + if path, err := os.Readlink(filepath.Join("corpus", fds[i].Name())); err != nil { + t.Errorf("broken symlink: %v", err) + } else { + if _, err := os.Stat(filepath.Join("corpus", path)); err != nil { + t.Errorf("broken symlink: %v", err) + } + } + } else { + t.Errorf("%s isn't a symlink, move outside corpus/ and symlink into directory", fds[i].Name()) + } + } +} diff --git a/test/issues/issue1010_test.go b/test/issues/issue1010_test.go new file mode 100644 index 000000000..cb06f78dd --- /dev/null +++ b/test/issues/issue1010_test.go @@ -0,0 +1,80 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package issues + +import ( + "testing" + "time" + + "github.com/moov-io/ach" +) + +func TestIssue1010(t *testing.T) { + // Batch Header + batchHeader := ach.NewBatchHeader() + batchHeader.ServiceClassCode = 200 + batchHeader.CompanyName = "COMPANY" + batchHeader.CompanyIdentification = "IDENTIFICATION" + batchHeader.StandardEntryClassCode = ach.WEB + batchHeader.CompanyEntryDescription = "DESCRIPTION" + batchHeader.CompanyDescriptiveDate = time.Now().Format("060102") + batchHeader.EffectiveEntryDate = time.Now().Add(time.Hour * 24).Format("060102") + batchHeader.ODFIIdentification = "1" + + // Addenda99 + addenda99 := ach.NewAddenda99() + addenda99.ReturnCode = "R07" + addenda99.OriginalTrace = "99912340000015" + addenda99.AddendaInformation = "Authorization Revoked" + addenda99.OriginalDFI = "9101298" + + // Entry + entry := ach.NewEntryDetail() + entry.TransactionCode = 22 + entry.RDFIIdentification = "1" + entry.CheckDigit = "3" + entry.DFIAccountNumber = "DUMMY" + entry.Amount = 1000 + entry.IndividualName = "DOE, JOHN" + entry.Category = ach.CategoryReturn + entry.Addenda99 = addenda99 + entry.AddendaRecordIndicator = 1 + + // Batches + batch1 := ach.NewBatchWEB(batchHeader) + batch1.AddEntry(entry) + + // Prints nil + if err := batch1.Create(); err != nil { + t.Fatal(err) + } + + batch2 := ach.NewBatchWEB(batchHeader) + batch2.AddEntry(entry) + batch2.WithOffset(&ach.Offset{ + RoutingNumber: "100000007", + AccountNumber: "DUMMY", + AccountType: "checking", + Description: "S", + }) + + // Prints error + if err := batch2.Create(); err != nil { + t.Fatal(err) + } +} diff --git a/test/issues/issue1024_test.go b/test/issues/issue1024_test.go new file mode 100644 index 000000000..bd0a9967a --- /dev/null +++ b/test/issues/issue1024_test.go @@ -0,0 +1,104 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package issues + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + "time" + + "github.com/moov-io/ach" + "github.com/moov-io/ach/server" + "github.com/moov-io/base/log" + + kitlog "github.com/go-kit/log" + "github.com/stretchr/testify/require" +) + +func TestIssue1024__Read(t *testing.T) { + bs, err := os.ReadFile(filepath.Join("testdata", "issue1024.json")) + require.NoError(t, err) + + file, err := ach.FileFromJSON(bs) + require.NoError(t, err) + require.Len(t, file.Batches, 1) + + entries := file.Batches[0].GetEntries() + require.Len(t, entries, 1) + + // I expected the traceNumber field in addenda99 to be automatically + // populated equal to the traceNumber of the entry + ed := entries[0] + require.Equal(t, "084106760000001", ed.TraceNumber) + require.NotNil(t, ed.Addenda99) + require.Equal(t, "084106760000001", ed.Addenda99.TraceNumber) +} + +func TestIssue1024__Server(t *testing.T) { + repo := server.NewRepositoryInMemory(0*time.Second, log.NewNopLogger()) + svc := server.NewService(repo) + handler := server.MakeHTTPHandler(svc, repo, kitlog.NewNopLogger()) + + fd, err := os.Open(filepath.Join("testdata", "issue1024.json")) + require.NoError(t, err) + + w := httptest.NewRecorder() + req := httptest.NewRequest("POST", "/files/create", fd) + req.Header.Set("Content-Type", "application/json") + handler.ServeHTTP(w, req) + w.Flush() + require.Equal(t, http.StatusOK, w.Code) + + var createResponse struct { + ID string `json:"id"` + } + err = json.NewDecoder(w.Body).Decode(&createResponse) + require.NoError(t, err) + + // Read the full file in JSON format + w = httptest.NewRecorder() + req = httptest.NewRequest("GET", fmt.Sprintf("/files/%s/build", createResponse.ID), nil) + req.Header.Set("Content-Type", "application/json") + handler.ServeHTTP(w, req) + w.Flush() + require.Equal(t, http.StatusOK, w.Code) + + var wrapper struct { + File ach.File `json:"file"` + Err error `json:"error"` + } + err = json.NewDecoder(w.Body).Decode(&wrapper) + require.NoError(t, err) + + require.Len(t, wrapper.File.Batches, 1) + + entries := wrapper.File.Batches[0].GetEntries() + require.Len(t, entries, 1) + + // I expected the traceNumber field in addenda99 to be automatically + // populated equal to the traceNumber of the entry + ed := entries[0] + require.Equal(t, "084106760000001", ed.TraceNumber) + require.NotNil(t, ed.Addenda99) + require.Equal(t, "084106760000001", ed.Addenda99.TraceNumber) +} diff --git a/test/issues/issue702_test.go b/test/issues/issue702_test.go new file mode 100644 index 000000000..7bddd8d38 --- /dev/null +++ b/test/issues/issue702_test.go @@ -0,0 +1,98 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package issues + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/moov-io/ach" +) + +func TestIssue702(t *testing.T) { + // A vendor gave issue702.ach to a customer of ours as a return but didn't properly + // format the line lengths and included a non-routing number in the ImmediateDestination + fd, err := os.Open(filepath.Join("testdata", "issue702.ach")) + if err != nil { + t.Fatal(err) + } + defer fd.Close() + + r := ach.NewReader(fd) + r.SetValidation(&ach.ValidateOpts{ + BypassDestinationValidation: true, + }) + + file, err := r.Read() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if err := file.Validate(); err != nil { + t.Error(err) + } + + if file.Header.ImmediateDestination != "YYYYYYYYY" { + t.Errorf("file.Header.ImmediateDestination=%s", file.Header.ImmediateDestination) + } + if n := len(file.Batches); n != 3 { + t.Errorf("got %d batches", n) + } + if n := len(file.ReturnEntries); n != 3 { + t.Errorf("got %d NOC's", n) + } +} + +func TestIssue702_1(t *testing.T) { + // This file was returned as a receipt from uploading an ACH file, but this file is + // pretty useless as it contains zero EntryDetail's. + fd, err := os.Open(filepath.Join("testdata", "issue702-1.ach")) + if err != nil { + t.Fatal(err) + } + defer fd.Close() + + r := ach.NewReader(fd) + r.SetValidation(&ach.ValidateOpts{ + BypassDestinationValidation: true, + }) + + file, err := r.Read() + if err != nil { + if !strings.Contains(err.Error(), "BatchCount 000000 is a mandatory field") { + t.Error(err) + } + } + if err := file.Validate(); err != nil { + if !strings.Contains(err.Error(), "BatchCount 000000 is a mandatory field") { + t.Error(err) + } + } + + if file.Header.ImmediateOrigin != "182327390" { + t.Errorf("ImmediateOrigin=%s", file.Header.ImmediateOrigin) + } + if file.Header.ImmediateDestination != "10006XXXX" { + t.Errorf("ImmediateDestination=%s", file.Header.ImmediateDestination) + } + if file.Header.ImmediateDestinationName != "PIMRET825324" { + t.Errorf("ImmediateDestinationName=%s", file.Header.ImmediateDestinationName) + } +} diff --git a/test/issues/issue751_test.go b/test/issues/issue751_test.go new file mode 100644 index 000000000..44362fa82 --- /dev/null +++ b/test/issues/issue751_test.go @@ -0,0 +1,52 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package issues + +import ( + "os" + "path/filepath" + "testing" + + "github.com/moov-io/ach" +) + +func TestIssue751(t *testing.T) { + fd, err := os.Open(filepath.Join("testdata", "issue751.ach")) + if err != nil { + t.Fatal(err) + } + file, err := ach.NewReader(fd).Read() + if err == nil { + t.Error("expected error") + } + if len(file.Batches) != 1 || len(file.IATBatches) != 0 { + t.Errorf("got %d Batches and %d IAT Batches", len(file.Batches), len(file.IATBatches)) + } + + entries := file.Batches[0].GetEntries() + if len(entries) != 2 { + t.Fatalf("got %d Entries", len(entries)) + } + + if acct := entries[0].DFIAccountNumber; acct != "82111184 " { + t.Errorf("got %q", acct) + } + if acct := entries[1].DFIAccountNumber; acct != "0110 " { + t.Errorf("got %q", acct) + } +} diff --git a/test/issues/issue863_test.go b/test/issues/issue863_test.go new file mode 100644 index 000000000..4150382ce --- /dev/null +++ b/test/issues/issue863_test.go @@ -0,0 +1,91 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package issues + +import ( + "archive/tar" + "io" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/moov-io/ach" +) + +func TestIssue863(t *testing.T) { + fd, err := os.Open(filepath.Join("testdata", "storage.tar")) + if err != nil { + t.Fatal(err) + } + files, err := readFiles(t, fd) + if err != nil { + t.Fatal(err) + } + t.Logf("read %d files", len(files)) + if n := len(files); n != 11 { + t.Fatalf("found %d ACH files", n) + } + + merged, err := ach.MergeFiles(files) + if err != nil { + t.Fatal(err) + } + if n := len(merged); n != 1 { + t.Fatalf("found %d files", n) + } + + if err := merged[0].Validate(); err != nil { + t.Fatal(err) + } + + final, err := merged[0].FlattenBatches() + if err != nil { + t.Fatal(err) + } + if err := final.Validate(); err != nil { + t.Fatal(err) + } +} + +func readFiles(t *testing.T, r io.Reader) ([]*ach.File, error) { + t.Helper() + + var out []*ach.File + rdr := tar.NewReader(r) + for { + header, err := rdr.Next() + if err != nil { + if err == io.EOF { + break + } + t.Fatal(err) + } + + if fd := header.FileInfo(); fd.IsDir() || strings.Contains(fd.Name(), ".json") { + continue + } + + f, err := ach.NewReader(rdr).Read() + if err != nil { + t.Fatal(err) + } + out = append(out, &f) + } + return out, nil +} diff --git a/test/issues/issue915_test.go b/test/issues/issue915_test.go new file mode 100644 index 000000000..d27c531b1 --- /dev/null +++ b/test/issues/issue915_test.go @@ -0,0 +1,94 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package issues + +import ( + "bytes" + "testing" + + "github.com/moov-io/ach" +) + +func TestIssue915(t *testing.T) { + rdr := ach.NewReader(bytes.NewReader(input)) + + file, _ := rdr.Read() + + if n := len(file.Batches); n != 12 { + t.Errorf("found %d batches", n) + } + for i := range file.Batches { + entries := file.Batches[i].GetEntries() + if n := len(entries); n != 1 { + t.Errorf("Batch[%d] has %d entries", i, n) + } + } +} + +var ( + input = []byte(`101CUSTOMNREC 0610001042103111747J094101CUSTOMN ABC BANK, N.A. +5200ABC COMPANY 9876543210PPDREVERSAL 0421162103110701123443210000001 +622061000104101123400076543210007654321123456789012345TEST CONSUMER 0021070000087794 +820000000100101000690000000000000000076543219876543210 123443210000001 +5200XYZ INCORPORATEDCOMPANY DESCRETIONAR9123456789PPDTESTCREDIT1604212103110701121214140000002 +622061000104101123400076543211234567890111111111111111TEST CONSUMER 0021070000087796 +820000000100101000690000000000000012345678909123456789 121214140000002 +5200ABC COMPANY 9876543210PPDREVERSAL 0421162103110701123443210000003 +622061000104101123400076543210007654321123456789012345TEST CONSUMER 1021070000087803 +705XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX00010087803 +820000000200101000690000000000000000076543219876543210 123443210000003 +5200XYZ INCORPORATEDCOMPANY DESCRETIONAR9123456789PPDTESTCREDIT1604212103110701121214140000004 +622061000104101123400076543211234567890111111111111111TEST CONSUMER 1021070000087806 +705XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX00010087806 +820000000200101000690000000000000012345678909123456789 121214140000004 +5200ABC COMPANY 9876543210WEBREVERSAL 0421162103110701123443210000005 +622061000104101123400076543210007654321123456789012345TEST CONSUMER S 0021070000087814 +820000000100101000690000000000000000076543219876543210 123443210000005 +5200XYZ INCORPORATEDCOMPANY DESCRETIONAR9123456789WEBTESTCREDIT1604212103110701121214140000006 +622061000104101123400076543211234567890111111111111111TEST CONSUMER S 0021070000087816 +820000000100101000690000000000000012345678909123456789 121214140000006 +5200ABC COMPANY 9876543210PPDTEST DEBIT0421162103110701123443210000007 +627061000104101123400076543210007654321123456789012345TEST CONSUMER 0021070000087792 +820000000100101000690000076543210000000000009876543210 123443210000007 +5200XYZ INCORPORATEDCOMPANY DESCRETIONAR9123456789PPDREVERSAL 1604212103110701121214140000008 +627061000104101123400076543211234567890111111111111111TEST CONSUMER 0021070000087798 +820000000100101000690012345678900000000000009123456789 121214140000008 +5200ABC COMPANY 9876543210PPDTEST DEBIT0421162103110701123443210000009 +627061000104101123400076543211234567890123456789012345TEST CONSUMER 1021070000087800 +705XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX00010087800 +820000000200101000690012345678900000000000009876543210 123443210000009 +5200XYZ INCORPORATEDCOMPANY DESCRETIONAR9123456789PPDREVERSAL 1604212103110701121214140000010 +627061000104101123400076543211234567890111111111111111TEST CONSUMER 1021070000087809 +705XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX00010087809 +820000000200101000690012345678900000000000009123456789 121214140000010 +5200ABC COMPANY 9876543210WEBTEST DEBIT0421162103110701123443210000011 +627061000104101123400076543210007654321123456789012345TEST CONSUMER S 0021070000087812 +820000000100101000690000076543210000000000009876543210 123443210000011 +5200XYZ INCORPORATEDCOMPANY DESCRETIONAR9123456789WEBREVERSAL 1604212103110701121214140000012 +627061000104101123400076543211234567890111111111111111TEST CONSUMER S 0021070000087818 +820000000100101000690012345678900000000000009123456789 121214140000012 +9000012000005000000160121200828004953580202003726666633 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999`) +) diff --git a/test/issues/issue927_test.go b/test/issues/issue927_test.go new file mode 100644 index 000000000..b745a7ee4 --- /dev/null +++ b/test/issues/issue927_test.go @@ -0,0 +1,47 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package issues + +import ( + "path/filepath" + "testing" + + "github.com/moov-io/ach" +) + +func TestIssue927(t *testing.T) { + before, err := ach.ReadDir(filepath.Join("testdata", "issue927")) + if err != nil { + t.Fatal(err) + } + after, err := ach.MergeFiles(before) + if err != nil { + t.Fatal(err) + } + + if len(after) != 2 { + t.Fatalf("merged %d files into %d files", len(before), len(after)) + } + + if n := len(after[0].Batches); n != 5 { + t.Errorf("found %d batches", n) + } + if n := len(after[1].Batches); n != 3 { + t.Errorf("found %d batches", n) + } +} diff --git a/test/issues/pull713_test.go b/test/issues/pull713_test.go new file mode 100644 index 000000000..ca22092f7 --- /dev/null +++ b/test/issues/pull713_test.go @@ -0,0 +1,56 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package issues + +import ( + "path/filepath" + "strings" + "testing" + + "github.com/moov-io/ach" +) + +func TestPull713(t *testing.T) { + // A vendor has these health files which contain zero batches and are used + // as a sort of daily heartbeat for software. + file, err := ach.ReadFile(filepath.Join("..", "testdata", "FISERV-ZEROFILE-PIMRET825324_032720_110221.ach")) + if err == nil { + t.Error("expected error") + } + if !strings.Contains(err.Error(), `ImmediateDestination 100067554 routing number checksum mismatch`) { + t.Error(err) + } + + fh := file.Header + if fh.ImmediateDestination != "100067554" { + t.Errorf("ImmediateDestination=%s", fh.ImmediateDestination) + } + if fh.ImmediateOrigin != "182327390" { + t.Errorf("ImmediateOrigin=%s", fh.ImmediateOrigin) + } + if fh.ImmediateDestinationName != "PIMRET825324" { + t.Errorf("ImmediateDestinationName=%s", fh.ImmediateDestinationName) + } + if fh.ImmediateOriginName != "FISERV" { + t.Errorf("ImmediateOriginName=%s", fh.ImmediateOriginName) + } + + if file.Control.BatchCount != 0 { + t.Errorf("control batch count: %d", file.Control.BatchCount) + } +} diff --git a/test/issues/testdata/issue1024.json b/test/issues/testdata/issue1024.json new file mode 100644 index 000000000..ca156b1ae --- /dev/null +++ b/test/issues/testdata/issue1024.json @@ -0,0 +1,47 @@ +{ + "fileHeader": { + "immediateOrigin": "121042882", + "immediateOriginName": "Test", + "immediateDestination": "121042882", + "immediateDestinationName": "Test", + "fileCreationDate": "220527", + "fileCreationTime": "0312" + }, + "batches": [ + { + "batchHeader": { + "id": "", + "serviceClassCode": 200, + "companyName": "Test", + "companyIdentification": "Test", + "standardEntryClassCode": "CCD", + "companyEntryDescription": "Test", + "effectiveEntryDate": "220513", + "originatorStatusCode": 1, + "ODFIIdentification": "08410676", + "batchNumber": 1 + }, + "entryDetails": [ + { + "id": "", + "transactionCode": 22, + "RDFIIdentification": "06100014", + "checkDigit": "6", + "DFIAccountNumber": "01004019 ", + "amount": 1238, + "identificationNumber": "1589 ", + "individualName": "A ROBERT ANDERSON ", + "discretionaryData": " ", + "addendaRecordIndicator": 1, + "addenda99": { + "typeCode": "99", + "returnCode": "R03", + "originalTrace": "061000140000001", + "originalDFI": "12510456", + "addendaInformation": "Testing" + } + } + ] + } + ] +} diff --git a/test/issues/testdata/issue702-1.ach b/test/issues/testdata/issue702-1.ach new file mode 100644 index 000000000..0f01da145 --- /dev/null +++ b/test/issues/testdata/issue702-1.ach @@ -0,0 +1,10 @@ +101 10006XXXX 1823273902003271043 094101PIMRET825324 FISERV +9000000000001000000000000000000000000000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/issues/testdata/issue702.ach b/test/issues/testdata/issue702.ach new file mode 100644 index 000000000..03153938f --- /dev/null +++ b/test/issues/testdata/issue702.ach @@ -0,0 +1,20 @@ +101 YYYYYYYYY 1823273902003310500 094101PIMRET825324 FISERV +5220POSSIBLE FINANCE 1823273909PPDLOAN 2003270911031101270002607 +621101206101686133344441 000000010260502a49fb51411Prasad Mahendra 1031101278009179 +799R04101206100000001 03110127PSEUDORETN 031101278009179 +822000000200101206100000000000000000000001021823273909 031101270002607 +5225POSSIBLE FINANCE 1823273909WEBPAYMENT 2003270911031101270002608 +626101206101154444444411 00000001013b212c569c794c0Prasad Mahendra 1031101278009180 +799R03101206100000001 03110127PSEUDORETN 031101278009180 +822500000200101206100000000001010000000000001823273909 031101270002608 +5225POSSIBLE FINANCE 1823273909WEBPAYMENT 2003270911031101270002609 +62610120610114444444YYYY 0000010001e05d5435dcf249aPrasad Mahendra 1031101278009181 +799R01101206100000001 03110127PSEUDORETN 031101278009181 +822500000200101206100000000100010000000000001823273909 031101270002609 +9000003000002000000060030361830000000010102000000000102 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/issues/testdata/issue751.ach b/test/issues/testdata/issue751.ach new file mode 100644 index 000000000..f511964ba --- /dev/null +++ b/test/issues/testdata/issue751.ach @@ -0,0 +1,6 @@ +5225MY COMPANY NAME 1823273909WEBPAYMENT 2007312171123456780000151 +62610120610182111184 0000007714c4437ad535504daFFFFFFFF LLLLL 1123456780000879 +799R01101206100003510 12345678 123456780000879 +6261012061010110 0000009446caddd043a027475FFFFFFF LLLLLL 1123456780000880 +799R03101206100003650 12345678 123456780000880 +822500000400202412200000000171600000000000001823273909 123456780000151 \ No newline at end of file diff --git a/test/issues/testdata/issue927/016461532850569c045ce6e1056e3d49b95727d1.ach b/test/issues/testdata/issue927/016461532850569c045ce6e1056e3d49b95727d1.ach new file mode 100644 index 000000000..d4aef6da2 --- /dev/null +++ b/test/issues/testdata/issue927/016461532850569c045ce6e1056e3d49b95727d1.ach @@ -0,0 +1,10 @@ +101 071000301 2739763692105051229A094101FRBATLANTA ODFI CU +5200MOOV FINANCIAL ISSUE927 CCDFUNDING 210505210506 1273976360000001 +627073902766YYYYYYYYYYY 0000200000 ISSUE927 LLC Ap0273976367998099 +632273976369XXXXXXXXXXX 0000200000 MOOV FINANCIAL OF0273976367998100 +82000000020034787912000000200000000000200000ISSUE927 273976360000001 +9000001000001000000020034787912000000200000000000200000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/issues/testdata/issue927/044f121c5da6ff398d22b6ca8679239a13270594.ach b/test/issues/testdata/issue927/044f121c5da6ff398d22b6ca8679239a13270594.ach new file mode 100644 index 000000000..c381e8405 --- /dev/null +++ b/test/issues/testdata/issue927/044f121c5da6ff398d22b6ca8679239a13270594.ach @@ -0,0 +1,10 @@ +101 071000301 2739763692105051225A094101FRBATLANTA ODFI CU +5200MOOV FINANCIAL ISSUE927 CCDFUNDING 210505210506 1273976360000001 +627073902766YYYYYYYYYY 0000007900 ISSUE927 LLC Ap0273976361975007 +632273976369XXXXXXXXXXX 0000007900 MOOV FINANCIAL OF0273976361975008 +82000000020034787912000000007900000000007900ISSUE927 273976360000001 +9000001000001000000020034787912000000007900000000007900 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/issues/testdata/issue927/2b5a940e6b5bcc9f2753d31ea6309d1f49b21891.ach b/test/issues/testdata/issue927/2b5a940e6b5bcc9f2753d31ea6309d1f49b21891.ach new file mode 100644 index 000000000..55ec123e1 --- /dev/null +++ b/test/issues/testdata/issue927/2b5a940e6b5bcc9f2753d31ea6309d1f49b21891.ach @@ -0,0 +1,10 @@ +101 071000301 2739763692105051312A094101FRBATLANTA ODFI CU +5200MOOV FINANCIAL ISSUE927 CCDFUNDING 210505210506 1273976360000001 +627073902766YYYYYYYYYY 0000015800 ISSUE927 LLC Ap0273976368549829 +632273976369XXXXXXXXXXX 0000015800 MOOV FINANCIAL OF0273976368549830 +82000000020034787912000000015800000000015800ISSUE927 273976360000001 +9000001000001000000020034787912000000015800000000015800 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/issues/testdata/issue927/44a858e07e89fb0b24bcfdda733070f38ef1e889.ach b/test/issues/testdata/issue927/44a858e07e89fb0b24bcfdda733070f38ef1e889.ach new file mode 100644 index 000000000..6370c2490 --- /dev/null +++ b/test/issues/testdata/issue927/44a858e07e89fb0b24bcfdda733070f38ef1e889.ach @@ -0,0 +1,10 @@ +101 071000301 2739763692105051122A094101FRBATLANTA ODFI CU +5200MOOV FINANCIAL ISSUE927 CCDFUNDING 210505210506 1273976360000001 +627073902766YYYYYYYYYY 0000200000 ISSUE927 LLC Ap0273976366639555 +632273976369XXXXXXXXXXX 0000200000 MOOV FINANCIAL OF0273976366639556 +82000000020034787912000000200000000000200000ISSUE927 273976360000001 +9000001000001000000020034787912000000200000000000200000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/issues/testdata/issue927/54fa1e16738209076a14548ff3dd061ef2e671a1.ach b/test/issues/testdata/issue927/54fa1e16738209076a14548ff3dd061ef2e671a1.ach new file mode 100644 index 000000000..3d9821356 --- /dev/null +++ b/test/issues/testdata/issue927/54fa1e16738209076a14548ff3dd061ef2e671a1.ach @@ -0,0 +1,10 @@ +101 071000301 2739763692105051315A094101FRBATLANTA ODFI CU +5200MOOV FINANCIAL ISSUE927 CCDFUNDING 210505210506 1273976360000001 +627073902766YYYYYYYYYYY 0000342400 ISSUE927 LLC Ap0273976364891931 +632273976369XXXXXXXXXXX 0000342400 MOOV FINANCIAL OF0273976364891932 +82000000020034787912000000342400000000342400ISSUE927 273976360000001 +9000001000001000000020034787912000000342400000000342400 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/issues/testdata/issue927/9bb2e883c8cfa2bfc73f433fcac11a7e90f8d6e5.ach b/test/issues/testdata/issue927/9bb2e883c8cfa2bfc73f433fcac11a7e90f8d6e5.ach new file mode 100644 index 000000000..f69ad5764 --- /dev/null +++ b/test/issues/testdata/issue927/9bb2e883c8cfa2bfc73f433fcac11a7e90f8d6e5.ach @@ -0,0 +1,10 @@ +101 071000301 2739763692105051330A094101FRBATLANTA ODFI CU +5200MOOV FINANCIAL ISSUE927 CCDFUNDING 210505210506 1273976360000001 +627073902766YYYYYYYYYYY 0000200000 ISSUE927 LLC Ap0273976363652315 +632273976369XXXXXXXXXXX 0000200000 MOOV FINANCIAL OF0273976363652316 +82000000020034787912000000200000000000200000ISSUE927 273976360000001 +9000001000001000000020034787912000000200000000000200000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/issues/testdata/issue927/a6ce094411be256f495f6135d5089b9d9533fa27.ach b/test/issues/testdata/issue927/a6ce094411be256f495f6135d5089b9d9533fa27.ach new file mode 100644 index 000000000..1a57b93b4 --- /dev/null +++ b/test/issues/testdata/issue927/a6ce094411be256f495f6135d5089b9d9533fa27.ach @@ -0,0 +1,10 @@ +101 071000301 2739763692105051255A094101FRBATLANTA ODFI CU +5200MOOV FINANCIAL ISSUE927 CCDFUNDING 210505210506 1273976360000001 +627073902766YYYYYYYYYY 0000250000 ISSUE927 LLC Ap0273976366639555 +632273976369XXXXXXXXXXX 0000250000 MOOV FINANCIAL OF0273976366639556 +82000000020034787912000000250000000000250000ISSUE927 273976360000001 +9000001000001000000020034787912000000250000000000250000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/issues/testdata/issue927/bfd6b648c4b4e2d1581a0767740233f2c67cebea.ach b/test/issues/testdata/issue927/bfd6b648c4b4e2d1581a0767740233f2c67cebea.ach new file mode 100644 index 000000000..2598c0872 --- /dev/null +++ b/test/issues/testdata/issue927/bfd6b648c4b4e2d1581a0767740233f2c67cebea.ach @@ -0,0 +1,10 @@ +101 071000301 2739763692105051309A094101FRBATLANTA ODFI CU +5200MOOV FINANCIAL ISSUE927 CCDFUNDING 210505210506 1273976360000001 +627073902766YYYYYYYYYYY 0000029500 ISSUE927 LLC Ap0273976361975007 +632273976369XXXXXXXXXXX 0000029500 MOOV FINANCIAL OF0273976361975008 +82000000020034787912000000029500000000029500ISSUE927 273976360000001 +9000001000001000000020034787912000000029500000000029500 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/issues/testdata/issue927/e2ef49fe5fa2b676feff5f3669d5fffb99e22cdc.ach b/test/issues/testdata/issue927/e2ef49fe5fa2b676feff5f3669d5fffb99e22cdc.ach new file mode 100644 index 000000000..9da1e61b1 --- /dev/null +++ b/test/issues/testdata/issue927/e2ef49fe5fa2b676feff5f3669d5fffb99e22cdc.ach @@ -0,0 +1,10 @@ +101 071000301 2739763692105051227A094101FRBATLANTA ODFI CU +5200MOOV FINANCIAL ISSUE927 CCDFUNDING 210505210506 1273976360000001 +627073902766YYYYYYYYYYY 0000052100 ISSUE927 LLC Ap0273976364891931 +632273976369XXXXXXXXXXX 0000052100 MOOV FINANCIAL OF0273976364891932 +82000000020034787912000000052100000000052100ISSUE927 273976360000001 +9000001000001000000020034787912000000052100000000052100 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/issues/testdata/storage.tar b/test/issues/testdata/storage.tar new file mode 100644 index 000000000..00eb7a3d5 Binary files /dev/null and b/test/issues/testdata/storage.tar differ diff --git a/test/same-day-ach-ppd-read/main.go b/test/same-day-ach-ppd-read/main.go new file mode 100644 index 000000000..905cb0689 --- /dev/null +++ b/test/same-day-ach-ppd-read/main.go @@ -0,0 +1,52 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/moov-io/ach" +) + +func main() { + // Open a file for reading, any io.Reader can be used + f, err := os.Open("same-day-ach-ppd-credit.ach") + if err != nil { + log.Fatalln(err) + } + r := ach.NewReader(f) + achFile, err := r.Read() + if err != nil { + log.Fatalf("reading file: %v\n", err) + } + // If you trust the file but its formatting is off, building will probably resolve the malformed file + if err := achFile.Create(); err != nil { + log.Fatalf("creating file: %v\n", err) + } + // Validate the ACH file + if err := achFile.Validate(); err != nil { + log.Fatalf("validating file: %v\n", err) + } + + fmt.Printf("File Name: %s\n\n", f.Name()) + fmt.Printf("Total Credit Amount: %d\n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("SEC Code: %s\n\n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + +} diff --git a/test/same-day-ach-ppd-read/main_test.go b/test/same-day-ach-ppd-read/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/same-day-ach-ppd-read/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/same-day-ach-ppd-read/same-day-ach-ppd-credit.ach b/test/same-day-ach-ppd-read/same-day-ach-ppd-credit.ach new file mode 100644 index 000000000..526cb976c --- /dev/null +++ b/test/same-day-ach-ppd-read/same-day-ach-ppd-credit.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821901080000A094101Federal Reserve Bank My Bank Name +5220Name on Account 121042882 PPDREG.SALARYSD1300190108 1121042880000001 +62223138010412345678 0100000000 Receiver Account Name 0121042880000001 +82200000010023138010000000000000000100000000121042882 121042880000001 +9000001000001000000010023138010000000000000000100000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/same-day-ach-ppd-write/main.go b/test/same-day-ach-ppd-write/main.go new file mode 100644 index 000000000..527bf5379 --- /dev/null +++ b/test/same-day-ach-ppd-write/main.go @@ -0,0 +1,89 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH PPD file to send/credit a external institution's account + // Important: All financial institutions are different and will require registration and exact field values. + + // Set originator bank ODFI and destination Operator for the financial institution + // this is the funding/receiving source of the transfer + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent + fh.ImmediateOrigin = "121042882" // Routing Number of the ACH Operator or sending point that is sending the file + fh.FileCreationDate = time.Now().Format("060102") // Today's Date + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + + // BatchHeader identifies the originating entity and the type of transactions contained in the batch + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.CreditsOnly + bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with receiver + bh.CompanyIdentification = fh.ImmediateOrigin + bh.StandardEntryClassCode = ach.PPD + bh.CompanyEntryDescription = "REG.SALARY" // will be on receiving account's statement + // Same Day ACH + bh.EffectiveEntryDate = time.Now().Format("060102") + // CompanyDescriptiveDate ODFIs at their discretion may require their Originators to further show intent for + // same-day settlement using an optional, yet standardized, same-day indicator in the Company Descriptive Date + // field. The Company Descriptive Date field (5 record, field 8) is an optional field with 6 positions available + // (positions 64-69). + bh.CompanyDescriptiveDate = "SD1300" + bh.ODFIIdentification = "121042882" // Originating Routing Number + + // Identifies the receiver's account information + // can be multiple entries per batch + entry := ach.NewEntryDetail() + // Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL) + entry.TransactionCode = ach.CheckingCredit + entry.SetRDFI("231380104") // Receiver's bank transit routing number + entry.DFIAccountNumber = "12345678" // Receiver's bank account number + entry.Amount = 100000000 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.IndividualName = "Receiver Account Name" // Identifies the receiver of the transaction + + // build the batch + batch := ach.NewBatchPPD(bh) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + log.Fatalf("Unexpected error building batch: %s\n", err) + } + + // build the file + file := ach.NewFile() + file.SetHeader(fh) + file.AddBatch(batch) + if err := file.Create(); err != nil { + log.Fatalf("Unexpected error building file: %s\n", err) + } + + // Write the file to stdout, any io.Writer can be used + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error writing file: %s\n", err) + } + w.Flush() +} diff --git a/test/same-day-ach-ppd-write/main_test.go b/test/same-day-ach-ppd-write/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/same-day-ach-ppd-write/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/server-example/main.go b/test/server-example/main.go new file mode 100644 index 000000000..f0982a820 --- /dev/null +++ b/test/server-example/main.go @@ -0,0 +1,63 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package serverexample + +import ( + "net/http" + "net/http/httptest" + "os" + "testing" + "time" + + "github.com/moov-io/ach/server" + + "github.com/go-kit/log" +) + +func TestServer__CreateFile(t *testing.T) { + // Local server setup - usually ach would be running on another machine. + repo := server.NewRepositoryInMemory(24*time.Hour, nil) + service := server.NewService(repo) + logger := log.NewLogfmtLogger(os.Stderr) + handler := server.MakeHTTPHandler(service, repo, logger) + + // Spin up a local HTTP server + server := httptest.NewServer(handler) + defer server.Close() + + // Read an Example ach.File in JSON format + file, err := os.Open("../testdata/ppd-valid.json") + if err != nil { + t.Fatal(err) + } + + // Make our request + req, err := http.NewRequest("POST", server.URL+"/files/create", file) + if err != nil { + t.Fatal(err) + } + resp, err := server.Client().Do(req) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Errorf("got %d HTTP status code", resp.StatusCode) + } +} diff --git a/test/simple-file-creation/main.go b/test/simple-file-creation/main.go new file mode 100644 index 000000000..989eb38cc --- /dev/null +++ b/test/simple-file-creation/main.go @@ -0,0 +1,138 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import ( + "fmt" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // To create a file + fh := ach.NewFileHeader() + fh.ImmediateDestination = "231380104" + fh.ImmediateOrigin = "121042882" + fh.FileCreationDate = time.Now().Format("060102") + fh.ImmediateDestinationName = "Federal Reserve Bank" + fh.ImmediateOriginName = "My Bank Name" + file := ach.NewFile() + file.SetHeader(fh) + + // To create a batch. + // Errors only if payment type is not supported. + bh := ach.NewBatchHeader() + bh.ServiceClassCode = ach.MixedDebitsAndCredits + bh.CompanyName = "Your Company" + bh.CompanyIdentification = file.Header.ImmediateOrigin + bh.StandardEntryClassCode = ach.PPD + bh.CompanyEntryDescription = "Trans. Description" + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh.ODFIIdentification = "121042882" + + batch, _ := ach.NewBatch(bh) + + // To create an entry + entry := ach.NewEntryDetail() + entry.TransactionCode = ach.CheckingCredit + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "81967038518" + entry.Amount = 1000000 + entry.IndividualName = "Wade Arnold" + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.IdentificationNumber = "ABC##jvkdjfuiwn" + entry.Category = ach.CategoryForward + entry.AddendaRecordIndicator = 1 + + // To add one or more optional addenda records for an entry + + addenda := ach.NewAddenda05() + addenda.PaymentRelatedInformation = "bonus pay for amazing work on #OSS" + entry.AddAddenda05(addenda) + + // Entries are added to batches like so: + + batch.AddEntry(entry) + + // When all of the Entries are added to the batch we must create it. + + if err := batch.Create(); err != nil { + fmt.Printf("%T: %s", err, err) + } + + // And batches are added to files much the same way: + + file.AddBatch(batch) + + // Now add a new batch for accepting payments on the web + + bh2 := ach.NewBatchHeader() + bh2.ServiceClassCode = ach.CreditsOnly + bh2.CompanyName = "Your Company" + bh2.CompanyIdentification = file.Header.ImmediateOrigin + bh2.StandardEntryClassCode = ach.WEB + bh2.CompanyEntryDescription = "Subscribe" + bh2.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD + bh2.ODFIIdentification = "121042882" + + batch2, _ := ach.NewBatch(bh2) + + // Add an entry and define if it is a single or recurring payment + // The following is a recurring payment for $7.99 + + entry2 := ach.NewEntryDetail() + entry2.TransactionCode = ach.CheckingCredit + entry2.SetRDFI("231380104") + entry2.DFIAccountNumber = "81967038518" + entry2.Amount = 799 + entry2.IndividualName = "Wade Arnold" + entry2.SetTraceNumber(bh2.ODFIIdentification, 2) + entry2.IdentificationNumber = "#123456" + entry2.DiscretionaryData = "R" + entry2.Category = ach.CategoryForward + entry2.AddendaRecordIndicator = 1 + + // To add one or more optional addenda records for an entry + addenda2 := ach.NewAddenda05() + addenda2.PaymentRelatedInformation = "Monthly Membership Subscription" + entry2.AddAddenda05(addenda2) + + // add the entry to the batch + batch2.AddEntry(entry2) + + // Create and add the second batch + if err := batch2.Create(); err != nil { + fmt.Printf("%T: %s", err, err) + } + file.AddBatch(batch2) + + // Once we added all our batches we must create the file + + if err := file.Create(); err != nil { + fmt.Printf("%T: %s", err, err) + } + + // Finally we wnt to write the file to an io.Writer + w := ach.NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + fmt.Printf("%T: %s", err, err) + } + w.Flush() +} diff --git a/test/simple-file-creation/main_test.go b/test/simple-file-creation/main_test.go new file mode 100644 index 000000000..dd853403e --- /dev/null +++ b/test/simple-file-creation/main_test.go @@ -0,0 +1,24 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/testdata/20110729A.ach b/test/testdata/20110729A.ach similarity index 71% rename from testdata/20110729A.ach rename to test/testdata/20110729A.ach index e2153b518..1e1446bad 100644 --- a/testdata/20110729A.ach +++ b/test/testdata/20110729A.ach @@ -1,175 +1,173 @@ -101 04200001312345678981107291600A094101US BANK NA TEST COMPANY -5225TEST COMPANY 11234567898PPDTEST BUYS 110801110801 1042000010000001 +101 0420000132313801041107291600A094101US BANK NA TEST COMPANY +5225TEST COMPANY 1231380104PPDTEST BUYS 110801110801 1098765430000001 627021200025998412345 0011900000OA313 AYDEN DERS 0098765434200001 -627021200025998412345 0009900000OA235 DYLAN HUNTER 0098765434200001 -627021200025998412345 0015100000OA357 COLTON LOWE 0098765434200001 -627021200025998412345 0026600000OA264 ISAIAH CARPENTER 0098765434200001 -627021200025998412345 0025500000OA363 COOPER BARNETT 0098765434200001 -627021200025998412345 0022800000OA298 KATHERINE GRAVES 0098765434200001 -627021200025998412345 0016100000OA332 JORDAN FRANKLIN 0098765434200001 -627021200025998412345 0022400000OA270 JACOB SIMPSON 0098765434200001 -627021200025998412345 0008900000OA268 ADAM WILLIS 0098765434200001 -627021200025998412345 0026600000OA354 ISAIAH CARPENTER 0098765434200001 -627021200025998412345 0026600000OA333 ISAIAH CARPENTER 0098765434200001 -627021200025998412345 0029700000OA250 JOSE JORDAN 0098765434200001 -627021200025998412345 0026900000OA300 JULIAN PERKINS 0098765434200001 -627021200025998412345 0029000000OA359 JACK GAS 0098765434200001 -627021200025998412345 0029900000OA281 SOFIA WATSON 0098765434200001 -627021200025998412345 0023800000OA316 ARIANNA KING 0098765434200001 -627021200025998412345 0029400000OA292 AYDEN SIMPSON 0098765434200001 -627021200025998412345 0024700000OA319 SOPHIE HENRY 0098765434200001 -627021200025998412345 0005900000OA267 MAKAYLA TERRY 0098765434200001 -627021200025998412345 0016100000OA257 JORDAN FRANKLIN 0098765434200001 -627021200025998412345 0016600000OA349 JASMINE CARTER 0098765434200001 -627021200025998412345 0015800000OA352 JACOB LEE 0098765434200001 -627021200025998412345 0022900000OA233 LIAM RTINEZ 0098765434200001 -627021200025998412345 0018200000OA283 LILY ROBERTSON 0098765434200001 -627021200025998412345 0028200000OA360 MORGAN NELSON 0098765434200001 -627021200025998412345 0018700000OA345 LUCAS THOMPSON 0098765434200001 -627021200025998412345 0022800000OA368 NOAH 0098765434200001 -627021200025998412345 0007600000OA340 ARIANNA LONG 0098765434200001 -627021200025998412345 0029000000OA338 JACK GAS 0098765434200001 -627021200025998412345 0020700000OA272 MORGAN FOX 0098765434200001 -627021200025998412345 0025500000OA342 COOPER BARNETT 0098765434200001 -627021200025998412345 0018500000OA224 JESSICA SILVA 0098765434200001 -627021200025998412345 0007800000OA273 JASMINE CARTER 0098765434200001 -627021200025998412345 0011700000OA299 ISABELLE ERRY 0098765434200001 -627021200025998412345 0006300000OA263 ZACHARY MEDINA 0098765434200001 -627021200025998412345 0001800000OA266 MATTHEW ALLEN 0098765434200001 -627021200025998412345 0015000000OA312 BROOKE SUTTON 0098765434200001 -627021200025998412345 0006600000OA242 ABIGAIL EVANS 0098765434200001 -627021200025998412345 0028000000OA304 ISAAC JORDAN 0098765434200001 -627021200025998412345 0007800000OA269 JUAN GILBERT 0098765434200001 -627021200025998412345 0020800000OA249 KEVIN CASTILLO 0098765434200001 -627021200025998412345 0010600000OA311 EVAN HARVEY 0098765434200001 -627021200025998412345 0019500000OA318 JESUS BRADLEY 0098765434200001 -627021200025998412345 0008100000OA260 MARIAH HERNANDEZ 0098765434200001 -627021200025998412345 0007600000OA361 ARIANNA LONG 0098765434200001 -627021200025998412345 0003700000OA265 ARIANNA BISHOP 0098765434200001 -627021200025998412345 0005900000OA254 CARLOS WILLIS 0098765434200001 -627021200025998412345 0025800000OA337 ISABELLA GREGORY 0098765434200001 -627021200025998412345 0011600000OA307 ALLISON SUTTON 0098765434200001 -627021200025998412345 0016200000OA290 COOPER LEZ 0098765434200001 -627021200025998412345 0010000000OA335 SOPHIA BAILEY 0098765434200001 -627021200025998412345 0017700000OA288 CHRISTIAN PEARSON 0098765434200001 -627021200025998412345 0028000000OA367 ISAAC JORDAN 0098765434200001 -627021200025998412345 0026100000OA244 LUKE CRAIG 0098765434200001 -627021200025998412345 0008300000OA278 GRACE HOWARD 0098765434200001 -627021200025998412345 0020300000OA228 SOPHIE NICHOLS 0098765434200001 -627021200025998412345 0027400000OA275 ALEXA WALKER 0098765434200001 -627021200025998412345 0006700000OA310 ADRIAN HOLLAND 0098765434200001 -627021200025998412345 0015400000OA314 GABRIEL MEDINA 0098765434200001 -627021200025998412345 0021600000OA344 KEVIN WOOD 0098765434200001 -627021200025998412345 0004700000OA262 KADEN POWELL 0098765434200001 -627021200025998412345 0009300000OA232 AVA GIBSON 0098765434200001 -627021200025998412345 0018700000OA303 LUCAS THOMPSON 0098765434200001 -627021200025998412345 0008500000OA308 ANGEL MCKINNEY 0098765434200001 -627021200025998412345 0002100000OA274 SEAN BRADLEY 0098765434200001 -627021200025998412345 0019100000OA245 IAN HOLLAND 0098765434200001 -627021200025998412345 0017500000OA323 ISABELLE FOX 0098765434200001 -627021200025998412345 0027400000OA355 ALEXA WALKER 0098765434200001 -627021200025998412345 0026100000OA226 JASON ANDERSON 0098765434200001 -627021200025998412345 0008600000OA243 GIANNA RUIZ 0098765434200001 -627021200025998412345 0011900000OA348 AYDEN DERS 0098765434200001 -627021200025998412345 0015100000OA336 COLTON LOWE 0098765434200001 -627021200025998412345 0002200000OA239 ARIANA THOMPSON 0098765434200001 -627021200025998412345 0025500000OA294 COOPER BARNETT 0098765434200001 -627021200025998412345 0028400000OA325 CAMILA HOLMES 0098765434200001 -627021200025998412345 0025800000OA358 ISABELLA GREGORY 0098765434200001 -627021200025998412345 0025800000OA259 HUNTER MILLS 0098765434200001 -627021200025998412345 0010000000OA279 SOPHIA BAILEY 0098765434200001 -627021200025998412345 0025600000OA293 ANGEL ORTIZ 0098765434200001 -627021200025998412345 0004600000OA252 JOSIAH GRIFFIN 0098765434200001 -627021200025998412345 0011200000OA364 SEBASTIAN MCDONALD 0098765434200001 -627021200025998412345 0022800000OA309 NOAH 0098765434200001 -627021200025998412345 0020500000OA324 AUBREY GRANT 0098765434200001 -627021200025998412345 0022800000OA256 KEVIN PERKINS 0098765434200001 -627021200025998412345 0014600000OA238 HENRY DAY 0098765434200001 -627021200025998412345 0011200000OA297 SEBASTIAN MCDONALD 0098765434200001 -627021200025998412345 0021100000OA282 CARLOS GREGORY 0098765434200001 -627021200025998412345 0009600000OA350 JACOB MORRISON 0098765434200001 -627021200025998412345 0009000000OA291 NATALIE JACOBS 0098765434200001 -627021200025998412345 0025800000OA285 ISABELLA GREGORY 0098765434200001 -627021200025998412345 0028200000OA339 MORGAN NELSON 0098765434200001 -627021200025998412345 0016100000OA353 JORDAN FRANKLIN 0098765434200001 -627021200025998412345 0016400000OA322 ELI BOWMAN 0098765434200001 -627021200025998412345 0003700000OA253 ANGEL SMITH 0098765434200001 -627021200025998412345 0026300000OA247 DIEGO WASHINGTON 0098765434200001 -627021200025998412345 0001900000OA328 ANGEL BROOKS 0098765434200001 -627021200025998412345 0015100000OA236 BAILEY PEREZ 0098765434200001 -627021200025998412345 0021600000OA301 KEVIN WOOD 0098765434200001 -627021200025998412345 0015800000OA331 JACOB LEE 0098765434200001 -627021200025998412345 0011200000OA343 SEBASTIAN MCDONALD 0098765434200001 -627021200025998412345 0026100000OA330 LUKE CRAIG 0098765434200001 -627021200025998412345 0016000000OA280 ABIGAIL ELTON 0098765434200001 -627021200025998412345 0010000000OA356 SOPHIA BAILEY 0098765434200001 -627021200025998412345 0029400000OA251 JAYDEN LYNCH 0098765434200001 -627021200025998412345 0011600000OA321 DESTINY MCDANIEL 0098765434200001 -627021200025998412345 0016600000OA315 JASMINE CARTER 0098765434200001 -627021200025998412345 0004000000OA306 MARY MARSHALL 0098765434200001 -627021200025998412345 0025600000OA362 ANGEL ORTIZ 0098765434200001 -627021200025998412345 0020800000OA258 SYDNEY REYES 0098765434200001 -627021200025998412345 0022800000OA347 NOAH 0098765434200001 -627021200025998412345 0006100000OA248 SYDNEY BUTLER 0098765434200001 -627021200025998412345 0015500000OA327 SOFIA GOMEZ 0098765434200001 -627021200025998412345 0017100000OA240 MARIA MITCHELL 0098765434200001 -627021200025998412345 0029500000OA241 MICHAEL WALLACE 0098765434200001 -627021200025998412345 0015100000OA305 HAYDEN DES 0098765434200001 -627021200025998412345 0006100000OA317 CAMILA T 0098765434200001 -627021200025998412345 0025600000OA341 ANGEL ORTIZ 0098765434200001 -627021200025998412345 0003600000OA329 LILY COLE 0098765434200001 -627021200025998412345 0012700000OA229 BRANDON WILSON 0098765434200001 -627021200025998412345 0018300000OA230 CONNOR GARDNER 0098765434200001 -627021200025998412345 0028200000OA287 MORGAN NELSON 0098765434200001 -627021200025998412345 0021600000OA365 KEVIN WOOD 0098765434200001 -627021200025998412345 0030000000OA326 ISABEL WEBB 0098765434200001 -627021200025998412345 0025600000OA261 MATTHEW ROSE 0098765434200001 -627021200025998412345 0029000000OA286 JACK GAS 0098765434200001 -627021200025998412345 0027400000OA302 JOSEPH DES 0098765434200001 -627021200025998412345 0013500000OA227 SARA NICHOLS 0098765434200001 -627021200025998412345 0027400000OA334 ALEXA WALKER 0098765434200001 -627021200025998412345 0007900000OA225 AIDEN MILES 0098765434200001 -627021200025998412345 0018700000OA366 LUCAS THOMPSON 0098765434200001 -627021200025998412345 0018000000OA234 CONNOR WILLIAMS 0098765434200001 -627021200025998412345 0009500000OA276 GABRIEL JENSEN 0098765434200001 -627021200025998412345 0015000000OA295 JAMES NEWMAN 0098765434200001 -627021200025998412345 0009600000OA320 JACOB MORRISON 0098765434200001 -627021200025998412345 0015800000OA255 JACOB LEE 0098765434200001 -627021200025998412345 0015100000OA284 COLTON LOWE 0098765434200001 -627021200025998412345 0013000000OA271 JUAN NICHOLS 0098765434200001 -627021200025998412345 0008200000OA237 ZOEY DAVIDSON 0098765434200001 -627021200025998412345 0013100000OA296 MARIA FLORES 0098765434200001 -627021200025998412345 0014000000OA277 SYDNEY TUCKER 0098765434200001 -627021200025998412345 0007600000OA289 ARIANNA LONG 0098765434200001 -627021200025998412345 0028000000OA346 ISAAC JORDAN 0098765434200001 -627021200025998412345 0005900000OA246 JESSICA WALKER 0098765434200001 -627021200025998412345 0008700000OA231 ELIZABETH COLE 0098765434200001 -627021200025998412345 0026100000OA351 LUKE CRAIG 0098765434200001 -822500014503074002900000247980000000000000001234567898 042000010000001 -5220TEST COMPANY 11234567898PPDVERIFY 110801110801 1042000010000003 +627021200025998412345 0009900000OA235 DYLAN HUNTER 0098765434200002 +627021200025998412345 0015100000OA357 COLTON LOWE 0098765434200003 +627021200025998412345 0026600000OA264 ISAIAH CARPENTER 0098765434200004 +627021200025998412345 0025500000OA363 COOPER BARNETT 0098765434200005 +627021200025998412345 0022800000OA298 KATHERINE GRAVES 0098765434200006 +627021200025998412345 0016100000OA332 JORDAN FRANKLIN 0098765434200007 +627021200025998412345 0022400000OA270 JACOB SIMPSON 0098765434200008 +627021200025998412345 0008900000OA268 ADAM WILLIS 0098765434200009 +627021200025998412345 0026600000OA354 ISAIAH CARPENTER 0098765434200010 +627021200025998412345 0026600000OA333 ISAIAH CARPENTER 0098765434200011 +627021200025998412345 0029700000OA250 JOSE JORDAN 0098765434200012 +627021200025998412345 0026900000OA300 JULIAN PERKINS 0098765434200013 +627021200025998412345 0029000000OA359 JACK GAS 0098765434200014 +627021200025998412345 0029900000OA281 SOFIA WATSON 0098765434200015 +627021200025998412345 0023800000OA316 ARIANNA KING 0098765434200016 +627021200025998412345 0029400000OA292 AYDEN SIMPSON 0098765434200017 +627021200025998412345 0024700000OA319 SOPHIE HENRY 0098765434200018 +627021200025998412345 0005900000OA267 MAKAYLA TERRY 0098765434200019 +627021200025998412345 0016100000OA257 JORDAN FRANKLIN 0098765434200020 +627021200025998412345 0016600000OA349 JASMINE CARTER 0098765434200021 +627021200025998412345 0015800000OA352 JACOB LEE 0098765434200022 +627021200025998412345 0022900000OA233 LIAM RTINEZ 0098765434200023 +627021200025998412345 0018200000OA283 LILY ROBERTSON 0098765434200024 +627021200025998412345 0028200000OA360 MORGAN NELSON 0098765434200025 +627021200025998412345 0018700000OA345 LUCAS THOMPSON 0098765434200026 +627021200025998412345 0022800000OA368 NOAH 0098765434200027 +627021200025998412345 0007600000OA340 ARIANNA LONG 0098765434200028 +627021200025998412345 0029000000OA338 JACK GAS 0098765434200029 +627021200025998412345 0020700000OA272 MORGAN FOX 0098765434200030 +627021200025998412345 0025500000OA342 COOPER BARNETT 0098765434200031 +627021200025998412345 0018500000OA224 JESSICA SILVA 0098765434200032 +627021200025998412345 0007800000OA273 JASMINE CARTER 0098765434200033 +627021200025998412345 0011700000OA299 ISABELLE ERRY 0098765434200034 +627021200025998412345 0006300000OA263 ZACHARY MEDINA 0098765434200035 +627021200025998412345 0001800000OA266 MATTHEW ALLEN 0098765434200036 +627021200025998412345 0015000000OA312 BROOKE SUTTON 0098765434200037 +627021200025998412345 0006600000OA242 ABIGAIL EVANS 0098765434200038 +627021200025998412345 0028000000OA304 ISAAC JORDAN 0098765434200039 +627021200025998412345 0007800000OA269 JUAN GILBERT 0098765434200040 +627021200025998412345 0020800000OA249 KEVIN CASTILLO 0098765434200041 +627021200025998412345 0010600000OA311 EVAN HARVEY 0098765434200042 +627021200025998412345 0019500000OA318 JESUS BRADLEY 0098765434200043 +627021200025998412345 0008100000OA260 MARIAH HERNANDEZ 0098765434200044 +627021200025998412345 0007600000OA361 ARIANNA LONG 0098765434200045 +627021200025998412345 0003700000OA265 ARIANNA BISHOP 0098765434200046 +627021200025998412345 0005900000OA254 CARLOS WILLIS 0098765434200047 +627021200025998412345 0025800000OA337 ISABELLA GREGORY 0098765434200048 +627021200025998412345 0011600000OA307 ALLISON SUTTON 0098765434200049 +627021200025998412345 0016200000OA290 COOPER LEZ 0098765434200050 +627021200025998412345 0010000000OA335 SOPHIA BAILEY 0098765434200051 +627021200025998412345 0017700000OA288 CHRISTIAN PEARSON 0098765434200052 +627021200025998412345 0028000000OA367 ISAAC JORDAN 0098765434200053 +627021200025998412345 0026100000OA244 LUKE CRAIG 0098765434200054 +627021200025998412345 0008300000OA278 GRACE HOWARD 0098765434200055 +627021200025998412345 0020300000OA228 SOPHIE NICHOLS 0098765434200056 +627021200025998412345 0027400000OA275 ALEXA WALKER 0098765434200057 +627021200025998412345 0006700000OA310 ADRIAN HOLLAND 0098765434200058 +627021200025998412345 0015400000OA314 GABRIEL MEDINA 0098765434200059 +627021200025998412345 0021600000OA344 KEVIN WOOD 0098765434200060 +627021200025998412345 0004700000OA262 KADEN POWELL 0098765434200061 +627021200025998412345 0009300000OA232 AVA GIBSON 0098765434200062 +627021200025998412345 0018700000OA303 LUCAS THOMPSON 0098765434200063 +627021200025998412345 0008500000OA308 ANGEL MCKINNEY 0098765434200064 +627021200025998412345 0002100000OA274 SEAN BRADLEY 0098765434200065 +627021200025998412345 0019100000OA245 IAN HOLLAND 0098765434200066 +627021200025998412345 0017500000OA323 ISABELLE FOX 0098765434200067 +627021200025998412345 0027400000OA355 ALEXA WALKER 0098765434200068 +627021200025998412345 0026100000OA226 JASON ANDERSON 0098765434200069 +627021200025998412345 0008600000OA243 GIANNA RUIZ 0098765434200070 +627021200025998412345 0011900000OA348 AYDEN DERS 0098765434200071 +627021200025998412345 0015100000OA336 COLTON LOWE 0098765434200072 +627021200025998412345 0002200000OA239 ARIANA THOMPSON 0098765434200073 +627021200025998412345 0025500000OA294 COOPER BARNETT 0098765434200074 +627021200025998412345 0028400000OA325 CAMILA HOLMES 0098765434200075 +627021200025998412345 0025800000OA358 ISABELLA GREGORY 0098765434200076 +627021200025998412345 0025800000OA259 HUNTER MILLS 0098765434200077 +627021200025998412345 0010000000OA279 SOPHIA BAILEY 0098765434200078 +627021200025998412345 0025600000OA293 ANGEL ORTIZ 0098765434200079 +627021200025998412345 0004600000OA252 JOSIAH GRIFFIN 0098765434200080 +627021200025998412345 0011200000OA364 SEBASTIAN MCDONALD 0098765434200081 +627021200025998412345 0022800000OA309 NOAH 0098765434200082 +627021200025998412345 0020500000OA324 AUBREY GRANT 0098765434200083 +627021200025998412345 0022800000OA256 KEVIN PERKINS 0098765434200084 +627021200025998412345 0014600000OA238 HENRY DAY 0098765434200085 +627021200025998412345 0011200000OA297 SEBASTIAN MCDONALD 0098765434200086 +627021200025998412345 0021100000OA282 CARLOS GREGORY 0098765434200087 +627021200025998412345 0009600000OA350 JACOB MORRISON 0098765434200088 +627021200025998412345 0009000000OA291 NATALIE JACOBS 0098765434200089 +627021200025998412345 0025800000OA285 ISABELLA GREGORY 0098765434200090 +627021200025998412345 0028200000OA339 MORGAN NELSON 0098765434200091 +627021200025998412345 0016100000OA353 JORDAN FRANKLIN 0098765434200092 +627021200025998412345 0016400000OA322 ELI BOWMAN 0098765434200093 +627021200025998412345 0003700000OA253 ANGEL SMITH 0098765434200094 +627021200025998412345 0026300000OA247 DIEGO WASHINGTON 0098765434200095 +627021200025998412345 0001900000OA328 ANGEL BROOKS 0098765434200096 +627021200025998412345 0015100000OA236 BAILEY PEREZ 0098765434200097 +627021200025998412345 0021600000OA301 KEVIN WOOD 0098765434200098 +627021200025998412345 0015800000OA331 JACOB LEE 0098765434200099 +627021200025998412345 0011200000OA343 SEBASTIAN MCDONALD 0098765434200100 +627021200025998412345 0026100000OA330 LUKE CRAIG 0098765434200101 +627021200025998412345 0016000000OA280 ABIGAIL ELTON 0098765434200102 +627021200025998412345 0010000000OA356 SOPHIA BAILEY 0098765434200103 +627021200025998412345 0029400000OA251 JAYDEN LYNCH 0098765434200104 +627021200025998412345 0011600000OA321 DESTINY MCDANIEL 0098765434200105 +627021200025998412345 0016600000OA315 JASMINE CARTER 0098765434200106 +627021200025998412345 0004000000OA306 MARY MARSHALL 0098765434200107 +627021200025998412345 0025600000OA362 ANGEL ORTIZ 0098765434200108 +627021200025998412345 0020800000OA258 SYDNEY REYES 0098765434200109 +627021200025998412345 0022800000OA347 NOAH 0098765434200110 +627021200025998412345 0006100000OA248 SYDNEY BUTLER 0098765434200111 +627021200025998412345 0015500000OA327 SOFIA GOMEZ 0098765434200112 +627021200025998412345 0017100000OA240 MARIA MITCHELL 0098765434200113 +627021200025998412345 0029500000OA241 MICHAEL WALLACE 0098765434200114 +627021200025998412345 0015100000OA305 HAYDEN DES 0098765434200115 +627021200025998412345 0006100000OA317 CAMILA T 0098765434200116 +627021200025998412345 0025600000OA341 ANGEL ORTIZ 0098765434200117 +627021200025998412345 0003600000OA329 LILY COLE 0098765434200118 +627021200025998412345 0012700000OA229 BRANDON WILSON 0098765434200119 +627021200025998412345 0018300000OA230 CONNOR GARDNER 0098765434200120 +627021200025998412345 0028200000OA287 MORGAN NELSON 0098765434200121 +627021200025998412345 0021600000OA365 KEVIN WOOD 0098765434200122 +627021200025998412345 0030000000OA326 ISABEL WEBB 0098765434200123 +627021200025998412345 0025600000OA261 MATTHEW ROSE 0098765434200124 +627021200025998412345 0029000000OA286 JACK GAS 0098765434200125 +627021200025998412345 0027400000OA302 JOSEPH DES 0098765434200126 +627021200025998412345 0013500000OA227 SARA NICHOLS 0098765434200127 +627021200025998412345 0027400000OA334 ALEXA WALKER 0098765434200128 +627021200025998412345 0007900000OA225 AIDEN MILES 0098765434200129 +627021200025998412345 0018700000OA366 LUCAS THOMPSON 0098765434200130 +627021200025998412345 0018000000OA234 CONNOR WILLIAMS 0098765434200131 +627021200025998412345 0009500000OA276 GABRIEL JENSEN 0098765434200132 +627021200025998412345 0015000000OA295 JAMES NEWMAN 0098765434200133 +627021200025998412345 0009600000OA320 JACOB MORRISON 0098765434200134 +627021200025998412345 0015800000OA255 JACOB LEE 0098765434200135 +627021200025998412345 0015100000OA284 COLTON LOWE 0098765434200136 +627021200025998412345 0013000000OA271 JUAN NICHOLS 0098765434200137 +627021200025998412345 0008200000OA237 ZOEY DAVIDSON 0098765434200138 +627021200025998412345 0013100000OA296 MARIA FLORES 0098765434200139 +627021200025998412345 0014000000OA277 SYDNEY TUCKER 0098765434200140 +627021200025998412345 0007600000OA289 ARIANNA LONG 0098765434200141 +627021200025998412345 0028000000OA346 ISAAC JORDAN 0098765434200142 +627021200025998412345 0005900000OA246 JESSICA WALKER 0098765434200143 +627021200025998412345 0008700000OA231 ELIZABETH COLE 0098765434200144 +627021200025998412345 0026100000OA351 LUKE CRAIG 0098765434200145 +82250001450307400290002479800000000000000000231380104 098765430000001 +5220TEST COMPANY 1231380104PPDVERIFY 110801110801 1098765430000003 622021200025998412345 0000001800OA370 CHASE BLACK 0098765434200001 -622021200025998412345 0000001300OA379 ANDREA BUTLER 0098765434200001 -622021200025998412345 0000000700OA371 BLAKE REYES 0098765434200001 -622021200025998412345 0000000300OA385 CONNOR MEDINA 0098765434200001 -622021200025998412345 0000001100OA369 CHASE BLACK 0098765434200001 -622021200025998412345 0000001300OA384 BRANDON ARMSTRONG 0098765434200001 -622021200025998412345 0000000300OA375 LUIS MEYER 0098765434200001 -622021200025998412345 0000001300OA374 BLAKE HICKS 0098765434200001 -622021200025998412345 0000000400OA372 BLAKE REYES 0098765434200001 -622021200025998412345 0000000100OA377 DOMINIC RAY 0098765434200001 -622021200025998412345 0000001900OA382 JONATHAN MORALES 0098765434200001 -622021200025998412345 0000000400OA381 JONATHAN MORALES 0098765434200001 -622021200025998412345 0000000200OA378 DOMINIC RAY 0098765434200001 -622021200025998412345 0000001100OA376 LUIS MEYER 0098765434200001 -622021200025998412345 0000002000OA373 BLAKE HICKS 0098765434200001 -622021200025998412345 0000000200OA386 CONNOR MEDINA 0098765434200001 -622021200025998412345 0000001600OA380 ANDREA BUTLER 0098765434200001 -622021200025998412345 0000001200OA383 BRANDON ARMSTRONG 0098765434200001 -822000001800381600360000000000000000000001721234567898 042000010000003 -5220TEST COMPANY 11234567898PPDTEST SALES110801110801 1042000010000002 -822000000000000000000000000000000000000000001234567898 042000010000002 -5225 FV3 CA1234567898IATTEST BUYS USDCAD110802 1042000010000004 -6270910502340 0012200000998412345 1098765430420000 +622021200025998412345 0000001300OA379 ANDREA BUTLER 0098765434200002 +622021200025998412345 0000000700OA371 BLAKE REYES 0098765434200003 +622021200025998412345 0000000300OA385 CONNOR MEDINA 0098765434200004 +622021200025998412345 0000001100OA369 CHASE BLACK 0098765434200005 +622021200025998412345 0000001300OA384 BRANDON ARMSTRONG 0098765434200006 +622021200025998412345 0000000300OA375 LUIS MEYER 0098765434200007 +622021200025998412345 0000001300OA374 BLAKE HICKS 0098765434200008 +622021200025998412345 0000000400OA372 BLAKE REYES 0098765434200009 +622021200025998412345 0000000100OA377 DOMINIC RAY 0098765434200010 +622021200025998412345 0000001900OA382 JONATHAN MORALES 0098765434200011 +622021200025998412345 0000000400OA381 JONATHAN MORALES 0098765434200012 +622021200025998412345 0000000200OA378 DOMINIC RAY 0098765434200013 +622021200025998412345 0000001100OA376 LUIS MEYER 0098765434200014 +622021200025998412345 0000002000OA373 BLAKE HICKS 0098765434200015 +622021200025998412345 0000000200OA386 CONNOR MEDINA 0098765434200016 +622021200025998412345 0000001600OA380 ANDREA BUTLER 0098765434200017 +622021200025998412345 0000001200OA383 BRANDON ARMSTRONG 0098765434200018 +82200000180038160036000000000000000000017200231380104 098765430000003 +5225 FV3 CA231380104IATTEST BUYS USDCAD110802 1098765430000004 +627091050234007 0012200000998412345 1098765430000001 710WEB DIEGO MAY 0000001 711TEST COMPANY 123 EASY STREET 0000001 712ANYTOWN*KS\ US*12345\ 0000001 @@ -177,7 +175,7 @@ 714CENTRAL 01021200025 CA 0000001 715OA391 PO Box 450 0000001 716Metropolis*ON\ CA*01234\ 0000001 -6270910502340 0004000000998412345 1098765430420000 +627091050234007 0004000000998412345 1098765430000002 710WEB JOSHUA SILVA 0000002 711TEST COMPANY 123 EASY STREET 0000002 712ANYTOWN*KS\ US*12345\ 0000002 @@ -185,7 +183,7 @@ 714CENTRAL 01021200025 CA 0000002 715OA389 PO Box 230 0000002 716Metropolis*ON\ CA*01234\ 0000002 -6270910502340 0012200000998412345 1098765430420000 +627091050234007 0012200000998412345 1098765430000003 710WEB DIEGO MAY 0000003 711TEST COMPANY 123 EASY STREET 0000003 712ANYTOWN*KS\ US*12345\ 0000003 @@ -193,7 +191,7 @@ 714CENTRAL 01021200025 CA 0000003 715OA398 PO Box 450 0000003 716Metropolis*ON\ CA*01234\ 0000003 -6270910502340 0026800000998412345 1098765430420000 +6270910502340007 0026800000998412345 1098765430000004 710WEB ZOEY PAYNE 0000004 711TEST COMPANY 123 EASY STREET 0000004 712ANYTOWN*KS\ US*12345\ 0000004 @@ -201,7 +199,7 @@ 714CENTRAL 01021200025 CA 0000004 715OA392 PO Box 150 0000004 716Metropolis*ON\ CA*01234\ 0000004 -6270910502340 0012200000998412345 1098765430420000 +627091050234007 0012200000998412345 1098765430000005 710WEB DIEGO MAY 0000005 711TEST COMPANY 123 EASY STREET 0000005 712ANYTOWN*KS\ US*12345\ 0000005 @@ -209,7 +207,7 @@ 714CENTRAL 01021200025 CA 0000005 715OA395 PO Box 450 0000005 716Metropolis*ON\ CA*01234\ 0000005 -6270910502340 0014800000998412345 1098765430420000 +627091050234007 0014800000998412345 1098765430000006 710WEB DYLAN RAMOS 0000006 711TEST COMPANY 123 EASY STREET 0000006 712ANYTOWN*KS\ US*12345\ 0000006 @@ -217,7 +215,7 @@ 714CENTRAL 01021200025 CA 0000006 715OA390 PO Box 800 0000006 716Metropolis*ON\ CA*01234\ 0000006 -6270910502340 0026800000998412345 1098765430420000 +627091050234007 0026800000998412345 1098765430000007 710WEB ZOEY PAYNE 0000007 711TEST COMPANY 123 EASY STREET 0000007 712ANYTOWN*KS\ US*12345\ 0000007 @@ -225,7 +223,7 @@ 714CENTRAL 01021200025 CA 0000007 715OA396 PO Box 150 0000007 716Metropolis*ON\ CA*01234\ 0000007 -6270910502340 0020300000998412345 1098765430420000 +627091050234007 0020300000998412345 1098765430000008 710WEB JAMES ROMERO 0000008 711TEST COMPANY 123 EASY STREET 0000008 712ANYTOWN*KS\ US*12345\ 0000008 @@ -233,7 +231,7 @@ 714CENTRAL 01021200025 CA 0000008 715OA393 PO Box 810 0000008 716Metropolis*ON\ CA*01234\ 0000008 -6270910502340 0008700000998412345 1098765430420000 +627091050234007 0008700000998412345 1098765430000009 710WEB KEVIN LARSON 0000009 711TEST COMPANY 123 EASY STREET 0000009 712ANYTOWN*KS\ US*12345\ 0000009 @@ -241,7 +239,7 @@ 714CENTRAL 01021200025 CA 0000009 715OA387 PO Box 340 0000009 716Metropolis*ON\ CA*01234\ 0000009 -6270910502340 0008700000998412345 1098765430420000 +627091050234007 0008700000998412345 1098765430000010 710WEB KEVIN LARSON 0000010 711TEST COMPANY 123 EASY STREET 0000010 712ANYTOWN*KS\ US*12345\ 0000010 @@ -249,7 +247,7 @@ 714CENTRAL 01021200025 CA 0000010 715OA394 PO Box 340 0000010 716Metropolis*ON\ CA*01234\ 0000010 -6270910502340 0025100000998412345 1098765430420000 +627091050234007 0025100000998412345 1098765430000011 710WEB SEBASTIAN NEWMAN 0000011 711TEST COMPANY 123 EASY STREET 0000011 712ANYTOWN*KS\ US*12345\ 0000011 @@ -257,7 +255,7 @@ 714CENTRAL 01021200025 CA 0000011 715OA388 PO Box 600 0000011 716Metropolis*ON\ CA*01234\ 0000011 -6270910502340 0026800000998412345 1098765430420000 +627091050234007 0026800000998412345 1098765430000012 710WEB ZOEY PAYNE 0000012 711TEST COMPANY 123 EASY STREET 0000012 712ANYTOWN*KS\ US*12345\ 0000012 @@ -265,7 +263,7 @@ 714CENTRAL 01021200025 CA 0000012 715OA399 PO Box 150 0000012 716Metropolis*ON\ CA*01234\ 0000012 -6270910502340 0008700000998412345 1098765430420000 +627091050234007 0008700000998412345 1098765430000013 710WEB KEVIN LARSON 0000013 711TEST COMPANY 123 EASY STREET 0000013 712ANYTOWN*KS\ US*12345\ 0000013 @@ -273,23 +271,23 @@ 714CENTRAL 01021200025 CA 0000013 715OA397 PO Box 340 0000013 716Metropolis*ON\ CA*01234\ 0000013 -822500010401183652990000020730000000000000001234567898 042000010000004 -5220 FV3 CA1234567898IATVERIFY USDCAD110802 1042000010000005 -6220910502340 0000001200998412345 1098765430420000 -710WEB LEAH BRYANT 0000014 -711TEST COMPANY 123 EASY STREET 0000014 -712ANYTOWN*KS\ US*12345\ 0000014 -713U.S. BANK 0104200001 US 0000014 -714CENTRAL 01021200025 CA 0000014 -715OA400 PO Box 410 0000014 -716Metropolis*ON\ CA*01234\ 0000014 -6220910502340 0000000100998412345 1098765430420000 -710WEB LEAH BRYANT 0000015 -711TEST COMPANY 123 EASY STREET 0000015 -712ANYTOWN*KS\ US*12345\ 0000015 -713U.S. BANK 0104200001 US 0000015 -714CENTRAL 01021200025 CA 0000015 -715OA401 PO Box 410 0000015 -716Metropolis*ON\ CA*01234\ 0000015 -822000001600182100460000000000000000000000131234567898 042000010000005 -9000005000030000002830482135671000026871000000000000185 \ No newline at end of file +82250001040118365299000207300000000000000000231380104 098765430000004 +5220 FV3 CA231380104IATVERIFY USDCAD110802 1098765430000005 +622091050234007 0000001200998412345 1098765430000001 +710WEB LEAH BRYANT 0000001 +711TEST COMPANY 123 EASY STREET 0000001 +712ANYTOWN*KS\ US*12345\ 0000001 +713U.S. BANK 0104200001 US 0000001 +714CENTRAL 01021200025 CA 0000001 +715OA400 PO Box 410 0000001 +716Metropolis*ON\ CA*01234\ 0000001 +622091050234007 0000000100998412345 1098765430000002 +710WEB LEAH BRYANT 0000002 +711TEST COMPANY 123 EASY STREET 0000002 +712ANYTOWN*KS\ US*12345\ 0000002 +713U.S. BANK 0104200001 US 0000002 +714CENTRAL 01021200025 CA 0000002 +715OA401 PO Box 410 0000002 +716Metropolis*ON\ CA*01234\ 0000002 +82200000160018210046000000000000000000001300231380104 098765430000005 +9000005000030000002830482135671002687100000000000018500 \ No newline at end of file diff --git a/testdata/20110805A.ach b/test/testdata/20110805A.ach similarity index 90% rename from testdata/20110805A.ach rename to test/testdata/20110805A.ach index 799cd5a26..696cda058 100644 --- a/testdata/20110805A.ach +++ b/test/testdata/20110805A.ach @@ -1,5 +1,5 @@ -101 04200001301234567891108052100A094101US BANK NA EXAMPLE COMPANY -5225EXAMPLE COMPANY 0123456789PPDBUY WIDGET110808110808 1042000010000001 +101 04200001302313801041108052100A094101US BANK NA EXAMPLE COMPANY +5225EXAMPLE COMPANY 0231380104PPDBUY WIDGET110808110808 1042000010000001 627021200025998412345 0000027000A271 JULIAN PRICE 0042000010000001 627021200025998412345 0000062000A272 SYDNEY BUTLER 0042000010000002 627021200025998412345 0000209000A273 KEVIN CASTILLO 0042000010000003 @@ -25,10 +25,8 @@ 627021200025998412345 0000255000A296 RYAN FLETCHER 0042000010000023 627021200025998412345 0000149000A297 PAYTON MCCOY 0042000010000024 627021200025998412345 0000217000A298 DESTINY COLEMAN 0042000010000025 -822500002500530000500000046100000000000000000123456789 042000010000001 -5220EXAMPLE COMPANY 0123456789PPDREFUND 110808110808 1042000010000002 -822000000000000000000000000000000000000000000123456789 042000010000002 -5220EXAMPLE COMPANY 0123456789PPDVERIFY 110808110808 1042000010000003 +822500002500530000500000046100000000000000000231380104 042000010000001 +5220EXAMPLE COMPANY 0231380104PPDVERIFY 110808110808 1042000010000003 622021200025998412345 0000000008A251 NATHAN NELSON 0042000010000001 622021200025998412345 0000000010A252 NATHAN NELSON 0042000010000002 622021200025998412345 0000000002A253 CHARLES REYES 0042000010000003 @@ -47,8 +45,8 @@ 622021200025998412345 0000000009A268 WILLIAM HUDSON 0042000010000016 622021200025998412345 0000000013A269 JOSHUA OBRIEN 0042000010000017 622021200025998412345 0000000009A270 JOSHUA OBRIEN 0042000010000018 -822000001800381600360000000000000000000001760123456789 042000010000003 -5225ABC INC FV3 CA0123456789IATBUY WIDGETUSDCAD110808 1042000010000004 +822000001800381600360000000000000000000001760231380104 042000010000003 +5225ABC INC FV3 CA0231380104IATBUY WIDGETUSDCAD110808 1042000010000004 6270910502340007 0000109000998412345 1042000010000001 710WEB000000000000000000 HAYDEN BANKS 0000001 711EXAMPLE COMPANY 123 EASY STREET 0000001 @@ -73,8 +71,8 @@ 714CENTRAL 01021200025 CA 0000003 715A293 PO Box 740 0000003 716Metropolis*ON\ CA*01234\ 0000003 -822500002400273150690000004910000000000000000123456789 042000010000004 -5220 FV3 CA0123456789IATVERIFY USDCAD110808 1042000010000005 +822500002400273150690000004910000000000000000231380104 042000010000004 +5220 FV3 CA0231380104IATVERIFY USDCAD110808 1042000010000005 6220910502340007 0000000018998412345 1042000010000001 710WEB000000000000000000 AIDAN BANKS 0000001 711EXAMPLE COMPANY 123 EASY STREET 0000001 @@ -91,5 +89,5 @@ 714CENTRAL 01021200025 CA 0000002 715A258 PO Box 160 0000002 716Metropolis*ON\ CA*01234\ 0000002 -822000001600182100460000000000000000000000240123456789 042000010000005 -9000005000010000000830136685201000005101000000000000200 +822000001600182100460000000000000000000000240231380104 042000010000005 +9000005000010000000830136685201000005101000000000000200000000000000000000000000000000000000000 diff --git a/test/testdata/20180713-IAT.ach b/test/testdata/20180713-IAT.ach new file mode 100644 index 000000000..af653d272 --- /dev/null +++ b/test/testdata/20180713-IAT.ach @@ -0,0 +1,30 @@ +101 031300012 2313801041807130000A094101Federal Reserve Bank My Bank Name +5220 FF3 US231380104 IATTRADEPAYMTCADUSD010101 1231380100000001 +6221210428820007 0000100000231380104 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +82200000080012104288000000000000000000100000 231380100000001 +5220 FF3 US231380104 IATTRADEPAYMTCADUSD010101 1231380100000002 +6271210428820007 0000002000231380104 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +82200000080012104288000000002000000000000000 231380100000002 +9000002000003000000160024208576000000002000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/20180716-IAT-A17-A18.ach b/test/testdata/20180716-IAT-A17-A18.ach new file mode 100644 index 000000000..b6aca0edc --- /dev/null +++ b/test/testdata/20180716-IAT-A17-A18.ach @@ -0,0 +1,40 @@ +101 031300012 2313801041807160000A094101Federal Reserve Bank My Bank Name +5220 FF3 US231380104 IATTRADEPAYMTCADUSD010101 1231380100000001 +6221210428820007 0000100000231380104 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +717Transfer of money from one country to another 00020000001 +718Bank of Germany 01987987987654654 DE 00010000001 +718Bank of Spain 01987987987123123 ES 00020000001 +718Bank of France 01456456456987987 FR 00030000001 +718Bank of Turkey 0112323138010410 TR 00040000001 +718Bank of United Kingdom 012313801040231380104023138010401234GB 00050000001 +82200000150012104288000000000000000000100000 231380100000001 +5220 FF3 US231380104 IATTRADEPAYMTCADUSD010101 1231380100000002 +6271210428820007 0000002000231380104 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +717Transfer of money from one country to another 00020000001 +718Bank of Germany 01987987987654654 DE 00010000001 +718Bank of Spain 01987987987123123 ES 00020000001 +718Bank of France 01456456456987987 FR 00030000001 +718Bank of Turkey 0112323138010410 TR 00040000001 +718Bank of United Kingdom 012313801040231380104023138010401234GB 00050000001 +82200000150012104288000000002000000000000000 231380100000002 +9000002000004000000300024208576000000002000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/20180716-IAT-A17.ach b/test/testdata/20180716-IAT-A17.ach new file mode 100644 index 000000000..bead65ec5 --- /dev/null +++ b/test/testdata/20180716-IAT-A17.ach @@ -0,0 +1,30 @@ +101 031300012 2313801041807160000A094101Federal Reserve Bank My Bank Name +5220 FF3 US231380104 IATTRADEPAYMTCADUSD010101 1231380100000001 +6221210428820007 0000100000231380104 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +717Transfer of money from one country to another 00020000001 +82200000100012104288000000000000000000100000 231380100000001 +5220 FF3 US231380104 IATTRADEPAYMTCADUSD010101 1231380100000002 +6271210428820007 0000002000231380104 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +82200000090012104288000000002000000000000000 231380100000002 +9000002000003000000190024208576000000002000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/FISERV-ZEROFILE-PIMRET825324_032720_110221.ach b/test/testdata/FISERV-ZEROFILE-PIMRET825324_032720_110221.ach new file mode 100644 index 000000000..04f45ecc1 --- /dev/null +++ b/test/testdata/FISERV-ZEROFILE-PIMRET825324_032720_110221.ach @@ -0,0 +1,10 @@ +101 100067554 1823273902003271043 094101PIMRET825324 FISERV +9000000000001000000000000000000000000000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/testdata/Iat-invalidAddenda13.ach b/test/testdata/Iat-invalidAddenda13.ach new file mode 100644 index 000000000..9fba3a654 --- /dev/null +++ b/test/testdata/Iat-invalidAddenda13.ach @@ -0,0 +1,20 @@ +101 121042882 2313801041812180000A094101Bank My Bank Name +5225 FF3 US123456789 IATTRADEPAYMTCADUSD181219 1231380100000001 +6221210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713WellsFargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +718Bank of France 01456456456987987 FR 00010000001 +82250000100012104288000000000000000000100000 231380100000001 +9000001000002000000100012104288000000000000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/README.md b/test/testdata/README.md new file mode 100644 index 000000000..6f8d4f2d2 --- /dev/null +++ b/test/testdata/README.md @@ -0,0 +1,3 @@ +## Test Data + +Data files utilized for integration tests. \ No newline at end of file diff --git a/test/testdata/adv-invalidBatchEntries.ach b/test/testdata/adv-invalidBatchEntries.ach new file mode 100644 index 000000000..7918cd9b5 --- /dev/null +++ b/test/testdata/adv-invalidBatchEntries.ach @@ -0,0 +1,9 @@ +101 231380104 1210428821811130000A094101Federal Reserve Bank My Bank Name +5280Company Name, In 121042882 ADVAccounting 181114 0121042880000001 +799R07099912340000015 09101298Authorization Revoked 000000000000000 +828000000200462760200000000000000025000000000000000000050000Company Name, Inc 121042880000001 +90000010000010000000200462760200000000000000025000000000000000000050000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/adv-invalidFileControl.ach b/test/testdata/adv-invalidFileControl.ach new file mode 100644 index 000000000..47aedfd0b --- /dev/null +++ b/test/testdata/adv-invalidFileControl.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821811130000A094101Federal Reserve Bank My Bank Name +5280Company Name, In 121042882 ADVAccounting 181114 0121042880000001 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500001 +682231380104744-5678-99 00000025000012104288211139 Name 0011000010500002 +828000000200462760200000000000000025000000000000000000050000Company Name, Inc 121042880000001 +90000010000010000000000462760200000000000000025000000000000000000050000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/adv-noFileControl.ach b/test/testdata/adv-noFileControl.ach new file mode 100644 index 000000000..d68db900d --- /dev/null +++ b/test/testdata/adv-noFileControl.ach @@ -0,0 +1,5 @@ +101 231380104 1210428821811130000A094101Federal Reserve Bank My Bank Name +5280Company Name, In 121042882 ADVAccounting 181114 0121042880000001 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500001 +682231380104744-5678-99 00000025000012104288211139 Name 0011000010500002 +828000000200462760200000000000000025000000000000000000050000Company Name, Inc 121042880000001 \ No newline at end of file diff --git a/test/testdata/adv-return.json b/test/testdata/adv-return.json new file mode 100644 index 000000000..854e49123 --- /dev/null +++ b/test/testdata/adv-return.json @@ -0,0 +1,79 @@ +{ + "id": "ADVReturn", + "fileHeader": { + "id": "ADVReturn", + "immediateDestination": "231380104", + "immediateOrigin": "121042882", + "fileCreationDate": "2018-10-08T00:00:00Z", + "fileCreationTime": "0000-01-01T00:00:00Z", + "fileIDModifier": "A", + "immediateDestinationName": "Citadel", + "immediateOriginName": "Wells Fargo" + }, + "batches": [ + { + "batchHeader": { + "id": "ADVReturn", + "serviceClassCode": 280, + "companyName": "Wells Fargo", + "companyIdentification": "121042882", + "standardEntryClassCode": "ADV", + "companyEntryDescription": "Trans. Des", + "effectiveEntryDate": "2018-10-09T00:00:00Z", + "ODFIIdentification": "12104288", + "batchNumber": 1 + }, + "advEntryDetails": [ + { + "id": "ADVReturn", + "transactionCode": 81, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "81967038518 ", + "amount": 100000, + "adviceRoutingNumber": "121042882", + "fileIdentification": "11131", + "achOperatorData": "", + "individualName": "Steven Tander ", + "discretionaryData": " ", + "addendaRecordIndicator": 1, + "achOperatorRoutingNumber": "01100001", + "julianDay": 2, + "sequenceNumber": 1, + "category": "Return", + "addenda99": { + "id": "ADVReturn", + "typeCode": "99", + "returnCode": "R14", + "originalTrace": "12312312", + "dateOfDeath": "190102", + "originalDFI": "98765432" + } + } + ], + "advBatchControl": { + "id": "ADVReturn", + "serviceClassCode": 280, + "entryAddendaCount": 1, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000, + "companyIdentification": "121042882", + "ODFIIdentification": "12104288", + "batchNumber": 1 + } + } + ], + "IATBatches": null, + "fileADVControl": { + "id": "ADVReturn", + "batchCount": 1, + "blockCount": 1, + "entryAddendaCount": 1, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000 + }, + "NotificationOfChange": null, + "ReturnEntries": null +} diff --git a/test/testdata/adv-valid.json b/test/testdata/adv-valid.json new file mode 100644 index 000000000..688ea2d8c --- /dev/null +++ b/test/testdata/adv-valid.json @@ -0,0 +1,70 @@ +{ + "id": "adv-01", + "fileHeader": { + "id": "adv-01", + "immediateDestination": "231380104", + "immediateOrigin": "121042882", + "fileCreationDate": "2018-10-08T00:00:00Z", + "fileCreationTime": "0000-01-01T00:00:00Z", + "fileIDModifier": "A", + "immediateDestinationName": "Citadel", + "immediateOriginName": "Wells Fargo" + }, + "batches": [ + { + "batchHeader": { + "id": "adv-01", + "serviceClassCode": 280, + "companyName": "Wells Fargo", + "companyIdentification": "121042882", + "standardEntryClassCode": "ADV", + "companyEntryDescription": "Trans. Des", + "effectiveEntryDate": "2018-10-09T00:00:00Z", + "ODFIIdentification": "12104288", + "batchNumber": 1 + }, + "advEntryDetails": [ + { + "id": "adv-01", + "transactionCode": 81, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "81967038518 ", + "amount": 100000, + "adviceRoutingNumber": "121042882", + "fileIdentification": "11131", + "achOperatorData": "", + "individualName": "Steven Tander ", + "discretionaryData": " ", + "addendaRecordIndicator": 0, + "achOperatorRoutingNumber": "01100001", + "julianDay": 2, + "sequenceNumber": 1 + } + ], + "advBatchControl": { + "id": "adv-01", + "serviceClassCode": 280, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000, + "companyIdentification": "121042882", + "ODFIIdentification": "12104288", + "batchNumber": 1 + } + } + ], + "IATBatches": null, + "fileADVControl": { + "id": "adv-01", + "batchCount": 1, + "blockCount": 1, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000 + }, + "NotificationOfChange": null, + "ReturnEntries": null +} diff --git a/test/testdata/bh-ed-ad-bh-ed-ad-ed-ad.ach b/test/testdata/bh-ed-ad-bh-ed-ad-ed-ad.ach new file mode 100644 index 000000000..aac19cb97 --- /dev/null +++ b/test/testdata/bh-ed-ad-bh-ed-ad-ed-ad.ach @@ -0,0 +1,8 @@ +5225Adam Shannon MOOVYYYYYYPPDtest trans2005262005271491273976360000188 +62627397636915XXXXXXXXXX1 0000000146ae1300b1c1cac5cVeridian Credit Union te1273976361273620 +799R02273976368613175 27397636 273976361273620 +5220Robert Smith MOOVYYYYYYPPDaccount va2005262005271491323274270000085 +6212739763691XXXXXXXXXXX2 0000000008dcd5f1ce42ae2e1Adam Shannon ac1323274270765782 +799R03273976360000001 32327427 323274270765782 +6212739763691XXXXXXXXXXX2 0000000019908ac8a237911d6Adam Shannon ac1323274270765784 +799R03273976360000001 32327427 323274270765784 \ No newline at end of file diff --git a/test/testdata/contested_addenda.txt b/test/testdata/contested_addenda.txt new file mode 100644 index 000000000..a544efb68 --- /dev/null +++ b/test/testdata/contested_addenda.txt @@ -0,0 +1,10 @@ +101 123456780 0610001462208061450A094101 SimBank +5200SimFintech 123123123 WEBHammerTime 220307 1364275030000001 +621123456780122455799 0000001560 Bruce Wayne S 1364275034310088 +799R72123456780000069 75639218 7563921800000010670112345678000007021868 364275034310088 +82000000020012345678000000000000000000001560123123123 364275030000001 +9000001000001000000020012345678000000000000000000001560 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/testdata/cor-example.ach b/test/testdata/cor-example.ach new file mode 100644 index 000000000..97e4e5037 --- /dev/null +++ b/test/testdata/cor-example.ach @@ -0,0 +1,10 @@ +101 23138010401210428821908291236A094101Federal Reserve Bank My Bank Name +5220Your Company, in 121042882 CORVendor Pay 000000 1121042880000001 +621231380104744-5678-99 0000000000location #23 Best Co. #23 S 1121042880000001 +798C01121042880000001 121042881918171614 091012980000088 +82200000020023138010000000000000000000000000121042882 121042880000001 +9000001000001000000020023138010000000000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/testdata/crashers/0.ach b/test/testdata/crashers/0.ach new file mode 100644 index 000000000..aeb00f814 --- /dev/null +++ b/test/testdata/crashers/0.ach @@ -0,0 +1 @@ +52200000000000000000000000000000000000000000000000SHR0000000000000000000000000000000000000000062723138010400000000000000000000000000000000000000000000000000000000000000000010000000000000017020000000000000000000000060 00000000000000000000000000000000000000000000000000000000000000000 diff --git a/test/testdata/crashers/1.ach b/test/testdata/crashers/1.ach new file mode 100644 index 000000000..0536d8ef0 --- /dev/null +++ b/test/testdata/crashers/1.ach @@ -0,0 +1 @@ +5225Name on Account 121042882 SHRPayment 180620 012104288000000162723138010412345678 0100000000072 123456789100001234567891123456789011121042880000001702REFONEAREFTERM021000490614123456Target Store 0049 PHILADELPHIA PA12104288000000182250000020023138010000100000000000000000000121042882 1210428800000019000001000001000000020023138010000100000000000000000000 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/testdata/crashers/2.ach b/test/testdata/crashers/2.ach new file mode 100644 index 000000000..d4c3cab8d --- /dev/null +++ b/test/testdata/crashers/2.ach @@ -0,0 +1 @@ +52200000000000000000000000000000000000000000000000SHR00000000000000000000000000000000000000000627231380104000000000000000000000000000000000000000000000000000000000000000000100000000000000170200000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000 diff --git a/test/testdata/crashers/3.ach b/test/testdata/crashers/3.ach new file mode 100644 index 000000000..cd7f28dd0 --- /dev/null +++ b/test/testdata/crashers/3.ach @@ -0,0 +1 @@ +5225Name on Account 121042882 SHRPayment 180620 012104288000000162723138010412345678 0100000000 Receiver Account Name 011121042880000001702REFONEAREFTERM021000490614123456Target Store 0049 PHILADELPHIA PA12104288000000182250000020023138010000100000000000000000000121042882 121042880000001900000100000100000003002313801000010000000200018036281 0810000300000005220Your Company Inc 0018036281WEBTrnsNicknaMar 16150316 10810000300000016220810002105654221 0000017500RAj##8k765j4k32Luke Skywalker S0081000030000004822000000100081000210000000000000000000175000018036281 0810000300000015225Your Company Inc 0018036281PPDTrnsNicknaMar 6 150306 1081000030000002627101000019923698412584 0000015000RAj##765432hj Jane Doe A10081000030000005822500000100101000010000000150000000000000000018036281 0810000300000029000003000002000000060050600106000000015000000000026820 999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/testdata/crashers/4.ach b/test/testdata/crashers/4.ach new file mode 100644 index 000000000..f9d56c97e --- /dev/null +++ b/test/testdata/crashers/4.ach @@ -0,0 +1 @@ +5220Name on Account 031300012 CCDVndr Pay 180804 0031300010000001627231380104744-5678-99 0000500000123456789 1231380100000001710ANN000000000000100000928383-23938 BEK Enterprises 0000001711BEK Solutions 15 West Place Street 0000001712JacobsTown*PA\ US*19305\ 0000001713Wells Fargo 01121042882 US 0000001714Citadel Bank 01231380104 US 00000017159874654932139872121 Front Street 0000001716LetterTown*AB\ CA*80014\ 0000001717This is an international payment 00010000001717Transfer of money from one country to another 00020000001718Bank of Germany 01987987987654654 DE 00010000001718Bank of Spain 01987987987123123 ES 00020000001718Bank of France 01456456456987987 FR 00030000001718Bank of Turkey 0112312345678910 TR 00040000001718Bank of United Kingdom 01134567890123456789012345678901234GB 0005000000172200000150012104288000000000000000000100000 2313801000000015220 FF3 US123456789 IATTRADEPAYMTCADUSD010101 0231381000000026271210428820007 0000002000123456789 1231380100000001710ANN000000000000100000928383-23938 BEK Enterprises 0000001711BEK Solutions 15 West Place Street 0000001712JacobsTown*PA\ US*19305\ 0000001713Wells Fargo 01121042882 US 0000001714Citadel Bank 01231380104 US 00000017159874654932139872121 Front Street 00000%�6LetterTown*AB\ CA*80014\ 0000001717This is an international payment 00010000001717Transfer of money from one country to another 00020000001718Bank of Germany 01987987987654654 DE 00010000001718Bank of Spain 01987987987123123 ES 00020000001718Bank of France 01456456456987987 FR 00030000001718Bank of Turkey 0112312345678910 TR 00040000001718Bank of United Kingdom 011234567890123456789012345678901234GB 0005000000182200000150012104288000000002000000000000000 2313801000000029000002000004000000300024208576000000002000000000100000 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999l99999999999 \ No newline at end of file diff --git a/test/testdata/crashers/5.ach b/test/testdata/crashers/5.ach new file mode 100644 index 000000000..5e2703505 --- /dev/null +++ b/test/testdata/crashers/5.ach @@ -0,0 +1 @@ +52200000000000000000000000000000000000000000000000POP0000000000000000000000000000000000000000�1�0�000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \ No newline at end of file diff --git a/test/testdata/crashers/d8f3557bf70ce706c9efbfcfd8143f131a2eef20 b/test/testdata/crashers/d8f3557bf70ce706c9efbfcfd8143f131a2eef20 new file mode 100644 index 000000000..10b23641d Binary files /dev/null and b/test/testdata/crashers/d8f3557bf70ce706c9efbfcfd8143f131a2eef20 differ diff --git a/test/testdata/flattenADVBatchesMultipleBatchHeaders.ach b/test/testdata/flattenADVBatchesMultipleBatchHeaders.ach new file mode 100644 index 000000000..ccc5104ac --- /dev/null +++ b/test/testdata/flattenADVBatchesMultipleBatchHeaders.ach @@ -0,0 +1,30 @@ +101 23138010401210428821909051411A094101Federal Reserve Bank My Bank Name +5280Company Name, In 121042882 ADVAccounting 190906 0121042880000001 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500001 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500002 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500003 +828000000300694140300000000000000000000000000000000000150000Company Name, Inc 121042880000001 +5280Company Name, In 121042882 ADVAccounting 190906 0121042880000002 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500001 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500002 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500003 +828000000300694140300000000000000000000000000000000000150000Company Name, Inc 121042880000002 +5280Company Incorpor 121042882 ADVAccounting 190906 0031202080000003 +681231380104744-5678-99 00000005000003120208411135 Name 0011000010500001 +681231380104744-5678-99 00000005000003120208411135 Name 0011000010500002 +681231380104744-5678-99 00000005000003120208411135 Name 0011000010500003 +828000000300694140300000000000000000000000000000000000150000Company Incorporate031202080000003 +5280Company Incorpor 121042882 ADVAccounting 190906 0031202080000004 +681231380104744-5678-99 00000005000003120208411135 Name 0011000010500001 +681231380104744-5678-99 00000005000003120208411135 Name 0011000010500002 +681231380104744-5678-99 00000005000003120208411135 Name 0011000010500003 +828000000300694140300000000000000000000000000000000000150000Company Incorporate031202080000004 +90000040000030000001202776561200000000000000000000000000000000000600000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/flattenADVBatchesOneBatchHeader.ach b/test/testdata/flattenADVBatchesOneBatchHeader.ach new file mode 100644 index 000000000..9e168f651 --- /dev/null +++ b/test/testdata/flattenADVBatchesOneBatchHeader.ach @@ -0,0 +1,30 @@ +101 23138010401210428821909051357A094101Federal Reserve Bank My Bank Name +5280Company Name, In 121042882 ADVAccounting 190906 0121042880000001 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500001 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500002 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500003 +828000000300694140300000000000000000000000000000000000150000Company Name, Inc 121042880000001 +5280Company Name, In 121042882 ADVAccounting 190906 0121042880000002 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500001 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500002 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500003 +828000000300694140300000000000000000000000000000000000150000Company Name, Inc 121042880000002 +5280Company Name, In 121042882 ADVAccounting 190906 0121042880000003 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500001 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500002 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500003 +828000000300694140300000000000000000000000000000000000150000Company Name, Inc 121042880000003 +5280Company Name, In 121042882 ADVAccounting 190906 0121042880000004 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500001 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500002 +681231380104744-5678-99 00000005000012104288211131 Name 0011000010500003 +828000000300694140300000000000000000000000000000000000150000Company Name, Inc 121042880000004 +90000040000030000001202776561200000000000000000000000000000000000600000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/flattenBatchesMultipleBatchHeaders.ach b/test/testdata/flattenBatchesMultipleBatchHeaders.ach new file mode 100644 index 000000000..35b8d196a --- /dev/null +++ b/test/testdata/flattenBatchesMultipleBatchHeaders.ach @@ -0,0 +1,40 @@ +101 231380104 1210428821908261725A094101Citadel Wells Fargo +5200Wells Fargo 121042882 PPDTrans. Des 190827 1121042880000001 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000001 +705bonus pay for amazing work on #OSS 00010000001 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000002 +705bonus pay for amazing work on #OSS 00010000002 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000003 +705bonus pay for amazing work on #OSS 00010000003 +82000000060069414030000000000000000000300000121042882 121042880000001 +5200Wells Fargo 121042882 PPDTrans. Des 190827 1121042880000002 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000004 +705bonus pay for amazing work on #OSS 00010000004 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000005 +705bonus pay for amazing work on #OSS 00010000005 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000006 +705bonus pay for amazing work on #OSS 00010000006 +82000000060069414030000000000000000000300000121042882 121042880000002 +5200Wells Bank 121042882 PPDTrans. Des 190827 1121042880000003 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000007 +705bonus pay for amazing work on #OSS 00010000007 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000008 +705bonus pay for amazing work on #OSS 00010000008 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000009 +705bonus pay for amazing work on #OSS 00010000009 +82000000060069414030000000000000000000300000121042882 121042880000003 +5200Wells 121042882 PPDTrans. Des 190827 1121042880000004 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000010 +705bonus pay for amazing work on #OSS 00010000010 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000011 +705bonus pay for amazing work on #OSS 00010000011 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000012 +705bonus pay for amazing work on #OSS 00010000012 +82000000060069414030000000000000000000300000121042882 121042880000004 +9000004000004000000240277656120000000000000000001200000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/testdata/flattenBatchesOneBatchHeader.ach b/test/testdata/flattenBatchesOneBatchHeader.ach new file mode 100644 index 000000000..16e3a9a98 --- /dev/null +++ b/test/testdata/flattenBatchesOneBatchHeader.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821908261725A094101Citadel Wells Fargo +5200Wells Fargo 121042882 PPDTrans. Des 190827 1121042880000001 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000001 +705bonus pay for amazing work on #OSS 00010000001 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000002 +705bonus pay for amazing work on #OSS 00010000002 +62223138010481967038518 0000100000#83738AB# Steven Tander 1121042880000003 +705bonus pay for amazing work on #OSS 00010000003 +82000000060069414030000000000000000000300000121042882 121042880000001 +9000001000001000000060069414030000000000000000000300000 diff --git a/test/testdata/flattenIATBatchesMultipleBatchHeaders.ach b/test/testdata/flattenIATBatchesMultipleBatchHeaders.ach new file mode 100644 index 000000000..3c9736fd9 --- /dev/null +++ b/test/testdata/flattenIATBatchesMultipleBatchHeaders.ach @@ -0,0 +1,130 @@ +101 12104288202313801041909051150A094101Bank My Bank Name +5200 FF3 US123456789 IATTRADEPAYMTCADUSD190906 0231380100000001 +6271210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +718Bank of France 01456456456987987 FR 00010000001 +6271210428820007 0000100000123456789 1231380100000002 +710ANN000000000000100000928383-23938 BEK Enterprises 0000002 +711BEK Solutions 15 West Place Street 0000002 +712JacobsTown*PA\ US*19305\ 0000002 +713Wells Fargo 01231380104 US 0000002 +714Citadel Bank 01121042882 CA 0000002 +7159874654932139872121 Front Street 0000002 +716LetterTown*AB\ CA*80014\ 0000002 +717This is an international payment 00010000002 +718Bank of France 01456456456987987 FR 00010000002 +6271210428820007 0000100000123456789 1231380100000003 +710ANN000000000000100000928383-23938 BEK Enterprises 0000003 +711BEK Solutions 15 West Place Street 0000003 +712JacobsTown*PA\ US*19305\ 0000003 +713Wells Fargo 01231380104 US 0000003 +714Citadel Bank 01121042882 CA 0000003 +7159874654932139872121 Front Street 0000003 +716LetterTown*AB\ CA*80014\ 0000003 +717This is an international payment 00010000003 +718Bank of France 01456456456987987 FR 00010000003 +82000000300036312864000000300000000000000000 231380100000001 +5200 FF3 US123456789 IATTRADEPAYMTCADUSD190906 0231380100000002 +6271210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +718Bank of France 01456456456987987 FR 00010000001 +6271210428820007 0000100000123456789 1231380100000002 +710ANN000000000000100000928383-23938 BEK Enterprises 0000002 +711BEK Solutions 15 West Place Street 0000002 +712JacobsTown*PA\ US*19305\ 0000002 +713Wells Fargo 01231380104 US 0000002 +714Citadel Bank 01121042882 CA 0000002 +7159874654932139872121 Front Street 0000002 +716LetterTown*AB\ CA*80014\ 0000002 +717This is an international payment 00010000002 +718Bank of France 01456456456987987 FR 00010000002 +6271210428820007 0000100000123456789 1231380100000003 +710ANN000000000000100000928383-23938 BEK Enterprises 0000003 +711BEK Solutions 15 West Place Street 0000003 +712JacobsTown*PA\ US*19305\ 0000003 +713Wells Fargo 01231380104 US 0000003 +714Citadel Bank 01121042882 CA 0000003 +7159874654932139872121 Front Street 0000003 +716LetterTown*AB\ CA*80014\ 0000003 +717This is an international payment 00010000003 +718Bank of France 01456456456987987 FR 00010000003 +82000000300036312864000000300000000000000000 231380100000002 +5200 FF3 US987654321 IATPAYMENT CADUSD190906 0231380100000003 +6271210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +718Bank of France 01456456456987987 FR 00010000001 +6271210428820007 0000100000123456789 1231380100000002 +710ANN000000000000100000928383-23938 BEK Enterprises 0000002 +711BEK Solutions 15 West Place Street 0000002 +712JacobsTown*PA\ US*19305\ 0000002 +713Wells Fargo 01231380104 US 0000002 +714Citadel Bank 01121042882 CA 0000002 +7159874654932139872121 Front Street 0000002 +716LetterTown*AB\ CA*80014\ 0000002 +717This is an international payment 00010000002 +718Bank of France 01456456456987987 FR 00010000002 +6271210428820007 0000100000123456789 1231380100000003 +710ANN000000000000100000928383-23938 BEK Enterprises 0000003 +711BEK Solutions 15 West Place Street 0000003 +712JacobsTown*PA\ US*19305\ 0000003 +713Wells Fargo 01231380104 US 0000003 +714Citadel Bank 01121042882 CA 0000003 +7159874654932139872121 Front Street 0000003 +716LetterTown*AB\ CA*80014\ 0000003 +717This is an international payment 00010000003 +718Bank of France 01456456456987987 FR 00010000003 +82000000300036312864000000300000000000000000 231380100000003 +5200 FF3 US987654321 IATPAYMENT CADUSD190906 0231380100000004 +6271210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +718Bank of France 01456456456987987 FR 00010000001 +6271210428820007 0000100000123456789 1231380100000002 +710ANN000000000000100000928383-23938 BEK Enterprises 0000002 +711BEK Solutions 15 West Place Street 0000002 +712JacobsTown*PA\ US*19305\ 0000002 +713Wells Fargo 01231380104 US 0000002 +714Citadel Bank 01121042882 CA 0000002 +7159874654932139872121 Front Street 0000002 +716LetterTown*AB\ CA*80014\ 0000002 +717This is an international payment 00010000002 +718Bank of France 01456456456987987 FR 00010000002 +6271210428820007 0000100000123456789 1231380100000003 +710ANN000000000000100000928383-23938 BEK Enterprises 0000003 +711BEK Solutions 15 West Place Street 0000003 +712JacobsTown*PA\ US*19305\ 0000003 +713Wells Fargo 01231380104 US 0000003 +714Citadel Bank 01121042882 CA 0000003 +7159874654932139872121 Front Street 0000003 +716LetterTown*AB\ CA*80014\ 0000003 +717This is an international payment 00010000003 +718Bank of France 01456456456987987 FR 00010000003 +82000000300036312864000000300000000000000000 231380100000004 +9000004000013000001200145251456000001200000000000000000 \ No newline at end of file diff --git a/test/testdata/flattenIATBatchesOneBatchHeader.ach b/test/testdata/flattenIATBatchesOneBatchHeader.ach new file mode 100644 index 000000000..9db850f32 --- /dev/null +++ b/test/testdata/flattenIATBatchesOneBatchHeader.ach @@ -0,0 +1,130 @@ +101 12104288202313801041909031530A094101Bank My Bank Name +5200 FF3 US123456789 IATTRADEPAYMTCADUSD190904 0231380100000001 +6271210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +718Bank of France 01456456456987987 FR 00010000001 +6271210428820007 0000100000123456789 1231380100000002 +710ANN000000000000100000928383-23938 BEK Enterprises 0000002 +711BEK Solutions 15 West Place Street 0000002 +712JacobsTown*PA\ US*19305\ 0000002 +713Wells Fargo 01231380104 US 0000002 +714Citadel Bank 01121042882 CA 0000002 +7159874654932139872121 Front Street 0000002 +716LetterTown*AB\ CA*80014\ 0000002 +717This is an international payment 00010000002 +718Bank of France 01456456456987987 FR 00010000002 +6271210428820007 0000100000123456789 1231380100000003 +710ANN000000000000100000928383-23938 BEK Enterprises 0000003 +711BEK Solutions 15 West Place Street 0000003 +712JacobsTown*PA\ US*19305\ 0000003 +713Wells Fargo 01231380104 US 0000003 +714Citadel Bank 01121042882 CA 0000003 +7159874654932139872121 Front Street 0000003 +716LetterTown*AB\ CA*80014\ 0000003 +717This is an international payment 00010000003 +718Bank of France 01456456456987987 FR 00010000003 +82000000300036312864000000300000000000000000 231380100000001 +5200 FF3 US123456789 IATTRADEPAYMTCADUSD190904 0231380100000002 +6271210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +718Bank of France 01456456456987987 FR 00010000001 +6271210428820007 0000100000123456789 1231380100000002 +710ANN000000000000100000928383-23938 BEK Enterprises 0000002 +711BEK Solutions 15 West Place Street 0000002 +712JacobsTown*PA\ US*19305\ 0000002 +713Wells Fargo 01231380104 US 0000002 +714Citadel Bank 01121042882 CA 0000002 +7159874654932139872121 Front Street 0000002 +716LetterTown*AB\ CA*80014\ 0000002 +717This is an international payment 00010000002 +718Bank of France 01456456456987987 FR 00010000002 +6271210428820007 0000100000123456789 1231380100000003 +710ANN000000000000100000928383-23938 BEK Enterprises 0000003 +711BEK Solutions 15 West Place Street 0000003 +712JacobsTown*PA\ US*19305\ 0000003 +713Wells Fargo 01231380104 US 0000003 +714Citadel Bank 01121042882 CA 0000003 +7159874654932139872121 Front Street 0000003 +716LetterTown*AB\ CA*80014\ 0000003 +717This is an international payment 00010000003 +718Bank of France 01456456456987987 FR 00010000003 +82000000300036312864000000300000000000000000 231380100000002 +5200 FF3 US123456789 IATTRADEPAYMTCADUSD190904 0231380100000003 +6271210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +718Bank of France 01456456456987987 FR 00010000001 +6271210428820007 0000100000123456789 1231380100000002 +710ANN000000000000100000928383-23938 BEK Enterprises 0000002 +711BEK Solutions 15 West Place Street 0000002 +712JacobsTown*PA\ US*19305\ 0000002 +713Wells Fargo 01231380104 US 0000002 +714Citadel Bank 01121042882 CA 0000002 +7159874654932139872121 Front Street 0000002 +716LetterTown*AB\ CA*80014\ 0000002 +717This is an international payment 00010000002 +718Bank of France 01456456456987987 FR 00010000002 +6271210428820007 0000100000123456789 1231380100000003 +710ANN000000000000100000928383-23938 BEK Enterprises 0000003 +711BEK Solutions 15 West Place Street 0000003 +712JacobsTown*PA\ US*19305\ 0000003 +713Wells Fargo 01231380104 US 0000003 +714Citadel Bank 01121042882 CA 0000003 +7159874654932139872121 Front Street 0000003 +716LetterTown*AB\ CA*80014\ 0000003 +717This is an international payment 00010000003 +718Bank of France 01456456456987987 FR 00010000003 +82000000300036312864000000300000000000000000 231380100000003 +5200 FF3 US123456789 IATTRADEPAYMTCADUSD190904 0231380100000004 +6271210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +718Bank of France 01456456456987987 FR 00010000001 +6271210428820007 0000100000123456789 1231380100000002 +710ANN000000000000100000928383-23938 BEK Enterprises 0000002 +711BEK Solutions 15 West Place Street 0000002 +712JacobsTown*PA\ US*19305\ 0000002 +713Wells Fargo 01231380104 US 0000002 +714Citadel Bank 01121042882 CA 0000002 +7159874654932139872121 Front Street 0000002 +716LetterTown*AB\ CA*80014\ 0000002 +717This is an international payment 00010000002 +718Bank of France 01456456456987987 FR 00010000002 +6271210428820007 0000100000123456789 1231380100000003 +710ANN000000000000100000928383-23938 BEK Enterprises 0000003 +711BEK Solutions 15 West Place Street 0000003 +712JacobsTown*PA\ US*19305\ 0000003 +713Wells Fargo 01231380104 US 0000003 +714Citadel Bank 01121042882 CA 0000003 +7159874654932139872121 Front Street 0000003 +716LetterTown*AB\ CA*80014\ 0000003 +717This is an international payment 00010000003 +718Bank of France 01456456456987987 FR 00010000003 +82000000300036312864000000300000000000000000 231380100000004 +9000004000013000001200145251456000001200000000000000000 \ No newline at end of file diff --git a/test/testdata/iat-batchHeaderErr.ach b/test/testdata/iat-batchHeaderErr.ach new file mode 100644 index 000000000..74c734d68 --- /dev/null +++ b/test/testdata/iat-batchHeaderErr.ach @@ -0,0 +1,31 @@ +101 987654321 1234567891807130000A094101Federal Reserve Bank My Bank Name +5220 FF3 US123456789 IATTRADEPAYMTCADUSD010101 1231380100000001 +6221210428820007 0000100000123456789 1231380100000001 +5220 FF3 US123456789 IATTRADEPAYMTCADUSD010101 0231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +82200000080012104288000000000000000000100000 231380100000001 +5220 FF3 US123456789 IATTRADEPAYMTCADUSD010101 1231380100000002 +6271210428820007 0000002000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +82200000080012104288000000002000000000000000 231380100000002 +9000002000003000000160024208576000000002000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/iat-debit.ach b/test/testdata/iat-debit.ach new file mode 100644 index 000000000..0d6f30801 --- /dev/null +++ b/test/testdata/iat-debit.ach @@ -0,0 +1,20 @@ +101 12104288202313801041908071513A094101Bank My Bank Name +5225 FF3 US123456789 IATTRADEPAYMTCADUSD190808 0231380100000001 +6271210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +718Bank of France 01456456456987987 FR 00010000001 +82250000100012104288000000100000000000000000 231380100000001 +9000001000002000000100012104288000000100000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/testdata/iat-debit.json b/test/testdata/iat-debit.json new file mode 100644 index 000000000..926d2d8ec --- /dev/null +++ b/test/testdata/iat-debit.json @@ -0,0 +1,156 @@ +{ + "id": "iat-datetime", + "fileHeader": { + "id": "iat-datetime", + "immediateDestination": "121042882", + "immediateOrigin": "231380104", + "fileCreationDate": "190807", + "fileCreationTime": "1513", + "fileIDModifier": "A", + "immediateDestinationName": "Bank", + "immediateOriginName": "My Bank Name" + }, + "batches": null, + "IATBatches": [ + { + "id": "iat-datetime", + "IATBatchHeader": { + "id": "iat-datetime", + "serviceClassCode": 225, + "foreignExchangeIndicator": "FF", + "foreignExchangeReferenceIndicator": 3, + "foreignExchangeReference": "", + "ISODestinationCountryCode": "US", + "originatorIdentification": "123456789", + "standardEntryClassCode": "IAT", + "companyEntryDescription": "TRADEPAYMT", + "ISOOriginatingCurrencyCode": "CAD", + "ISODestinationCurrencyCode": "USD", + "effectiveEntryDate": "2019-09-23T09:14:55.794Z", + "ODFIIdentification": "23138010", + "batchNumber": 1 + }, + "IATEntryDetails": [ + { + "id": "iat-datetime", + "transactionCode": 27, + "RDFIIdentification": "12104288", + "checkDigit": "2", + "AddendaRecords": 7, + "amount": 100000, + "DFIAccountNumber": "123456789 ", + "OFACScreeningIndicator": " ", + "SecondaryOFACScreeningIndicator": " ", + "addendaRecordIndicator": 1, + "traceNumber": "231380100000001", + "addenda10": { + "id": "iat-datetime", + "typeCode": "10", + "transactionTypeCode": "ANN", + "foreignPaymentAmount": 100000, + "foreignTraceNumber": "928383-23938", + "name": "BEK Enterprises", + "entryDetailSequenceNumber": 1 + }, + "addenda11": { + "id": "iat-datetime", + "typeCode": "11", + "originatorName": "BEK Solutions", + "originatorStreetAddress": "15 West Place Street", + "entryDetailSequenceNumber": 1 + }, + "addenda12": { + "id": "iat-datetime", + "typeCode": "12", + "originatorCityStateProvince": "JacobsTown*PA\\", + "originatorCountryPostalCode": "US*19305\\", + "entryDetailSequenceNumber": 1 + }, + "addenda13": { + "id": "iat-datetime", + "typeCode": "13", + "ODFIName": "Wells Fargo", + "ODFIIDNumberQualifier": "01", + "ODFIIdentification": "231380104", + "ODFIBranchCountryCode": "US", + "entryDetailSequenceNumber": 1 + }, + "addenda14": { + "id": "iat-datetime", + "typeCode": "14", + "RDFIName": "Citadel Bank", + "RDFIIDNumberQualifier": "01", + "RDFIIdentification": "121042882", + "RDFIBranchCountryCode": "CA", + "entryDetailSequenceNumber": 1 + }, + "addenda15": { + "id": "iat-datetime", + "typeCode": "15", + "receiverIDNumber": "987465493213987", + "receiverStreetAddress": "2121 Front Street", + "entryDetailSequenceNumber": 1 + }, + "addenda16": { + "id": "iat-datetime", + "typeCode": "16", + "receiverCityStateProvince": "LetterTown*AB\\", + "receiverCountryPostalCode": "CA*80014\\", + "entryDetailSequenceNumber": 1 + }, + "addenda17": [ + { + "id": "iat-datetime", + "typeCode": "17", + "paymentRelatedInformation": "This is an international payment", + "sequenceNumber": 1, + "entryDetailSequenceNumber": 1 + } + ], + "addenda18": [ + { + "id": "iat-datetime", + "typeCode": "18", + "foreignCorrespondentBankName": "Bank of France", + "foreignCorrespondentBankIDNumberQualifier": "01", + "foreignCorrespondentBankIDNumber": "456456456987987", + "foreignCorrespondentBankBranchCountryCode": "FR", + "sequenceNumber": 1, + "entryDetailSequenceNumber": 1 + } + ] + } + ], + "batchControl": { + "id": "iat-datetime", + "serviceClassCode": 225, + "entryAddendaCount": 10, + "entryHash": 12104288, + "totalDebit": 100000, + "totalCredit": 0, + "companyIdentification": "", + "ODFIIdentification": "23138010", + "batchNumber": 1 + } + } + ], + "fileControl": { + "id": "iat-datetime", + "batchCount": 1, + "blockCount": 2, + "entryAddendaCount": 10, + "entryHash": 12104288, + "totalDebit": 100000, + "totalCredit": 0 + }, + "fileADVControl": { + "id": "iat-datetime", + "batchCount": 0, + "entryAddendaCount": 0, + "entryHash": 0, + "totalDebit": 0, + "totalCredit": 0 + }, + "NotificationOfChange": null, + "ReturnEntries": null +} diff --git a/test/testdata/iat-invalidAddenda10.ach b/test/testdata/iat-invalidAddenda10.ach new file mode 100644 index 000000000..75dff8e88 --- /dev/null +++ b/test/testdata/iat-invalidAddenda10.ach @@ -0,0 +1,30 @@ +101 987654321 1234567891807130000A094101Federal Reserve Bank My Bank Name +5220 FF3 US123456789 IATTRADEPAYMTCADUSD010101 1231380100000001 +6221210428820007 0000100000123456789 1231380100000001 +710BIL000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +82200000080012104288000000000000000000100000 231380100000001 +5220 FF3 US123456789 IATTRADEPAYMTCADUSD010101 1231380100000002 +6271210428820007 0000002000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +82200000080012104288000000002000000000000000 231380100000002 +9000002000003000000160024208576000000002000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/iat-invalidAddenda11.ach b/test/testdata/iat-invalidAddenda11.ach new file mode 100644 index 000000000..0ffadd555 --- /dev/null +++ b/test/testdata/iat-invalidAddenda11.ach @@ -0,0 +1,20 @@ +101 121042882 2313801041812180000A094101Bank My Bank Name +5225 FF3 US123456789 IATTRADEPAYMTCADUSD181219 0231380100000001 +6221210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BE Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +718Bank of France 01456456456987987 FR 00010000001 +82250000100012104288000000000000000000100000 231380100000001 +9000001000002000000100012104288000000000000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/iat-invalidAddenda12.ach b/test/testdata/iat-invalidAddenda12.ach new file mode 100644 index 000000000..21a75c267 --- /dev/null +++ b/test/testdata/iat-invalidAddenda12.ach @@ -0,0 +1,20 @@ +101 121042882 2313801041812180000A094101Bank My Bank Name +5225 FF3 US123456789 IATTRADEPAYMTCADUSD181219 1231380100000001 +6221210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +718Bank of France 01456456456987987 FR 00010000001 +82250000100012104288000000000000000000100000 231380100000001 +9000001000002000000100012104288000000000000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/iat-invalidAddenda14.ach b/test/testdata/iat-invalidAddenda14.ach new file mode 100644 index 000000000..37fa7910c --- /dev/null +++ b/test/testdata/iat-invalidAddenda14.ach @@ -0,0 +1,20 @@ +101 121042882 2313801041812180000A094101Bank My Bank Name +5225 FF3 US123456789 IATTRADEPAYMTCADUSD181219 1231380100000001 +6221210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714CitadelBank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +718Bank of France 01456456456987987 FR 00010000001 +82250000100012104288000000000000000000100000 231380100000001 +9000001000002000000100012104288000000000000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/iat-invalidAddenda15.ach b/test/testdata/iat-invalidAddenda15.ach new file mode 100644 index 000000000..d73513d4b --- /dev/null +++ b/test/testdata/iat-invalidAddenda15.ach @@ -0,0 +1,20 @@ +101 121042882 2313801041812180000A094101Bank My Bank Name +5225 FF3 US123456789 IATTRADEPAYMTCADUSD181219 1231380100000001 +6221210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 FrontStreet 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +718Bank of France 01456456456987987 FR 00010000001 +82250000100012104288000000000000000000100000 231380100000001 +9000001000002000000100012104288000000000000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/iat-invalidAddenda16.ach b/test/testdata/iat-invalidAddenda16.ach new file mode 100644 index 000000000..b9694001b --- /dev/null +++ b/test/testdata/iat-invalidAddenda16.ach @@ -0,0 +1,20 @@ +101 121042882 2313801041812180000A094101Bank My Bank Name +5225 FF3 US123456789 IATTRADEPAYMTCADUSD181219 1231380100000001 +6221210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetteTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +718Bank of France 01456456456987987 FR 00010000001 +82250000100012104288000000000000000000100000 231380100000001 +9000001000002000000100012104288000000000000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/iat-invalidAddenda17.ach b/test/testdata/iat-invalidAddenda17.ach new file mode 100644 index 000000000..3e0608540 --- /dev/null +++ b/test/testdata/iat-invalidAddenda17.ach @@ -0,0 +1,20 @@ +101 121042882 2313801041812180000A094101Bank My Bank Name +5225 FF3 US123456789 IATTRADEPAYMTCADUSD181219 1231380100000001 +6221210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an iternational payment 00010000001 +718Bank of France 01456456456987987 FR 00010000001 +82250000100012104288000000000000000000100000 231380100000001 +9000001000002000000100012104288000000000000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/iat-invalidAddenda18.ach b/test/testdata/iat-invalidAddenda18.ach new file mode 100644 index 000000000..f6c2ddc68 --- /dev/null +++ b/test/testdata/iat-invalidAddenda18.ach @@ -0,0 +1,20 @@ +101 121042882 2313801041812180000A094101Bank My Bank Name +5225 FF3 US123456789 IATTRADEPAYMTCADUSD181219 1231380100000001 +6221210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +718Bank of Frane 01456456456987987 FR 00010000001 +82250000100012104288000000000000000000100000 231380100000001 +9000001000002000000100012104288000000000000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/iat-invalidAddenda98.ach b/test/testdata/iat-invalidAddenda98.ach new file mode 100644 index 000000000..11cf78167 --- /dev/null +++ b/test/testdata/iat-invalidAddenda98.ach @@ -0,0 +1,20 @@ +101 231380104 1210428821901160000A094101Federal Reserve Bank My Bank Name +5220IATCOR FF3 US123456789 CORTRADEPAYMTCADUSD000000 1231380100000001 +6211210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +798C78231380100000001 1210428889722-C3 121042880000001 +82200000090012104288000000000000000000100000 231380100000001 +9000001000002000000090012104288000000000000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/iat-invalidAddenda99.ach b/test/testdata/iat-invalidAddenda99.ach new file mode 100644 index 000000000..30dba211f --- /dev/null +++ b/test/testdata/iat-invalidAddenda99.ach @@ -0,0 +1,20 @@ +101 231380104 1210428821901160000A094101Federal Reserve Bank My Bank Name +5220 FF3 US123456789 IATTRADEPAYMTCADUSD000000 1231380100000001 +6221210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +799R96231380100000001 121042880000100000Authorization Revoked 000000000000000 +82200000090012104288000000000000000000100000 231380100000001 +9000001000002000000090012104288000000000000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/iat-invalidAddendaRecordIndicator.ach b/test/testdata/iat-invalidAddendaRecordIndicator.ach new file mode 100644 index 000000000..2a075507d --- /dev/null +++ b/test/testdata/iat-invalidAddendaRecordIndicator.ach @@ -0,0 +1,20 @@ +101 231380104 1210428821901160000A094101Federal Reserve Bank My Bank Name +5220 FF3 US123456789 IATTRADEPAYMTCADUSD000000 1231380100000001 +6221210428820007 0000100000123456789 5231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +82200000080012104288000000000000000000100000 231380100000001 +9000001000002000000080012104288000000000000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/iat-invalidBatchControl.ach b/test/testdata/iat-invalidBatchControl.ach new file mode 100644 index 000000000..78a02c73b --- /dev/null +++ b/test/testdata/iat-invalidBatchControl.ach @@ -0,0 +1,30 @@ +101 987654321 1234567891807130000A094101Federal Reserve Bank My Bank Name +5220 FF3 US123456789 IATTRADEPAYMTCADUSD010101 1231380100000001 +6221210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +82200000080012104288000000000000000000100000 231000000000000 +5220 FF3 US123456789 IATTRADEPAYMTCADUSD010101 1231380100000002 +6271210428820007 0000002000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +82200000080012104288000000002000000000000000 231380100000002 +9000002000003000000160024208576000000002000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/testdata/iat-invalidBatchHeader.ach b/test/testdata/iat-invalidBatchHeader.ach new file mode 100644 index 000000000..dc2e07597 --- /dev/null +++ b/test/testdata/iat-invalidBatchHeader.ach @@ -0,0 +1,30 @@ +101 987654321 1234567891807130000A094101Federal Reserve Bank My Bank Name +5100 FF3 US123456789 IATTRADEPAYMTCADUSD010101 1231380100000001 +6221210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +82200000080012104288000000000000000000100000 231380100000001 +5220 FF3 US123456789 IATTRADEPAYMTCADUSD010101 1231380100000002 +6271210428820007 0000002000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +82200000080012104288000000002000000000000000 231380100000002 +9000002000003000000160024208576000000002000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/iat-invalidEntryDetail.ach b/test/testdata/iat-invalidEntryDetail.ach new file mode 100644 index 000000000..ba9c2b4e3 --- /dev/null +++ b/test/testdata/iat-invalidEntryDetail.ach @@ -0,0 +1,30 @@ +101 987654321 1234567891807160000A094101Federal Reserve Bank My Bank Name +5220 FF3 US123456789 IATTRADEPAYMTCADUSD010101 1231380100000001 +6901210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +717Transfer of money from one country to another 00020000001 +82200000100012104288000000000000000000100000 231380100000001 +5220 FF3 US123456789 IATTRADEPAYMTCADUSD010101 1231380100000002 +6271210428820007 0000002000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01121042882 US 0000001 +714Citadel Bank 01231380104 US 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00010000001 +82200000090012104288000000002000000000000000 231380100000002 +9000002000003000000190024208576000000002000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/iat-mixedDebitCredit.ach b/test/testdata/iat-mixedDebitCredit.ach new file mode 100644 index 000000000..5df89fca4 --- /dev/null +++ b/test/testdata/iat-mixedDebitCredit.ach @@ -0,0 +1,30 @@ +101 12104288202313801041908071540A094101Bank My Bank Name +5200 FF3 US123456789 IATTRADEPAYMTCADUSD190808 0231380100000001 +6271210428820007 0000100000123456789 1231380100000001 +710ANN000000000000100000928383-23938 BEK Enterprises 0000001 +711BEK Solutions 15 West Place Street 0000001 +712JacobsTown*PA\ US*19305\ 0000001 +713Wells Fargo 01231380104 US 0000001 +714Citadel Bank 01121042882 CA 0000001 +7159874654932139872121 Front Street 0000001 +716LetterTown*AB\ CA*80014\ 0000001 +717This is an international payment 00020000001 +717This is an international payment 00020000001 +718Bank of Fr`nce 01456456456987987 FR 00010000001 +6221210428820007 0000100000123456789 1231380100000002 +710ANN000000000000100000928383-23938 ADCAF Enterprises 0000002 +711ADCAF Solutions 15 West Place Street 0000002 +712JacobsTown*PA\ US*19305\ 0000002 +713Wells Fargo 01231380104 US 0000002 +714Citadel Bank 01121042882 CA 0000002 +71598746549321398718 Fifth Street 0000002 +716LetterTown*AB\ CA*80014\ 0000002 +718Bank of Fr`nce 01456456456987987 FR 00010000002 +82000000200024208576000000100000000000100000 231380100000001 +9000001000003000000200024208576000000100000000000100000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/invalid-batchNumber.json b/test/testdata/invalid-batchNumber.json new file mode 100644 index 000000000..c8f00ba7a --- /dev/null +++ b/test/testdata/invalid-batchNumber.json @@ -0,0 +1,72 @@ +{ + "ID": "3f2d23ee214", + "fileHeader": { + "immediateOrigin": "211370529", + "immediateOriginName": "AVIDIA BANK", + "immediateDestination": "011000015", + "immediateDestinationName": "FRB BOSTON", + "fileCreationTime": "1504", + "fileCreationDate": "200722", + "fileIDModifier": "0" + }, + "batches": [ + { + "batchHeader": { + "ID": "913b5742", + "serviceClassCode": 220, + "companyName": "Moov Corp", + "companyDiscretionaryData": "Payment", + "companyIdentification": "P812345", + "standardEntryClassCode": "PPD", + "companyEntryDescription": "DISB", + "companyDescriptiveDate": "200722", + "effectiveEntryDate": "200723", + "originatorStatusCode": 1, + "ODFIIdentification": "21137052", + "batchNumber": "1234567" + }, + "entryDetails": [ + { + "ID": "842a2261", + "transactionCode": 22, + "RDFIIdentification": "06310751", + "checkDigit": "3", + "DFIAccountNumber": "7289457611", + "amount": 100, + "individualName": "JANE DOE", + "addendaRecordIndicator": 1, + "traceNumber": "2113705290000001", + "addenda05": [ + { + "id": "5ca8d25a", + "typeCode": "05", + "paymentRelatedInformation": "Car Payment", + "sequenceNumber": 1, + "entryDetailSequenceNumber": 1 + } + ] + } + ], + "batchControl": { + "id": "5ca8d25b", + "serviceClassCode": 220, + "entryAddendaCount": 1, + "entryHash": 12621502, + "totalDebit": 0, + "totalCredit": 100, + "companyIdentification": "P812345", + "ODFIIdentification": "21137052", + "batchNumber": 1 + } + } + ], + "fileControl":{ + "id":"5ca8d25c", + "batchCount": 1, + "blockCount": 1, + "entryAddendaCount": 2, + "entryHash": 12621502, + "totalDebit": 0, + "totalCredit": 100 + } +} diff --git a/test/testdata/invalid-two-micro-deposits.ach b/test/testdata/invalid-two-micro-deposits.ach new file mode 100644 index 000000000..d6e0e0bed --- /dev/null +++ b/test/testdata/invalid-two-micro-deposits.ach @@ -0,0 +1,20 @@ +101 121042882 12104288220032415591094101Moov Bank Moov, Inc +5200Moov - paygate m 001 PPDMoov, Inc 200324200325 1121042880000001 +632121042882322580734 0000000044e681e50d1cc83dcDistracted Austin Mo1121042886829038 +705paygate transaction 00016829038 +632121042882322580734 0000000032e681e50d1cc83dcDistracted Austin Mo1121042886829039 +705paygate transaction 00016829039 +627121042882322580734 0000000076e681e50d1cc83dcDistracted Austin Mo1121042886829040 +705paygate transaction 00016829040 +82000000060036312864000000000076000000000076001 121042880000001 +5200Moov - paygate m 001 PPDMoov, Inc 200324200325 1121042880000002 +632121042882191759324 000000000234e5b6f1cf14232Distracted Austin Mo1121042889211556 +705paygate transaction 00019211556 +632121042882191759324 000000004234e5b6f1cf14232Distracted Austin Mo1121042889211557 +705paygate transaction 00019211557 +6271210428821917593249989988A00CCC0000444e5b6f1cf14232Distracted Austin Mo1121042889AAZZZ8 +705paygate transaction 00019211558 +82000000060036312864000000000044000000000044001 121042880000002 +9000002000002000000120072625728000000000120000000000120 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/testdata/iso8601.json b/test/testdata/iso8601.json new file mode 100644 index 000000000..4608688eb --- /dev/null +++ b/test/testdata/iso8601.json @@ -0,0 +1,68 @@ +{ + "id": "iso8601", + "fileHeader": { + "id": "iso8601", + "immediateDestination": "231380104", + "immediateOrigin": "121042882", + "fileCreationDate": "2019-09-20T21:14:55.794Z", + "fileCreationTime": "2019-09-20T21:14:55.794Z", + "fileIDModifier": "A", + "immediateDestinationName": "Citadel", + "immediateOriginName": "Wells Fargo" + }, + "batches": [ + { + "batchHeader": { + "id": "iso8601", + "serviceClassCode": 200, + "companyName": "Wells Fargo", + "companyIdentification": "121042882", + "standardEntryClassCode": "PPD", + "companyEntryDescription": "Trans. Des", + "companyDescriptiveDate": "2019-09-20T21:14:55.794Z", + "effectiveEntryDate": "2019-09-20T21:14:55.794Z", + "ODFIIdentification": "12104288", + "batchNumber": 1 + }, + "entryDetails": [ + { + "id": "iso8601", + "transactionCode": 22, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "81967038518 ", + "amount": 100000, + "identificationNumber": "#83738AB# ", + "individualName": "Steven Tander ", + "discretionaryData": " ", + "addendaRecordIndicator": 0, + "traceNumber": "121042880000001", + "category": "Forward" + } + ], + "batchControl": { + "id": "iso8601", + "serviceClassCode": 200, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000, + "companyIdentification": "121042882", + "ODFIIdentification": "12104288", + "batchNumber": 1 + } + } + ], + "IATBatches": null, + "fileControl": { + "id": "iso8601", + "batchCount": 1, + "blockCount": 1, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000 + }, + "NotificationOfChange": null, + "ReturnEntries": null +} diff --git a/test/testdata/json-bypass-origin-and-destination.json b/test/testdata/json-bypass-origin-and-destination.json new file mode 100644 index 000000000..48b961cb0 --- /dev/null +++ b/test/testdata/json-bypass-origin-and-destination.json @@ -0,0 +1,67 @@ +{ + "id": "adam-01", + "fileHeader": { + "id": "adam-01", + "immediateDestination": "000000000", + "immediateOrigin": "000000000", + "fileCreationDate": "181008", + "fileCreationTime": "", + "fileIDModifier": "A", + "immediateDestinationName": "Citadel", + "immediateOriginName": "Wells Fargo" + }, + "batches": [ + { + "batchHeader": { + "id": "adam-01", + "serviceClassCode": 200, + "companyName": "Wells Fargo", + "companyIdentification": "121042882", + "standardEntryClassCode": "PPD", + "companyEntryDescription": "Trans. Des", + "effectiveEntryDate": "2018-10-09T00:00:00Z", + "ODFIIdentification": "12104288", + "batchNumber": 1 + }, + "entryDetails": [ + { + "id": "adam-01", + "transactionCode": 22, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "81967038518 ", + "amount": 100000, + "identificationNumber": "#83738AB# ", + "individualName": "Steven Tander ", + "discretionaryData": " ", + "addendaRecordIndicator": 0, + "traceNumber": "121042880000001", + "category": "Forward" + } + ], + "batchControl": { + "id": "adam-01", + "serviceClassCode": 200, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000, + "companyIdentification": "121042882", + "ODFIIdentification": "12104288", + "batchNumber": 1 + } + } + ], + "IATBatches": null, + "fileControl": { + "id": "adam-01", + "batchCount": 1, + "blockCount": 1, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000 + }, + "NotificationOfChange": null, + "ReturnEntries": null +} diff --git a/test/testdata/json-bypass-origin.json b/test/testdata/json-bypass-origin.json new file mode 100644 index 000000000..ca53685b9 --- /dev/null +++ b/test/testdata/json-bypass-origin.json @@ -0,0 +1,67 @@ +{ + "id": "adam-01", + "fileHeader": { + "id": "adam-01", + "immediateDestination": "231380104", + "immediateOrigin": "000000000", + "fileCreationDate": "181008", + "fileCreationTime": "", + "fileIDModifier": "A", + "immediateDestinationName": "Citadel", + "immediateOriginName": "Wells Fargo" + }, + "batches": [ + { + "batchHeader": { + "id": "adam-01", + "serviceClassCode": 200, + "companyName": "Wells Fargo", + "companyIdentification": "121042882", + "standardEntryClassCode": "PPD", + "companyEntryDescription": "Trans. Des", + "effectiveEntryDate": "2018-10-09T00:00:00Z", + "ODFIIdentification": "12104288", + "batchNumber": 1 + }, + "entryDetails": [ + { + "id": "adam-01", + "transactionCode": 22, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "81967038518 ", + "amount": 100000, + "identificationNumber": "#83738AB# ", + "individualName": "Steven Tander ", + "discretionaryData": " ", + "addendaRecordIndicator": 0, + "traceNumber": "121042880000001", + "category": "Forward" + } + ], + "batchControl": { + "id": "adam-01", + "serviceClassCode": 200, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000, + "companyIdentification": "121042882", + "ODFIIdentification": "12104288", + "batchNumber": 1 + } + } + ], + "IATBatches": null, + "fileControl": { + "id": "adam-01", + "batchCount": 1, + "blockCount": 1, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000 + }, + "NotificationOfChange": null, + "ReturnEntries": null +} diff --git a/test/testdata/long-line.ach b/test/testdata/long-line.ach new file mode 100644 index 000000000..42706a617 --- /dev/null +++ b/test/testdata/long-line.ach @@ -0,0 +1,10 @@ +101 23138010401210428821906240000A094101Federal Reserve Bank My Bank Name +5225Name on Account 121042882 PPDREG.SALARY 190625 1121042880000001 +62723138010412345678 0100000000 Receiver Account Name 0121042880000001 +82250000010023138010000100000000000000000000121042882 121042880000001 +9000001000001000000010023138010000100000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/testdata/pos-invalidEntryDetail.ach b/test/testdata/pos-invalidEntryDetail.ach new file mode 100644 index 000000000..8175fe4cc --- /dev/null +++ b/test/testdata/pos-invalidEntryDetail.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821806140000A094101Federal Reserve Bank My Bank Name +5225Name on Account 121042882 POSREG.SALARY 180615 1121042880000001 +62723138010412345678 0100000000 Receive Account Name 011121042880000001 +702REFONEAREFTERM021000490614123456Target Store 0049 PHILADELPHIA PA121042880000001 +82250000020023138010000100000000000000000000121042882 121042880000001 +9000001000001000000020023138010000100000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/pos-invalidReturnFile.ach b/test/testdata/pos-invalidReturnFile.ach new file mode 100644 index 000000000..db2f30f86 --- /dev/null +++ b/test/testdata/pos-invalidReturnFile.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821901160000A094101Federal Reserve Bank My Bank Name +5225Payee Name 121042882 POSACH POS 000000 1121042880000001 +627231380104744-5678-99 000002500045689033 Wade Arnold 011121042880000001 +799R96099912340000015 09101298Authorization Revoked 000000000000000 +82250000020023138010000000025000000000000000121042882 121042880000001 +9000001000001000000020023138010000000025000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/ppd-debit-customTraceNumber.ach b/test/testdata/ppd-debit-customTraceNumber.ach new file mode 100644 index 000000000..510adca01 --- /dev/null +++ b/test/testdata/ppd-debit-customTraceNumber.ach @@ -0,0 +1,10 @@ +101 23138010401210428821906240000A094101Federal Reserve Bank My Bank Name +5225Name on Account 121042882 PPDREG.SALARY 190625 1121042880000001 +62723138010412345678 0100000000 Receiver Account Name 0111111110000001 +82250000010023138010000100000000000000000000121042882 121042880000001 +9000001000001000000010023138010000100000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/testdata/ppd-debit-fixedLength.ach b/test/testdata/ppd-debit-fixedLength.ach similarity index 100% rename from testdata/ppd-debit-fixedLength.ach rename to test/testdata/ppd-debit-fixedLength.ach diff --git a/test/testdata/ppd-debit-fixedLengthInvalid.ach b/test/testdata/ppd-debit-fixedLengthInvalid.ach new file mode 100644 index 000000000..39258b664 --- /dev/null +++ b/test/testdata/ppd-debit-fixedLengthInvalid.ach @@ -0,0 +1 @@ +1101 076401251 0764012510807291511A094101achdestname companyname 5225companyname origid PPDCHECKPAYMT000002080730 107640125000000162705320001912345 0000010500c-1 Bachman Eric DD007640125565529182250000010005320001000000010500000000000000origid 0764012500000019000001000001000000010005320001000000010500000000000000 diff --git a/test/testdata/ppd-debit.ach b/test/testdata/ppd-debit.ach new file mode 100644 index 000000000..99a433ba6 --- /dev/null +++ b/test/testdata/ppd-debit.ach @@ -0,0 +1,10 @@ +101 23138010401210428821906240000A094101Federal Reserve Bank My Bank Name +5225Name on Account 121042882 PPDREG.SALARY 190625 1121042880000001 +62723138010412345678 0100000000 Receiver Account Name 0121042880000001 +82250000010023138010000100000000000000000000121042882 121042880000001 +9000001000001000000010023138010000100000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/ppd-invalid-EntryDetail-checkDigit.json b/test/testdata/ppd-invalid-EntryDetail-checkDigit.json new file mode 100644 index 000000000..837faa29b --- /dev/null +++ b/test/testdata/ppd-invalid-EntryDetail-checkDigit.json @@ -0,0 +1,67 @@ +{ + "id": "adam-01", + "fileHeader": { + "id": "adam-01", + "immediateDestination": "231380104", + "immediateOrigin": "121042882", + "fileCreationDate": "181008", + "fileCreationTime": "", + "fileIDModifier": "A", + "immediateDestinationName": "Citadel", + "immediateOriginName": "Wells Fargo" + }, + "batches": [ + { + "batchHeader": { + "id": "adam-01", + "serviceClassCode": 200, + "companyName": "Wells Fargo", + "companyIdentification": "121042882", + "standardEntryClassCode": "PPD", + "companyEntryDescription": "Trans. Des", + "effectiveEntryDate": "2018-10-09T00:00:00Z", + "ODFIIdentification": "12104288", + "batchNumber": 1 + }, + "entryDetails": [ + { + "id": "adam-01", + "transactionCode": 22, + "RDFIIdentification": "23138010", + "checkDigit": "1", + "DFIAccountNumber": "81967038518 ", + "amount": 100000, + "identificationNumber": "#83738AB# ", + "individualName": "Steven Tander ", + "discretionaryData": " ", + "addendaRecordIndicator": 0, + "traceNumber": "121042880000001", + "category": "Forward" + } + ], + "batchControl": { + "id": "adam-01", + "serviceClassCode": 200, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000, + "companyIdentification": "121042882", + "ODFIIdentification": "12104288", + "batchNumber": 1 + } + } + ], + "IATBatches": null, + "fileControl": { + "id": "adam-01", + "batchCount": 1, + "blockCount": 1, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000 + }, + "NotificationOfChange": null, + "ReturnEntries": null +} diff --git a/test/testdata/ppd-invalid.json b/test/testdata/ppd-invalid.json new file mode 100644 index 000000000..dac494729 --- /dev/null +++ b/test/testdata/ppd-invalid.json @@ -0,0 +1,67 @@ +{ + "id": "adam-01", + "fileHeader": { + "id": "adam-01, + "immediateDestination": "231380104", + "immediateOrigin": "121042882", + "fileCreationDate": "2018-10-08T00:00:00Z", + "fileCreationTime": "0000-01-01T00:00:00Z", + "fileIDModifier": "A", + "immediateDestinationName": "Citadel", + "immediateOriginName": "Wells Fargo" + }, + "batches": [ + { + "batchHeader": { + "id": "adam-01", + "serviceClassCode": 200, + "companyName": "Wells Fargo", + "companyIdentification": "121042882", + "standardEntryClassCode": "PPD", + "companyEntryDescription": "Trans. Des", + "effectiveEntryDate": "2018-10-09T00:00:00Z", + "ODFIIdentification": "12104288", + "batchNumber": 1 + }, + "entryDetails": [ + { + "id": "adam-01", + "transactionCode": 22, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "81967038518 ", + "amount": 100000, + "identificationNumber": "#83738AB# ", + "individualName": "Steven Tander ", + "discretionaryData": " ", + "addendaRecordIndicator": 0, + "traceNumber": "121042880000001", + "category": "Forward" + } + ], + "batchControl": { + "id": "adam-01", + "serviceClassCode": 200, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000, + "companyIdentification": "121042882", + "ODFIIdentification": "12104288", + "batchNumber": 1 + } + } + ], + "IATBatches": null, + "fileControl": { + "id": "adam-01", + "batchCount": 1, + "blockCount": 1, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000 + }, + "NotificationOfChange": null, + "ReturnEntries": null +} diff --git a/test/testdata/ppd-invalidFile.json b/test/testdata/ppd-invalidFile.json new file mode 100644 index 000000000..825f6ea52 --- /dev/null +++ b/test/testdata/ppd-invalidFile.json @@ -0,0 +1,67 @@ +{ + "id": "adam-01", + "fileHeader": { + "id": "adam-01", + "immediateDestination": "231380104", + "immediateOrigin": "121042882", + "fileCreationDate": "2018-10-08T00:00:00Z", + "fileCreationTime": "0000-01-01T00:00:00Z", + "fileIDModifier": "®©", + "immediateDestinationName": "Citadel", + "immediateOriginName": "Wells Fargo" + }, + "batches": [ + { + "batchHeader": { + "id": "adam-01", + "serviceClassCode": 200, + "companyName": "Wells Fargo", + "companyIdentification": "121042882", + "standardEntryClassCode": "PPD", + "companyEntryDescription": "Trans. Des", + "effectiveEntryDate": "2018-10-09T00:00:00Z", + "ODFIIdentification": "12104288", + "batchNumber": 1 + }, + "entryDetails": [ + { + "id": "adam-01", + "transactionCode": 22, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "81967038518 ", + "amount": 100000, + "identificationNumber": "#83738AB# ", + "individualName": "Steven Tander ", + "discretionaryData": " ", + "addendaRecordIndicator": 0, + "traceNumber": "121042880000001", + "category": "Forward" + } + ], + "batchControl": { + "id": "adam-01", + "serviceClassCode": 200, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000, + "companyIdentification": "121042882", + "ODFIIdentification": "12104288", + "batchNumber": 1 + } + } + ], + "IATBatches": null, + "fileControl": { + "id": "adam-01", + "batchCount": 1, + "blockCount": 1, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000 + }, + "NotificationOfChange": null, + "ReturnEntries": null +} diff --git a/test/testdata/ppd-mixedDebitCredit-valid.json b/test/testdata/ppd-mixedDebitCredit-valid.json new file mode 100644 index 000000000..b32543ecc --- /dev/null +++ b/test/testdata/ppd-mixedDebitCredit-valid.json @@ -0,0 +1,81 @@ +{ + "id": "adam-01", + "fileHeader": { + "id": "adam-01", + "immediateDestination": "231380104", + "immediateOrigin": "121042882", + "fileCreationDate": "181008", + "fileCreationTime": "", + "fileIDModifier": "A", + "immediateDestinationName": "Citadel", + "immediateOriginName": "Wells Fargo" + }, + "batches": [ + { + "batchHeader": { + "id": "adam-01", + "serviceClassCode": 200, + "companyName": "Wells Fargo", + "companyIdentification": "121042882", + "standardEntryClassCode": "PPD", + "companyEntryDescription": "Trans. Des", + "effectiveEntryDate": "181008", + "ODFIIdentification": "12104288", + "batchNumber": 1 + }, + "entryDetails": [ + { + "id": "adam-01", + "transactionCode": 22, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "81967038518 ", + "amount": 100000, + "identificationNumber": "#83738AB# ", + "individualName": "Steven Tander ", + "discretionaryData": " ", + "addendaRecordIndicator": 0, + "traceNumber": "121042880000001", + "category": "Forward" + }, + { + "id": "adam-02", + "transactionCode": 27, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "91987638987 ", + "amount": 100000, + "identificationNumber": "#83738AB# ", + "individualName": "Jason Bogo ", + "discretionaryData": " ", + "addendaRecordIndicator": 0, + "traceNumber": "121042880000002", + "category": "Forward" + } + ], + "batchControl": { + "id": "adam-01", + "serviceClassCode": 200, + "entryAddendaCount": 2, + "entryHash": 23138010, + "totalDebit": 100000, + "totalCredit": 100000, + "companyIdentification": "121042882", + "ODFIIdentification": "12104288", + "batchNumber": 1 + } + } + ], + "IATBatches": null, + "fileControl": { + "id": "adam-01", + "batchCount": 1, + "blockCount": 1, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 200000 + }, + "NotificationOfChange": null, + "ReturnEntries": null +} \ No newline at end of file diff --git a/test/testdata/ppd-mixedDebitCredit.ach b/test/testdata/ppd-mixedDebitCredit.ach new file mode 100644 index 000000000..a0425e652 --- /dev/null +++ b/test/testdata/ppd-mixedDebitCredit.ach @@ -0,0 +1,10 @@ +101 23138010401210428821907181055A094101Federal Reserve Bank My Bank Name +5200Name on Account 121042882 PPDREG.SALARY 190719 1121042880000001 +627231380104123456789 0200000000 Debit Account 0121042880000001 +622231380104987654321 0100000000 Credit Account 1 0121042880000002 +622231380104837098765 0100000000 Credit Account 2 0121042880000003 +82000000030069414030000200000000000200000000121042882 121042880000001 +9000001000001000000030069414030000200000000000200000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/testdata/ppd-no-control-blobs-valid.json b/test/testdata/ppd-no-control-blobs-valid.json new file mode 100644 index 000000000..499469ae9 --- /dev/null +++ b/test/testdata/ppd-no-control-blobs-valid.json @@ -0,0 +1,56 @@ +{ + "id": "adam-01", + "fileHeader": { + "id": "adam-01", + "immediateDestination": "231380104", + "immediateOrigin": "121042882", + "fileCreationDate": "2018-10-08T00:00:00Z", + "fileCreationTime": "0000-01-01T00:00:00Z", + "fileIDModifier": "A", + "immediateDestinationName": "Citadel", + "immediateOriginName": "Wells Fargo" + }, + "batches": [ + { + "batchHeader": { + "id": "adam-01", + "serviceClassCode": 200, + "companyName": "Wells Fargo", + "companyIdentification": "121042882", + "standardEntryClassCode": "PPD", + "companyEntryDescription": "Trans. Des", + "effectiveEntryDate": "2018-10-09T00:00:00Z", + "ODFIIdentification": "12104288", + "batchNumber": 1 + }, + "entryDetails": [ + { + "id": "adam-01", + "transactionCode": 22, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "81967038518 ", + "amount": 100000, + "identificationNumber": "#83738AB# ", + "individualName": "Steven Tander ", + "discretionaryData": " ", + "addendaRecordIndicator": 1, + "traceNumber": "121042880000001", + "category": "Forward", + "addenda05": [ + { + "entryDetailSequenceNumber": 1, + "sequenceNumber": 1, + "paymentRelatedInformation": "Bonus for working on #OSS!", + "typeCode": "", + "id": "gvluehibyuuqdoajiqfn" + } + ] + } + ] + } + ], + "IATBatches": null, + "NotificationOfChange": null, + "ReturnEntries": null +} diff --git a/test/testdata/ppd-noBatches.json b/test/testdata/ppd-noBatches.json new file mode 100644 index 000000000..63543e650 --- /dev/null +++ b/test/testdata/ppd-noBatches.json @@ -0,0 +1,45 @@ +{ + "id": "adam-01", + "fileHeader": { + "id": "adam-01", + "immediateDestination": "231380104", + "immediateOrigin": "121042882", + "fileCreationDate": "2018-10-08T00:00:00Z", + "fileCreationTime": "0000-01-01T00:00:00Z", + "fileIDModifier": "A", + "immediateDestinationName": "Citadel", + "immediateOriginName": "Wells Fargo" + }, + "batches": [ + { + "entryDetails": [ + { + "id": "adam-01", + "transactionCode": 22, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "81967038518 ", + "amount": 100000, + "identificationNumber": "#83738AB# ", + "individualName": "Steven Tander ", + "discretionaryData": " ", + "addendaRecordIndicator": 0, + "traceNumber": "121042880000001", + "category": "Forward" + } + ] + } + ], + "IATBatches": null, + "fileControl": { + "id": "adam-01", + "batchCount": 1, + "blockCount": 1, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000 + }, + "NotificationOfChange": null, + "ReturnEntries": null +} \ No newline at end of file diff --git a/test/testdata/ppd-valid-debit.json b/test/testdata/ppd-valid-debit.json new file mode 100644 index 000000000..6a6be0984 --- /dev/null +++ b/test/testdata/ppd-valid-debit.json @@ -0,0 +1,67 @@ +{ + "id": "adam-01", + "fileHeader": { + "id": "adam-01", + "immediateDestination": "231380104", + "immediateOrigin": "121042882", + "fileCreationDate": "181008", + "fileCreationTime": "", + "fileIDModifier": "A", + "immediateDestinationName": "Citadel", + "immediateOriginName": "Wells Fargo" + }, + "batches": [ + { + "batchHeader": { + "id": "adam-01", + "serviceClassCode": 200, + "companyName": "Wells Fargo", + "companyIdentification": "121042882", + "standardEntryClassCode": "PPD", + "companyEntryDescription": "Trans. Des", + "effectiveEntryDate": "2018-10-09T00:00:00Z", + "ODFIIdentification": "12104288", + "batchNumber": 1 + }, + "entryDetails": [ + { + "id": "adam-01", + "transactionCode": 27, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "81967038518 ", + "amount": 100000, + "identificationNumber": "#83738AB# ", + "individualName": "Steven Tander ", + "discretionaryData": " ", + "addendaRecordIndicator": 0, + "traceNumber": "121042880000001", + "category": "Forward" + } + ], + "batchControl": { + "id": "adam-01", + "serviceClassCode": 200, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 100000, + "totalCredit": 0, + "companyIdentification": "121042882", + "ODFIIdentification": "12104288", + "batchNumber": 1 + } + } + ], + "IATBatches": null, + "fileControl": { + "id": "adam-01", + "batchCount": 1, + "blockCount": 1, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 100000, + "totalCredit": 0 + }, + "NotificationOfChange": null, + "ReturnEntries": null +} diff --git a/test/testdata/ppd-valid.json b/test/testdata/ppd-valid.json new file mode 100644 index 000000000..8662dc538 --- /dev/null +++ b/test/testdata/ppd-valid.json @@ -0,0 +1,67 @@ +{ + "id": "1f707c97-da19-49d0-a3c9-49eebc042e68", + "fileHeader": { + "id": "0a0b39ed-24be-4711-9a87-662d00372e90", + "immediateDestination": "231380104", + "immediateOrigin": "121042882", + "fileCreationDate": "181008", + "fileCreationTime": "", + "fileIDModifier": "A", + "immediateDestinationName": "Citadel", + "immediateOriginName": "Wells Fargo" + }, + "batches": [ + { + "batchHeader": { + "id": "45f0cc00-6228-4e50-bffa-aac0e1902df7", + "serviceClassCode": 200, + "companyName": "Wells Fargo", + "companyIdentification": "121042882", + "standardEntryClassCode": "PPD", + "companyEntryDescription": "Trans. Des", + "effectiveEntryDate": "2018-10-09T00:00:00Z", + "ODFIIdentification": "12104288", + "batchNumber": 1 + }, + "entryDetails": [ + { + "id": "2b5ab363-762c-4459-a8cf-9573fd69029c", + "transactionCode": 22, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "81967038518 ", + "amount": 100000, + "identificationNumber": "#83738AB# ", + "individualName": "Steven Tander ", + "discretionaryData": " ", + "addendaRecordIndicator": 0, + "traceNumber": "121042880000001", + "category": "Forward" + } + ], + "batchControl": { + "id": "a34c8873-50a2-4315-8d98-b9f84ccccc01", + "serviceClassCode": 200, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000, + "companyIdentification": "121042882", + "ODFIIdentification": "12104288", + "batchNumber": 1 + } + } + ], + "IATBatches": null, + "fileControl": { + "id": "1fc34982-0510-4a33-ba0b-9748ea72a254", + "batchCount": 1, + "blockCount": 1, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000 + }, + "NotificationOfChange": null, + "ReturnEntries": null +} diff --git a/testdata/rck.ach b/test/testdata/rck.ach old mode 100755 new mode 100644 similarity index 100% rename from testdata/rck.ach rename to test/testdata/rck.ach diff --git a/test/testdata/return-PPD-custom-reason-code.ach b/test/testdata/return-PPD-custom-reason-code.ach new file mode 100644 index 000000000..09e9ce160 --- /dev/null +++ b/test/testdata/return-PPD-custom-reason-code.ach @@ -0,0 +1,10 @@ +101 092221172 1724443902107221026A094101Fake destination Fake origin +5200dummy company PAYROLL 1234567 PPDDIR DEP 072221210702 1092221170000001 +6210922211721234567 0000106161xxxxxxx3105 Jane Doe DD1092221170000001 +799R97092221172022300 12330515 092221170000001 +820000000200092221170000000000000000001061611234567 092221170000001 +9000001000001000000020009222117000000000000000000106161 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/testdata/return-WEB.ach b/test/testdata/return-WEB.ach new file mode 100644 index 000000000..d81130c86 --- /dev/null +++ b/test/testdata/return-WEB.ach @@ -0,0 +1,10 @@ +101 091400606 6910001341810170306A094101FIRST BANK & TRUST ASF APPLICATION SUPERVI +5200CoinLion 123456789 WEBTRANSFER 000101 1091000010000001 +626091400606123456789 0000012354MjMxNDAwMjAtOGQPaul Jones S 1091000017611242 +799R01091400600000001 09100001 091000017611242 +82000000020009140060000000012354000000000000 123456789 091000010000001 +5200CoinLion 123456789 WEBTRANSFER 000101 1021000020000002 +621091400606867530999999 0000004565NmRjZTJmMzItMGNBob Marley S 1021000029461242 +799R03091400600000003 02100002 021000029461242 +82000000020009140060000000000000000000004565 123456789 021000020000002 +9000002000001000000040018280120000000012354000000004565 \ No newline at end of file diff --git a/test/testdata/return-no-file-header-control.ach b/test/testdata/return-no-file-header-control.ach new file mode 100644 index 000000000..631865a88 --- /dev/null +++ b/test/testdata/return-no-file-header-control.ach @@ -0,0 +1,4 @@ +5200CoinLion 123456789 WEBTRANSFER 000101 1091000010000001 +626091400606123456789 0000012354MjMxNDAwMjAtOGQPaul Jones S 1091000017611242 +799R01091400600000001 09100001 091000017611242 +82000000020009140060000000012354000000000000 123456789 091000010000001 diff --git a/test/testdata/rfc3339.json b/test/testdata/rfc3339.json new file mode 100644 index 000000000..dd4aa72da --- /dev/null +++ b/test/testdata/rfc3339.json @@ -0,0 +1,68 @@ +{ + "id": "rfc3339", + "fileHeader": { + "id": "rfc3339", + "immediateDestination": "231380104", + "immediateOrigin": "121042882", + "fileCreationDate": "2009-11-10T23:00:00Z", + "fileCreationTime": "2009-11-10T23:00:00Z", + "fileIDModifier": "A", + "immediateDestinationName": "Citadel", + "immediateOriginName": "Wells Fargo" + }, + "batches": [ + { + "batchHeader": { + "id": "rfc3339", + "serviceClassCode": 200, + "companyName": "Wells Fargo", + "companyIdentification": "121042882", + "standardEntryClassCode": "PPD", + "companyEntryDescription": "Trans. Des", + "companyDescriptiveDate": "2009-11-10T23:00:00Z", + "effectiveEntryDate": "2009-11-10T23:00:00Z", + "ODFIIdentification": "12104288", + "batchNumber": 1 + }, + "entryDetails": [ + { + "id": "rfc3339", + "transactionCode": 22, + "RDFIIdentification": "23138010", + "checkDigit": "4", + "DFIAccountNumber": "81967038518 ", + "amount": 100000, + "identificationNumber": "#83738AB# ", + "individualName": "Steven Tander ", + "discretionaryData": " ", + "addendaRecordIndicator": 0, + "traceNumber": "121042880000001", + "category": "Forward" + } + ], + "batchControl": { + "id": "rfc3339", + "serviceClassCode": 200, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000, + "companyIdentification": "121042882", + "ODFIIdentification": "12104288", + "batchNumber": 1 + } + } + ], + "IATBatches": null, + "fileControl": { + "id": "rfc3339", + "batchCount": 1, + "blockCount": 1, + "entryAddendaCount": 0, + "entryHash": 23138010, + "totalDebit": 0, + "totalCredit": 100000 + }, + "NotificationOfChange": null, + "ReturnEntries": null +} diff --git a/test/testdata/short-line.ach b/test/testdata/short-line.ach new file mode 100644 index 000000000..a0440c815 --- /dev/null +++ b/test/testdata/short-line.ach @@ -0,0 +1,10 @@ +101 23138010401210428821906240000A094101Federal Reserve Bank My Bank Name +5225Name on Account 121042882 PPDREG.SALARY 190625 1121042880000001 +62723138010412345678 0100000000 Receiver Account Name 0121042880000001 +82250000010023138010000100000000000000000000121042882 121042880000001 +9000001000001000000010023138010000100000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/testdata/two-micro-deposits.ach b/test/testdata/two-micro-deposits.ach new file mode 100644 index 000000000..264036b44 --- /dev/null +++ b/test/testdata/two-micro-deposits.ach @@ -0,0 +1,20 @@ +101 121042882 12104288220032415591094101Moov Bank Moov, Inc +5200Moov - paygate m 001 PPDMoov, Inc 200324200325 1121042880000001 +632121042882322580734 0000000044e681e50d1cc83dcDistracted Austin Mo1121042886829038 +705paygate transaction 00016829038 +632121042882322580734 0000000032e681e50d1cc83dcDistracted Austin Mo1121042886829039 +705paygate transaction 00016829039 +627121042882322580734 0000000076e681e50d1cc83dcDistracted Austin Mo1121042886829040 +705paygate transaction 00016829040 +82000000060036312864000000000076000000000076001 121042880000001 +5200Moov - paygate m 001 PPDMoov, Inc 200324200325 1121042880000002 +632121042882191759324 000000000234e5b6f1cf14232Distracted Austin Mo1121042889211556 +705paygate transaction 00019211556 +632121042882191759324 000000004234e5b6f1cf14232Distracted Austin Mo1121042889211557 +705paygate transaction 00019211557 +627121042882191759324 000000004434e5b6f1cf14232Distracted Austin Mo1121042889211558 +705paygate transaction 00019211558 +82000000060036312864000000000044000000000044001 121042880000002 +9000002000002000000120072625728000000000120000000000120 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/testdata/web-debit.ach b/test/testdata/web-debit.ach similarity index 69% rename from testdata/web-debit.ach rename to test/testdata/web-debit.ach index ff60a1c3d..f1dfa8f64 100644 --- a/testdata/web-debit.ach +++ b/test/testdata/web-debit.ach @@ -1,16 +1,16 @@ -101 081000032 0180362811503042207A094101Some Bank Your Company Inc A0000001 -5220Your Company Inc 0018036281WEBTrnsNicknaMar 5 150305 1081000030000000 +101 031300012 2313801041503042207A094101Some Bank Your Company Inc A0000001 +5220Your Company Inc 0231380104WEBTrnsNicknaMar 5 150305 1081000030000001 622081000210123456789012345670000003521RAj##23920rjf31John Doe S0081000030000000 6220810002105654221 0000002300RAj##32b1kn1bb3Bob Dole S0081000030000001 6220810002105654221 0000002499RAj##765kn4 Adam Something S0081000030000002 6220810002105654221 0000001000RAj##3j43kj4 James Bond S0081000030000003 -822000000400324000840000000000000000000093200018036281 081000030000000 -5220Your Company Inc 0018036281WEBTrnsNicknaMar 16150316 1081000030000001 +822000000400324000840000000000000000000093200231380104 081000030000001 +5220Your Company Inc 0231380104WEBTrnsNicknaMar 16150316 1081000030000002 6220810002105654221 0000017500RAj##8k765j4k32Luke Skywalker S0081000030000004 -822000000100081000210000000000000000000175000018036281 081000030000001 -5225Your Company Inc 0018036281PPDTrnsNicknaMar 6 150306 1081000030000002 +822000000100081000210000000000000000000175000231380104 081000030000002 +5225Your Company Inc 0231380104PPDTrnsNicknaMar 6 150306 1081000030000003 627101000019923698412584 0000015000RAj##765432hj Jane Doe A10081000030000005 -822500000100101000010000000150000000000000000018036281 081000030000002 +822500000100101000010000000150000000000000000231380104 081000030000003 9000003000002000000060050600106000000015000000000026820 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/testdata/web-invalidNOCFile.ach b/test/testdata/web-invalidNOCFile.ach new file mode 100644 index 000000000..af5653ae4 --- /dev/null +++ b/test/testdata/web-invalidNOCFile.ach @@ -0,0 +1,10 @@ +101 231380104 1210428821901160000A094101Federal Reserve Bank My Bank Name +5220Your Company, in 121042882 CORVendor Pay 000000 1121042880000001 +621231380104744-5678-99 0000000000location #23 Best Co. #23 S 1121042880000001 +798C92000000000012345 091012981918171614 091012980000088 +82200000020023138010000000000000000000000000121042882 121042880000001 +9000001000001000000020023138010000000000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/testdata/ppd-debit.ach b/testdata/ppd-debit.ach deleted file mode 100755 index aae128d4e..000000000 --- a/testdata/ppd-debit.ach +++ /dev/null @@ -1,5 +0,0 @@ -101 076401251 0764012510807291511A094101achdestname companyname -5225companyname origid PPDCHECKPAYMT000002080730 1076401250000001 -62705320001912345 0000010500c-1 Bachman Eric DD0076401255655291 -82250000010005320001000000010500000000000000origid 076401250000001 -9000001000001000000010005320001000000010500000000000000 \ No newline at end of file diff --git a/validators.go b/validators.go index 79a111090..738fa72be 100644 --- a/validators.go +++ b/validators.go @@ -1,6 +1,19 @@ -// Copyright 2017 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. package ach @@ -10,69 +23,214 @@ import ( "math" "regexp" "strconv" + "time" + "unicode/utf8" ) -// validator is common validation and formating of golang types to ach type strings +var ( + upperAlphanumericRegex = regexp.MustCompile(fmt.Sprintf( + `[^ A-Z0-9!"#$%%&'()*+,-.\\/:;<>=?@\[\]^_{}|~%s]+`, "`")) + alphanumericRegex = regexp.MustCompile(fmt.Sprintf( + `[^ \w!"#$%%&'()*+,-.\\/:;<>=?@\[\]^_{}|~%s]+`, "`")) +) + +// validator is common validation and formatting of golang types to ach type strings type validator struct{} -// FieldError is returned for errors at a field level in a record -type FieldError struct { - FieldName string // field name where error happend - Value string // value that cause error - Msg string // context of the error. +// isCardTransactionType ensures card transaction type of a batchPOS is valid +func (v *validator) isCardTransactionType(code string) error { + switch code { + case + // Purchase of goods or services + "01", + // Cash + "02", + // Return Reversal + "03", + // Purchase Reversal + "11", + // Cash Reversal + "12", + // Return + "13", + // Adjustment + "21", + // Miscellaneous Transaction + "99": + return nil + } + return ErrCardTransactionType +} + +// isCreditCardYear validates a 2 digit year for credit cards, but +// only accepts a range of years. 2018 to 2050 +func (v *validator) isCreditCardYear(s string) error { + if s < "18" || s > "50" { + return ErrValidYear + } + return nil +} + +// isMonth validates a 2 digit month 01-12 +func (v *validator) isMonth(s string) error { + switch s { + case + "01", "02", "03", "04", "05", "06", + "07", "08", "09", "10", "11", "12": + return nil + } + return ErrValidMonth } -// Error message is constructed -// FieldName Msg Value -// Example1: BatchCount $% has none alphanumeric characters -// Example2: BatchCount 5 is out-of-balance with file count 6 -func (e *FieldError) Error() string { - return fmt.Sprintf("%s %s %s", e.FieldName, e.Value, e.Msg) +// isDay validates a 2 digit day based on a 2 digit month +// month 01-12 day 01-31 based on month +func (v *validator) isDay(m string, d string) error { + switch m { + // February + case "02": + switch d { + case + "01", "02", "03", "04", "05", "06", + "07", "08", "09", "10", "11", "12", + "13", "14", "15", "16", "17", "18", + "19", "20", "21", "22", "23", "24", + "25", "26", "27", "28", "29": + return nil + } + // April, June, September, November + case "04", "06", "09", "11": + switch d { + case + "01", "02", "03", "04", "05", "06", + "07", "08", "09", "10", "11", "12", + "13", "14", "15", "16", "17", "18", + "19", "20", "21", "22", "23", "24", + "25", "26", "27", "28", "29", "30": + return nil + } + // January, March, May, July, August, October, December + case "01", "03", "05", "07", "08", "10", "12": + switch d { + case + "01", "02", "03", "04", "05", "06", + "07", "08", "09", "10", "11", "12", + "13", "14", "15", "16", "17", "18", + "19", "20", "21", "22", "23", "24", + "25", "26", "27", "28", "29", "30", "31": + return nil + } + } + return ErrValidDay +} + +// validateSimpleDate will return the incoming string only if it matches a valid YYMMDD +// date format. (Y=Year, M=Month, D=Day) +func (v *validator) validateSimpleDate(s string) string { + _, err := time.Parse("060102", s) // YYMMDD + if err != nil { + return "" + } + return s } -// Errors specific to validation var ( - msgAlphanumeric = "has non alphanumeric characters" - msgUpperAlpha = "is not uppercase A-Z or 0-9" - msgFieldInclusion = "is a mandatory field and has a default value" - msgValidFieldLength = "is not length %d" - msgServiceClass = "is an invalid Service Class Code" - msgSECCode = "is an invalid Standard Entry Class Code" - msgOrigStatusCode = "is an invalid Originator Status Code" - msgAddendaTypeCode = "is an invalid Addenda Type Code" - msgTransactionCode = "is an invalid Transaction Code" - msgValidCheckDigit = "does not match calculated check digit %d" + // hhmmRegex defines a regex for all valid 24-hour clock timestamps. + // Format: HHmm (H=hour, m=minute) - (first H can only be 0, 1, or 2) + hhmmRegex = regexp.MustCompile(`^([0-2]{1}[\d]{1}[0-5]{1}\d{1})$`) ) -// iServiceClass returns true if a valid service class code -func (v *validator) isServiceClass(code int) error { +// validateSimpleTime will return the incoming string only if it is a valid 24-hour clock time. +func (v *validator) validateSimpleTime(s string) string { + if hhmmRegex.MatchString(s) { + return s // successfully matched and validated + } + return "" +} + +// isForeignExchangeIndicator ensures foreign exchange indicators of an +// IATBatchHeader is valid +func (v *validator) isForeignExchangeIndicator(code string) error { + switch code { + case + "FV", "VF", "FF": + return nil + } + return ErrForeignExchangeIndicator +} + +// isForeignExchangeReferenceIndicator ensures foreign exchange reference +// indicator of am IATBatchHeader is valid +func (v *validator) isForeignExchangeReferenceIndicator(code int) error { + switch code { + case + 1, 2, 3: + return nil + } + return ErrForeignExchangeReferenceIndicator +} + +// isIDNumberQualifier ensures ODFI Identification Number Qualifier is valid +// For Inbound IATs: The 2-digit code that identifies the numbering scheme used in the +// Foreign DFI Identification Number field: +// 01 = National Clearing System +// 02 = BIC Code +// 03 = IBAN Code +// used for both ODFIIDNumberQualifier and RDFIIDNumberQualifier +func (v *validator) isIDNumberQualifier(s string) error { + switch s { + case + "01", "02", "03": + return nil + } + return ErrIDNumberQualifier +} + +// isOriginatorStatusCode ensures status code of a batch is valid +func (v *validator) isOriginatorStatusCode(code int) error { switch code { case - // ACH Mixed Debits and Credits - 200, - // ACH Credits Only - 220, - // ACH Debits Only - 225, - // ACH Automated Accounting Advices - 280: + // ADV file - prepared by an ACH Operator + 0, + // Originator is a financial institution + 1, + // Originator is a Government Agency or other agency not subject to ACH Rules + 2: return nil } - return errors.New(msgServiceClass) + return ErrOrigStatusCode } -// isSECCode returns true if a SEC Code is found +// isSECCode returns true if a SEC Code of a Batch is found func (v *validator) isSECCode(code string) error { switch code { case - "ACK", "ADV", "ARC", "ATX", "BOC", "CCD", "CIE", "COR", "CTX", "DNE", "ENR", - "IAT", "MTE", "POS", "PPD", "POP", "RCK", "SHR", "TEL", "TRC", "TRX", "WEB", "XCK": + ACK, ADV, ARC, ATX, BOC, CCD, CIE, COR, CTX, DNE, ENR, + IAT, MTE, POS, PPD, POP, RCK, SHR, TEL, TRC, TRX, WEB, XCK: + return nil + } + return ErrSECCode +} + +// iServiceClass returns true if a valid service class code of a batch is found +func (v *validator) isServiceClass(code int) error { + switch code { + case + // Mixed Debits and Credits + MixedDebitsAndCredits, + // Credits Only + CreditsOnly, + // Debits Only + DebitsOnly, + // Automated Accounting Advices + AutomatedAccountingAdvices: return nil } - return errors.New(msgSECCode) + return ErrServiceClass } -// isTypeCode returns true if a valid type code is found +// isTypeCode returns true if a valid type code of an Addendum is found +// +// The Addenda Type Code defines the specific interpretation and format for the addenda information contained in the Entry. func (v *validator) isTypeCode(code string) error { switch code { case @@ -85,98 +243,184 @@ func (v *validator) isTypeCode(code string) error { // Return, Dishonored Return and Contested Dishonored Return Entries "99", // IAT forward Entries and IAT Returns - "10", "11", "12", "13", "14", "15", "16", "17", + "10", "11", "12", "13", "14", "15", "16", "17", "18", // ACK, ATX, CCD, CIE, CTX, DNE, ENR, PPD, TRX and WEB Entries "05": return nil } - return errors.New(msgAddendaTypeCode) + return ErrAddendaTypeCode } -// isTransactionCode ensures TransactionCode code is valid +// isTransactionCode ensures TransactionCode of an Entry is valid +// +// The Tran Code is a two-digit code in positions 2 - 3 of the Entry Detail Record (6 Record) within an ACH File. +// The first digit of the Tran Code indicates the account type to which the entry will post, where the number: +// +// "2" designates a Checking Account. +// "3" designates a Savings Account. +// "4" designates a General Ledger Account. +// "5" designates Loan Account. +// +// The second digit of the Tran Code identifies the entry as: +// +// an original forward entry, where the number: +// "2" designates a credit. or +// "7" designates a debit. +// a return or NOC, where the number: +// "1" designates the return/NOC of a credit, or +// "6" designates a return/NOC of a debit. +// a pre-note or non-monetary informational transaction, where the number: +// "3" designates a credit, or +// "8" designates a debit. func (v *validator) isTransactionCode(code int) error { + return StandardTransactionCode(code) +} + +// StandardTransactionCode checks the provided TransactionCode to verify it is a valid NACHA value. +func StandardTransactionCode(code int) error { switch code { // TransactionCode if the receivers account is: case - // Automated Return or Notification of Change for - // original transaction code '22', '23, '24' - 21, - // Credit (deposit) to checking account ‘22’ - 22, - // Prenote for credit to checking account ‘23’ - 23, - // Zero dollar with remittance data (CCD/CTX only) - 24, + // Demand Credit Records (for checking, NOW, and share draft accounts) + + // Automated Return or Notification of Change for original transaction code '22', '23, '24' + CheckingReturnNOCCredit, + // Credit (deposit) to checking account '22' + CheckingCredit, + // Prenote for credit to checking account '23' + CheckingPrenoteCredit, + // Zero dollar with remittance data + CheckingZeroDollarRemittanceCredit, + + // Demand Debit Records (for checking, NOW, and share draft accounts) + // Automated Return or Notification of Change for original transaction code 27, 28, or 29 - 26, - // Debit (withdrawal) to checking account ‘27’ - 27, - // Prenote for debit to checking account ‘28’ - 28, - // Automated Return or Notification of Change for original transaction code 32, 33, or 34 - 31, - // Credit to savings account ‘32’ - 32, - // Prenote for credit to savings account ‘33’ - 33, - // Autmoated Return or Notification of Change for - // original transaction code '37', '38', '39 - 36, - // Debit to savings account ‘37’ - 37, - // Prenote for debit to savings account ‘38’ - 38, - // Zero dollar with remittance data (CCD/CTX only) - 39: + CheckingReturnNOCDebit, + // Debit (withdrawal) to checking account '27' + CheckingDebit, + // Prenote for debit to checking account '28' + CheckingPrenoteDebit, + // Zero dollar with remittance data (for CCD, CTX, and IAT Entries only) + CheckingZeroDollarRemittanceDebit, + + // Savings Account Credit Records + + // Return or Notification of Change for original transaction code 32, 33, or 34 + SavingsReturnNOCCredit, + // Credit to savings account '32' + SavingsCredit, + // Prenote for credit to savings account '33' + SavingsPrenoteCredit, + // Zero dollar with remittance data (for CCD, CTX, and IAT Entries only); Acknowledgment Entries (ACK and ATX Entries only) + SavingsZeroDollarRemittanceCredit, + + // Savings Account Debit Records + + // Automated Return or Notification of Change for original transaction code '37', '38', '39 + SavingsReturnNOCDebit, + // Debit to savings account '37' + SavingsDebit, + // Prenote for debit to savings account '38' + SavingsPrenoteDebit, + // Zero dollar with remittance data + SavingsZeroDollarRemittanceDebit, + + // Financial Institution General Ledger Credit Records + + //Return or Notification of Change for original transaction code 42, 43, or 44 + GLReturnNOCCredit, + // General Ledger Credit + GLCredit, + // Prenotification of General Ledger Credit (non-dollar) + GLPrenoteCredit, + // Zero dollar with remittance data + GLZeroDollarRemittanceCredit, + + // Financial Institution General Ledger Debit Records + + // Return or Notification of Change for original transaction code 47, 48, or 49 + GLReturnNOCDebit, + //General Ledger Debit + GLDebit, + // Prenotification of General Ledger Debit (non-dollar) + GLPrenoteDebit, + // Zero dollar with remittance data + GLZeroDollarRemittanceDebit, + + // Loan Account Credit Records + // Return or Notification of Change for original transaction code 52, 53, or 54 + LoanReturnNOCCredit, + // Loan Account Credit + LoanCredit, + // Prenotification of Loan Account Credit (non-dollar) + LoanPrenoteCredit, + // Zero dollar with remittance data + LoanZeroDollarRemittanceCredit, + + // Loan Account Debit Records (for Reversals Only) + + // Loan Account Debit (Reversals Only) + LoanDebit, + // Return or Notification of Change for original transaction code 55 + LoanReturnNOCDebit, + + // Accounting Records (for use in ADV Files only) + // These transaction codes represent accounting Entries. + + // Credit for ACH debits originated + CreditForDebitsOriginated, + //Debit for ACH credits originated + DebitForCreditsOriginated, + // Credit for ACH credits received + CreditForCreditsReceived, + // Debit for ACH debits received + DebitForDebitsReceived, + // Credit for ACH credits in Rejected batches + CreditForCreditsRejected, + // Debit for ACH debits in Rejected batches + DebitForDebitsRejectedBatches, + // Summary credit for respondent ACH activity + CreditSummary, + // Summary debit for respondent ACH activity + DebitSummary: return nil } - return errors.New(msgTransactionCode) + return ErrTransactionCode } -// isOriginatorStatusCode ensures status code is valid -func (v *validator) isOriginatorStatusCode(code int) error { - switch code { - case - // ADV file - prepared by an ACH Operator - 0, - //Originator is a financial institution - 1, - // Originator is a Government Agency or other agency not subject to ACH Rules - 2: +// isTransactionTypeCode verifies Addenda10 TransactionTypeCode is a valid value +// ANN = Annuity, BUS = Business/Commercial, DEP = Deposit, LOA = Loan, MIS = Miscellaneous, MOR = Mortgage +// PEN = Pension, RLS = Rent/Lease, REM = Remittance2, SAL = Salary/Payroll, TAX = Tax, TEL = Telephone-Initiated Transaction +// WEB = Internet-Initiated Transaction, ARC = Accounts Receivable Entry, BOC = Back Office Conversion Entry, +// POP = Point of Purchase Entry, RCK = Re-presented Check Entry +func (v *validator) isTransactionTypeCode(s string) error { + switch s { + case "ANN", "BUS", "DEP", "LOA", "MIS", "MOR", + "PEN", "RLS", "REM", "SAL", "TAX", TEL, WEB, + ARC, BOC, POP, RCK: return nil } - return errors.New(msgOrigStatusCode) + return ErrTransactionTypeCode } // isUpperAlphanumeric checks if string only contains ASCII alphanumeric upper case characters func (v *validator) isUpperAlphanumeric(s string) error { - if regexp.MustCompile(`[^ A-Z0-9!"#$%&'()*+,-.\\/:;<>=?@\[\]^_{}|~]+`).MatchString(s) { - return errors.New(msgUpperAlpha) + if upperAlphanumericRegex.MatchString(s) { + return ErrUpperAlpha } return nil } // isAlphanumeric checks if a string only contains ASCII alphanumeric characters func (v *validator) isAlphanumeric(s string) error { - if regexp.MustCompile(`[^ \w!"#$%&'()*+,-.\\/:;<>=?@\[\]^_{}|~]+`).MatchString(s) { + if alphanumericRegex.MatchString(s) { // ^[ A-Za-z0-9_@./#&+-]*$/ - return errors.New(msgAlphanumeric) - } - return nil -} - -// isOriginatorStatusCode ensures status code is valid -func (v *validator) isCheckDigit(routingNumber string, checkDigit int) error { - calculated := v.CalculateCheckDigit(routingNumber) - if calculated != checkDigit { - msg := fmt.Sprintf(msgValidCheckDigit, calculated) - return &FieldError{FieldName: "RoutingNumber", Value: routingNumber, Msg: msg} - //return msgValidCheckDigit + return ErrNonAlphanumeric } return nil } -// CalculateCheckDigit returns a check digit for a rounting number +// CalculateCheckDigit returns a check digit for a routing number // Multiply each digit in the Routing number by a weighting factor. The weighting factors for each digit are: // Position: 1 2 3 4 5 6 7 8 // Weights : 3 7 1 3 7 1 3 7 @@ -184,12 +428,19 @@ func (v *validator) isCheckDigit(routingNumber string, checkDigit int) error { // Subtract the sum from the next highest multiple of 10. // The result is the Check Digit func (v *validator) CalculateCheckDigit(routingNumber string) int { + if n := utf8.RuneCountInString(routingNumber); n != 8 && n != 9 { + return -1 + } + var routeIndex [8]string for i := 0; i < 8; i++ { + if routingNumber[i] < '0' || routingNumber[i] > '9' { + return -1 // only digits are allowed + } routeIndex[i] = string(routingNumber[i]) } n, _ := strconv.Atoi(routeIndex[0]) - sum := (n * 3) + sum := n * 3 n, _ = strconv.Atoi(routeIndex[1]) sum = sum + (n * 7) n, _ = strconv.Atoi(routeIndex[2]) @@ -208,7 +459,46 @@ func (v *validator) CalculateCheckDigit(routingNumber string) int { return v.roundUp10(sum) - sum } +// CheckRoutingNumber returns a nil error if the provided routingNumber is valid according to +// NACHA rules. See CalculateCheckDigit for details on computing the check digit. +func CheckRoutingNumber(routingNumber string) error { + if routingNumber == "" { + return errors.New("no routing number provided") + } + if n := utf8.RuneCountInString(routingNumber); n != 9 { + return fmt.Errorf("invalid routing number length of %d", n) + } + + v := new(validator) + check := fmt.Sprintf("%d", v.CalculateCheckDigit(routingNumber)) + last := string(routingNumber[len(routingNumber)-1]) + if check != last { + return fmt.Errorf("routing number checksum mismatch: expected %s but got %s", check, last) + } + return nil +} + // roundUp10 round number up to the next ten spot. func (v *validator) roundUp10(n int) int { return int(math.Ceil(float64(n)/10.0)) * 10 } + +func (v *validator) validateSettlementDate(s string) string { + emptyField := " " + + if s == emptyField || len(s) != len(emptyField) { + return emptyField + } + + day, err := strconv.Atoi(s) + if err != nil { + return emptyField + } + + if day < 1 || day > 366 { + return emptyField + } + + return s + +} diff --git a/validators_test.go b/validators_test.go new file mode 100644 index 000000000..83128917b --- /dev/null +++ b/validators_test.go @@ -0,0 +1,189 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +import ( + "fmt" + "testing" +) + +func TestValidators__checkDigit(t *testing.T) { + cases := map[string]int{ + // invalid + "": -1, + "123456": -1, + "1a8ab": -1, + "0730002a": -1, + "0730A002": -1, + "YYYYYYYYY": -1, // users often mask ABA numbers + // valid + "07300022": 8, // Wells Fargo - Iowa + "10200007": 6, // Wells Fargo - Colorado + } + + v := validator{} + for rtn, check := range cases { + answer := v.CalculateCheckDigit(rtn) + if check != answer { + t.Errorf("input=%s answer=%d expected=%d", rtn, answer, check) + } + if err := CheckRoutingNumber(fmt.Sprintf("%s%d", rtn, check)); err != nil && check >= 0 { + t.Errorf("input=%s answer=%d expected=%d: %v", rtn, answer, check, err) + } + } +} + +func TestValidators__isCreditCardYear(t *testing.T) { + cases := map[string]bool{ + // invalid (or out of range until 2051 or 2100-2117) + "10": false, + "00": false, + "51": false, + "17": false, + // valid + "20": true, + "19": true, + } + v := validator{} + for yy, valid := range cases { + err := v.isCreditCardYear(yy) + if valid && err != nil { + t.Errorf("yy=%s failed: %v", yy, err) + } + if !valid && err == nil { + t.Errorf("yy=%s should have failed", yy) + } + } +} + +func TestValidators__validateSimpleDate(t *testing.T) { + cases := map[string]string{ + // invalid + "": "", + "01": "", + "001520": "", // no 15th month + "001240": "", // no 40th Day + "190001": "", // no 0th month + "190100": "", // no 0th day + "230229": "", // Feb 29th 2023 is not a leap year + // valid + "190101": "190101", // Jan 1st + "201231": "201231", // Dec 31st + "220731": "220731", // July 31st + "350430": "350430", // April 30th + "240229": "240229", // Feb 29th 2024 (Leap Year) + } + + v := validator{} + for input, expected := range cases { + answer := v.validateSimpleDate(input) + if expected != answer { + t.Errorf("input=%q got=%q expected=%q", input, answer, expected) + } + } +} + +func TestValidators__validateSimpleTime(t *testing.T) { + cases := map[string]string{ + // invalid + "": "", + "01": "", + "012": "", + "123142": "", + // valid + "0000": "0000", + "0100": "0100", + "2359": "2359", + "1201": "1201", + "1238": "1238", + } + v := validator{} + for input, expected := range cases { + answer := v.validateSimpleTime(input) + if expected != answer { + t.Errorf("input=%q got=%q expected=%q", input, answer, expected) + } + } +} + +func TestValidators__isAlphanumeric(t *testing.T) { + v := validator{} + + tests := []struct { + name string + checkFunc func(string) error + shouldErr func(i int) bool + }{ + // Ensure that ASCII characters from 0x20 to 0x7E are considered alphanumeric. + { + name: "is alphanumeric", + checkFunc: v.isAlphanumeric, + shouldErr: func(i int) bool { + return i < 0x20 || i > 0x7E + }, + }, + // Ensure that ASCII characters from 0x20 to 0x60 and 0x7B to 0x7E are considered upper case alphanumeric. + { + name: "is upper alphanumeric", + checkFunc: v.isUpperAlphanumeric, + shouldErr: func(i int) bool { + return i < 0x20 || i > 0x7E || (i > 0x60 && i < 0x7B) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for i := 0; i < 255; i++ { + chr := string(rune(i)) + err := tt.checkFunc(chr) + shouldError := tt.shouldErr(i) + + if shouldError && err == nil { + t.Errorf("expected rune %x (%s) to be non-alphanumeric", i, chr) + } else if !shouldError && err != nil { + t.Errorf("expected rune %x (%s) to be alphanumeric: %v", i, chr, err) + } + } + }) + } +} + +func TestValidators__validateJulianDay(t *testing.T) { + empty := " " + cases := map[string]string{ + // invalid + "": empty, + " ": empty, + "01": empty, + "01234": empty, + "XXX": empty, + "000": empty, + "367": empty, + // valid + "001": "001", + "020": "020", + "366": "366", + } + v := validator{} + for input, valid := range cases { + if v.validateSettlementDate(input) != valid { + t.Errorf("julian day=%s failed", input) + } + } +} diff --git a/version.go b/version.go new file mode 100644 index 000000000..0166391b8 --- /dev/null +++ b/version.go @@ -0,0 +1,21 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ach + +// Version Number +const Version = "v1.19.3" diff --git a/writer.go b/writer.go index 023499763..29369e342 100644 --- a/writer.go +++ b/writer.go @@ -1,6 +1,19 @@ -// Copyright 2016 The ACH Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. package ach @@ -10,94 +23,222 @@ import ( "strings" ) -// A Writer writes an ach.file to a NACHA encoded file. -// -// As returned by NewWriter, a Writer writes ach.file structs into -// NACHA formted files. -// +// Writer writes a File to an io.Writer. +// The File is validated against Nacha guidelines unless BypassValidation is enabled. type Writer struct { - w *bufio.Writer - lineNum int //current line being written + w *bufio.Writer + lineNum int //current line being written + LineEnding string // configurable line ending to support different consumer requirements + // BypassValidation can be set to skip file validation and will allow non-compliant Nacha files to be written. + BypassValidation bool } // NewWriter returns a new Writer that writes to w. func NewWriter(w io.Writer) *Writer { return &Writer{ - w: bufio.NewWriter(w), + w: bufio.NewWriter(w), + LineEnding: "\n", //set default line ending } } // Writer writes a single ach.file record to w func (w *Writer) Write(file *File) error { - if err := file.Validate(); err != nil { - return err + if !w.BypassValidation { + if err := file.Validate(); err != nil { + return err + } } w.lineNum = 0 // Iterate over all records in the file - if _, err := w.w.WriteString(file.Header.String() + "\n"); err != nil { + if _, err := w.w.WriteString(file.Header.String() + w.LineEnding); err != nil { return err } w.lineNum++ - for _, batch := range file.Batches { - if _, err := w.w.WriteString(batch.GetHeader().String() + "\n"); err != nil { + if err := w.writeBatch(file); err != nil { + return err + } + + if err := w.writeIATBatch(file); err != nil { + return err + } + + if !file.IsADV() { + if _, err := w.w.WriteString(file.Control.String() + w.LineEnding); err != nil { return err } - w.lineNum++ - for _, entry := range batch.GetEntries() { - if _, err := w.w.WriteString(entry.String() + "\n"); err != nil { - return err - } - w.lineNum++ - for _, addenda := range entry.Addendum { - if _, err := w.w.WriteString(addenda.String() + "\n"); err != nil { - return err - } - w.lineNum++ - } - } - if _, err := w.w.WriteString(batch.GetControl().String() + "\n"); err != nil { + } else { + if _, err := w.w.WriteString(file.ADVControl.String() + w.LineEnding); err != nil { return err } - w.lineNum++ - } - if _, err := w.w.WriteString(file.Control.String() + "\n"); err != nil { - return err } w.lineNum++ // pad the final block for i := 0; i < (10-(w.lineNum%10)) && w.lineNum%10 != 0; i++ { - if _, err := w.w.WriteString(strings.Repeat("9", 94) + "\n"); err != nil { + if _, err := w.w.WriteString(strings.Repeat("9", 94) + w.LineEnding); err != nil { return err } } - return nil + return w.w.Flush() } // Flush writes any buffered data to the underlying io.Writer. -// To check if an error occurred during the Flush, call Error. -func (w *Writer) Flush() { - w.w.Flush() +func (w *Writer) Flush() error { + return w.w.Flush() } -// Error reports any error that has occurred during a previous Write or Flush. -func (w *Writer) Error() error { - _, err := w.w.Write(nil) - return err +func (w *Writer) writeBatch(file *File) error { + for _, batch := range file.Batches { + if _, err := w.w.WriteString(batch.GetHeader().String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + if !file.IsADV() { + for _, entry := range batch.GetEntries() { + if _, err := w.w.WriteString(entry.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + + if entry.Addenda02 != nil { + if _, err := w.w.WriteString(entry.Addenda02.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + } + for _, addenda05 := range entry.Addenda05 { + if _, err := w.w.WriteString(addenda05.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + } + if entry.Addenda98 != nil { + if _, err := w.w.WriteString(entry.Addenda98.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + } + if entry.Addenda99 != nil { + if _, err := w.w.WriteString(entry.Addenda99.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + } + if entry.Addenda99Dishonored != nil { + if _, err := w.w.WriteString(entry.Addenda99Dishonored.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + } + if entry.Addenda99Contested != nil { + if _, err := w.w.WriteString(entry.Addenda99Contested.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + } + } + } else { + for _, entry := range batch.GetADVEntries() { + if _, err := w.w.WriteString(entry.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + if entry.Addenda99 != nil { + if _, err := w.w.WriteString(entry.Addenda99.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + } + } + } + + if batch.GetHeader().StandardEntryClassCode != ADV { + if _, err := w.w.WriteString(batch.GetControl().String() + w.LineEnding); err != nil { + return err + } + } else { + if _, err := w.w.WriteString(batch.GetADVControl().String() + w.LineEnding); err != nil { + return err + } + } + w.lineNum++ + } + return nil } -// WriteAll writes multiple ach.fieles to w using Write and then calls Flush. -func (w *Writer) WriteAll(files []*File) error { - for _, file := range files { - err := w.Write(file) - // TODO if one of the files errors at a Writer struct flag to decide if - // the other files should still be written - if err != nil { +func (w *Writer) writeIATBatch(file *File) error { + for _, iatBatch := range file.IATBatches { + if _, err := w.w.WriteString(iatBatch.GetHeader().String() + w.LineEnding); err != nil { return err } + w.lineNum++ + for _, entry := range iatBatch.GetEntries() { + if _, err := w.w.WriteString(entry.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + if _, err := w.w.WriteString(entry.Addenda10.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + if _, err := w.w.WriteString(entry.Addenda11.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + if _, err := w.w.WriteString(entry.Addenda12.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + if _, err := w.w.WriteString(entry.Addenda13.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + if _, err := w.w.WriteString(entry.Addenda14.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + if _, err := w.w.WriteString(entry.Addenda15.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + if _, err := w.w.WriteString(entry.Addenda16.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + // IAT Addenda17 + for _, addenda17 := range entry.Addenda17 { + if _, err := w.w.WriteString(addenda17.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + } + // IAT Addenda18 + for _, addenda18 := range entry.Addenda18 { + if _, err := w.w.WriteString(addenda18.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + } + if entry.Addenda98 != nil { + if _, err := w.w.WriteString(entry.Addenda98.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + } + if entry.Addenda99 != nil { + if _, err := w.w.WriteString(entry.Addenda99.String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ + } + } + if _, err := w.w.WriteString(iatBatch.GetControl().String() + w.LineEnding); err != nil { + return err + } + w.lineNum++ } - return w.w.Flush() + return nil } diff --git a/writer_test.go b/writer_test.go index 2ea8120b1..40b009702 100644 --- a/writer_test.go +++ b/writer_test.go @@ -1,19 +1,702 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + package ach import ( "bytes" "strings" "testing" + + "github.com/moov-io/base" + + "github.com/stretchr/testify/require" ) +// testPPDWrite writes a PPD ACH file +func testPPDWrite(t testing.TB) { + file := NewFile().SetHeader(mockFileHeader()) + entry := mockEntryDetail() + entry.AddendaRecordIndicator = 1 + entry.AddAddenda05(mockAddenda05()) + batch := NewBatchPPD(mockBatchPPDHeader()) + batch.SetHeader(mockBatchHeader()) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + t.Fatal(err) + } + file.AddBatch(batch) + + if err := file.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + if err := file.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + + b := &bytes.Buffer{} + f := NewWriter(b) + + if err := f.Write(file); err != nil { + t.Errorf("%T: %s", err, err) + } + + // Verify we output the expected number of lines + require.Equal(t, 10, strings.Count(b.String(), "\n")) + + r := NewReader(strings.NewReader(b.String())) + _, err := r.Read() + if err != nil { + t.Errorf("%T: %s", err, err) + } + if err = r.File.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestPPDWrite tests writing a PPD ACH file func TestPPDWrite(t *testing.T) { + testPPDWrite(t) +} + +// BenchmarkPPDWrite benchmarks validating writing a PPD ACH file +func BenchmarkPPDWrite(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testPPDWrite(b) + } +} + +// testFileWriteErr validates error for file write +func testFileWriteErr(t testing.TB) { + file := NewFile().SetHeader(mockFileHeader()) + entry := mockEntryDetail() + entry.AddendaRecordIndicator = 1 + entry.AddAddenda05(mockAddenda05()) + batch := NewBatchPPD(mockBatchPPDHeader()) + batch.SetHeader(mockBatchHeader()) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + t.Fatal(err) + } + if err := batch.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + file.AddBatch(batch) + + if err := file.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + if err := file.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + + file.Batches[0].GetControl().EntryAddendaCount = 10 + + b := &bytes.Buffer{} + f := NewWriter(b) + + err := f.Write(file) + if !base.Match(err, NewErrBatchCalculatedControlEquality(10, 2)) { + t.Errorf("%T: %s", err, err) + } +} + +// TestFileWriteErr tests validating error for file write +func TestFileWriteErr(t *testing.T) { + testFileWriteErr(t) +} + +// BenchmarkFileWriteErr benchmarks error for file write +func BenchmarkFileWriteErr(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testFileWriteErr(b) + } +} + +// testIATWrite writes a IAT ACH file +func testIATWrite(t testing.TB) { + file := NewFile().SetHeader(mockFileHeader()) + iatBatch := IATBatch{} + iatBatch.SetHeader(mockIATBatchHeaderFF()) + iatBatch.AddEntry(mockIATEntryDetail()) + iatBatch.Entries[0].Addenda10 = mockAddenda10() + iatBatch.Entries[0].Addenda11 = mockAddenda11() + iatBatch.Entries[0].Addenda12 = mockAddenda12() + iatBatch.Entries[0].Addenda13 = mockAddenda13() + iatBatch.Entries[0].Addenda14 = mockAddenda14() + iatBatch.Entries[0].Addenda15 = mockAddenda15() + iatBatch.Entries[0].Addenda16 = mockAddenda16() + iatBatch.Entries[0].AddAddenda17(mockAddenda17()) + iatBatch.Entries[0].AddAddenda17(mockAddenda17B()) + iatBatch.Entries[0].AddAddenda18(mockAddenda18()) + iatBatch.Entries[0].AddAddenda18(mockAddenda18B()) + iatBatch.Entries[0].AddAddenda18(mockAddenda18C()) + iatBatch.Entries[0].AddAddenda18(mockAddenda18D()) + iatBatch.Entries[0].AddAddenda18(mockAddenda18E()) + if err := iatBatch.Create(); err != nil { + t.Fatal(err) + } + file.AddIATBatch(iatBatch) + + iatBatch2 := IATBatch{} + iatBatch2.SetHeader(mockIATBatchHeaderFF()) + iatBatch2.AddEntry(mockIATEntryDetail()) + iatBatch2.GetEntries()[0].TransactionCode = CheckingDebit + iatBatch2.GetEntries()[0].Amount = 2000 + iatBatch2.Entries[0].Addenda10 = mockAddenda10() + iatBatch2.Entries[0].Addenda11 = mockAddenda11() + iatBatch2.Entries[0].Addenda12 = mockAddenda12() + iatBatch2.Entries[0].Addenda13 = mockAddenda13() + iatBatch2.Entries[0].Addenda14 = mockAddenda14() + iatBatch2.Entries[0].Addenda15 = mockAddenda15() + iatBatch2.Entries[0].Addenda16 = mockAddenda16() + iatBatch2.Entries[0].AddAddenda17(mockAddenda17()) + iatBatch2.Entries[0].AddAddenda17(mockAddenda17B()) + iatBatch2.Entries[0].AddAddenda18(mockAddenda18()) + iatBatch2.Entries[0].AddAddenda18(mockAddenda18B()) + iatBatch2.Entries[0].AddAddenda18(mockAddenda18C()) + iatBatch2.Entries[0].AddAddenda18(mockAddenda18D()) + iatBatch2.Entries[0].AddAddenda18(mockAddenda18E()) + if err := iatBatch2.Create(); err != nil { + t.Fatal(err) + } + file.AddIATBatch(iatBatch2) + + if err := file.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + if err := file.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + + b := &bytes.Buffer{} + f := NewWriter(b) + + if err := f.Write(file); err != nil { + t.Errorf("%T: %s", err, err) + } + + r := NewReader(strings.NewReader(b.String())) + _, err := r.Read() + if err != nil { + t.Errorf("%T: %s", err, err) + } + if err = r.File.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestIATWrite tests writing a IAT ACH file +func TestIATWrite(t *testing.T) { + testIATWrite(t) +} + +// BenchmarkIATWrite benchmarks validating writing a IAT ACH file +func BenchmarkIATWrite(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATWrite(b) + } +} + +// testPPDIATWrite writes an ACH file which writing an ACH file which contains PPD and IAT entries +func testPPDIATWrite(t testing.TB) { + file := NewFile().SetHeader(mockFileHeader()) + + entry := mockEntryDetail() + entry.AddendaRecordIndicator = 1 + entry.AddAddenda05(mockAddenda05()) + batch := NewBatchPPD(mockBatchPPDHeader()) + batch.SetHeader(mockBatchHeader()) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + t.Fatal(err) + } + file.AddBatch(batch) + + iatBatch := IATBatch{} + iatBatch.SetHeader(mockIATBatchHeaderFF()) + iatBatch.AddEntry(mockIATEntryDetail()) + iatBatch.Entries[0].Addenda10 = mockAddenda10() + iatBatch.Entries[0].Addenda11 = mockAddenda11() + iatBatch.Entries[0].Addenda12 = mockAddenda12() + iatBatch.Entries[0].Addenda13 = mockAddenda13() + iatBatch.Entries[0].Addenda14 = mockAddenda14() + iatBatch.Entries[0].Addenda15 = mockAddenda15() + iatBatch.Entries[0].Addenda16 = mockAddenda16() + iatBatch.Entries[0].AddAddenda17(mockAddenda17()) + iatBatch.Entries[0].AddAddenda17(mockAddenda17B()) + iatBatch.Entries[0].AddAddenda18(mockAddenda18()) + iatBatch.Entries[0].AddAddenda18(mockAddenda18B()) + iatBatch.Entries[0].AddAddenda18(mockAddenda18C()) + iatBatch.Entries[0].AddAddenda18(mockAddenda18D()) + iatBatch.Entries[0].AddAddenda18(mockAddenda18E()) + if err := iatBatch.Create(); err != nil { + t.Fatal(err) + } + file.AddIATBatch(iatBatch) + + iatBatch2 := IATBatch{} + iatBatch2.SetHeader(mockIATBatchHeaderFF()) + iatBatch2.AddEntry(mockIATEntryDetail()) + iatBatch2.GetEntries()[0].TransactionCode = CheckingDebit + iatBatch2.GetEntries()[0].Amount = 2000 + iatBatch2.Entries[0].Addenda10 = mockAddenda10() + iatBatch2.Entries[0].Addenda11 = mockAddenda11() + iatBatch2.Entries[0].Addenda12 = mockAddenda12() + iatBatch2.Entries[0].Addenda13 = mockAddenda13() + iatBatch2.Entries[0].Addenda14 = mockAddenda14() + iatBatch2.Entries[0].Addenda15 = mockAddenda15() + iatBatch2.Entries[0].Addenda16 = mockAddenda16() + iatBatch2.Entries[0].AddAddenda17(mockAddenda17()) + iatBatch2.Entries[0].AddAddenda17(mockAddenda17B()) + iatBatch2.Entries[0].AddAddenda18(mockAddenda18()) + iatBatch2.Entries[0].AddAddenda18(mockAddenda18B()) + iatBatch2.Entries[0].AddAddenda18(mockAddenda18C()) + iatBatch2.Entries[0].AddAddenda18(mockAddenda18D()) + iatBatch2.Entries[0].AddAddenda18(mockAddenda18E()) + if err := iatBatch2.Create(); err != nil { + t.Fatal(err) + } + file.AddIATBatch(iatBatch2) + + if err := file.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + if err := file.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + + b := &bytes.Buffer{} + f := NewWriter(b) + + if err := f.Write(file); err != nil { + t.Errorf("%T: %s", err, err) + } + + r := NewReader(strings.NewReader(b.String())) + _, err := r.Read() + if err != nil { + t.Errorf("%T: %s", err, err) + } + if err = r.File.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + + /* // Write records to standard output. Anything io.Writer + w := NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error: %s\n", err) + } + w.Flush()*/ +} + +// TestPPDIATWrite tests writing a IAT ACH file +func TestPPDIATWrite(t *testing.T) { + testPPDIATWrite(t) +} + +// BenchmarkPPDIATWrite benchmarks validating writing an ACH file which contain PPD and IAT entries +func BenchmarkPPDIATWrite(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testPPDIATWrite(b) + } +} + +// testIATReturn writes a IAT ACH Return file +func testIATReturn(t testing.TB) { + file := NewFile().SetHeader(mockFileHeader()) + iatBatch := IATBatch{} + iatBatch.SetHeader(mockIATBatchHeaderFF()) + iatBatch.AddEntry(mockIATEntryDetail()) + iatBatch.Entries[0].Addenda10 = mockAddenda10() + iatBatch.Entries[0].Addenda11 = mockAddenda11() + iatBatch.Entries[0].Addenda12 = mockAddenda12() + iatBatch.Entries[0].Addenda13 = mockAddenda13() + iatBatch.Entries[0].Addenda14 = mockAddenda14() + iatBatch.Entries[0].Addenda15 = mockAddenda15() + iatBatch.Entries[0].Addenda16 = mockAddenda16() + iatBatch.Entries[0].Addenda99 = mockIATAddenda99() + iatBatch.Entries[0].Category = CategoryReturn + if err := iatBatch.Create(); err != nil { + t.Fatal(err) + } + file.AddIATBatch(iatBatch) + + if err := file.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + if err := file.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + + b := &bytes.Buffer{} + f := NewWriter(b) + + if err := f.Write(file); err != nil { + t.Errorf("%T: %s", err, err) + } + + r := NewReader(strings.NewReader(b.String())) + _, err := r.Read() + if err != nil { + t.Errorf("%T: %s", err, err) + } + if err = r.File.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + + /* // Write IAT records to standard output. Anything io.Writer + w := NewWriter(os.Stdout) + if err := w.Write(file); err != nil { + log.Fatalf("Unexpected error: %s\n", err) + } + w.Flush()*/ +} + +// TestIATReturn tests writing a IAT ACH Return file +func TestIATReturn(t *testing.T) { + testIATReturn(t) +} + +// BenchmarkIATReturn benchmarks validating writing a IAT ACH Return file +func BenchmarkIATReturn(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testIATReturn(b) + } +} + +// TestIATNOC writes and then reads an IAT ACH NOC +func TestIATNOC(t *testing.T) { + file := NewFile().SetHeader(mockFileHeader()) + iatBatch := IATBatch{} + iatBatch.SetHeader(mockIATBatchHeaderFF()) + iatBatch.Header.IATIndicator = IATCOR + iatBatch.Header.StandardEntryClassCode = "COR" + iatBatch.AddEntry(mockIATEntryDetail()) + iatBatch.Entries[0].TransactionCode = CheckingReturnNOCCredit + iatBatch.Entries[0].Addenda10 = mockAddenda10() + iatBatch.Entries[0].Addenda11 = mockAddenda11() + iatBatch.Entries[0].Addenda12 = mockAddenda12() + iatBatch.Entries[0].Addenda13 = mockAddenda13() + iatBatch.Entries[0].Addenda14 = mockAddenda14() + iatBatch.Entries[0].Addenda15 = mockAddenda15() + iatBatch.Entries[0].Addenda16 = mockAddenda16() + iatBatch.Entries[0].Addenda98 = mockIATAddenda98() + iatBatch.Entries[0].Category = CategoryNOC + if err := iatBatch.Create(); err != nil { + t.Fatal(err) + } + file.AddIATBatch(iatBatch) + + if err := file.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + if err := file.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + + b := &bytes.Buffer{} + f := NewWriter(b) + + if err := f.Write(file); err != nil { + t.Errorf("%T: %s", err, err) + } + + r := NewReader(strings.NewReader(b.String())) + _, err := r.Read() + if err != nil { + t.Errorf("%T: %s", err, err) + } + if err = r.File.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVWrite writes a ADV ACH file +func TestADVWrite(t *testing.T) { + file := NewFile().SetHeader(mockFileHeader()) + entry := mockADVEntryDetail() + entry.AddendaRecordIndicator = 0 + batch := NewBatchADV(mockBatchADVHeader()) + batch.SetHeader(mockBatchADVHeader()) + batch.AddADVEntry(entry) + if err := batch.Create(); err != nil { + t.Fatal(err) + } + file.AddBatch(batch) + + if err := file.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + if err := file.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + + b := &bytes.Buffer{} + f := NewWriter(b) + + if err := f.Write(file); err != nil { + t.Errorf("%T: %s", err, err) + } + + r := NewReader(strings.NewReader(b.String())) + _, err := r.Read() + if err != nil { + t.Errorf("%T: %s", err, err) + } + if err = r.File.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestPOSWrite writes a POS ACH file +func TestPOSWrite(t *testing.T) { + file := NewFile().SetHeader(mockFileHeader()) + entry := mockPOSEntryDetail() + entry.AddendaRecordIndicator = 1 + entry.Addenda02 = mockAddenda02() + posHeader := mockBatchPOSHeader() + batch := NewBatchPOS(posHeader) + batch.SetHeader(posHeader) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + t.Fatal(err) + } + file.AddBatch(batch) + + if err := file.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + if err := file.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + + b := &bytes.Buffer{} + f := NewWriter(b) + + if err := f.Write(file); err != nil { + t.Errorf("%T: %s", err, err) + } + + r := NewReader(strings.NewReader(b.String())) + _, err := r.Read() + if err != nil { + t.Errorf("%T: %s", err, err) + } + if err = r.File.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestPOSReturnWrite writes a POS Return ACH file +func TestPOSReturnWrite(t *testing.T) { + file := NewFile().SetHeader(mockFileHeader()) + entry := mockPOSEntryDetail() + entry.AddendaRecordIndicator = 1 + entry.Addenda99 = mockAddenda99() + entry.Category = CategoryReturn + posHeader := mockBatchPOSHeader() + batch := NewBatchPOS(posHeader) + batch.SetHeader(posHeader) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + t.Fatal(err) + } + file.AddBatch(batch) + + if err := file.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + if err := file.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + + b := &bytes.Buffer{} + f := NewWriter(b) + + if err := f.Write(file); err != nil { + t.Errorf("%T: %s", err, err) + } + + r := NewReader(strings.NewReader(b.String())) + _, err := r.Read() + if err != nil { + t.Errorf("%T: %s", err, err) + } + if err = r.File.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestPOSDishonoredReturnWrite writes a POS Return ACH file +func TestPOSDishonoredReturnWrite(t *testing.T) { + file := NewFile().SetHeader(mockFileHeader()) + entry := NewEntryDetail() + entry.TransactionCode = CheckingDebit + entry.SetRDFI("121042882") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 25000 + entry.IdentificationNumber = "45689033" + entry.IndividualName = "Wade Arnold" + entry.SetTraceNumber(mockBatchPOSHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.AddendaRecordIndicator = 1 + entry.Category = CategoryDishonoredReturn + + addenda99Dishonored := mockAddenda99Dishonored() + entry.Addenda99Dishonored = addenda99Dishonored + + posHeader := NewBatchHeader() + posHeader.ServiceClassCode = DebitsOnly + posHeader.StandardEntryClassCode = POS + posHeader.CompanyName = "Payee Name" + posHeader.CompanyIdentification = "231380104" + posHeader.CompanyEntryDescription = "ACH POS" + posHeader.ODFIIdentification = "23138010" + + batch := NewBatchPOS(posHeader) + batch.SetHeader(posHeader) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + t.Fatal(err) + } + file.AddBatch(batch) + + if err := file.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + if err := file.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + + b := &bytes.Buffer{} + f := NewWriter(b) + + if err := f.Write(file); err != nil { + t.Errorf("%T: %s", err, err) + } + + r := NewReader(strings.NewReader(b.String())) + _, err := r.Read() + if err != nil { + t.Errorf("%T: %s", err, err) + } + if err = r.File.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestNOCWrite writes a COR NOC ACH file +func TestNOCWrite(t *testing.T) { + file := NewFile().SetHeader(mockFileHeader()) + entry := mockCOREntryDetail() + entry.AddendaRecordIndicator = 1 + entry.Addenda98 = mockAddenda98() + entry.Category = CategoryNOC + corHeader := mockBatchCORHeader() + batch := NewBatchCOR(corHeader) + batch.SetHeader(corHeader) + batch.AddEntry(entry) + if err := batch.Create(); err != nil { + t.Fatal(err) + } + file.AddBatch(batch) + + if err := file.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + if err := file.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + + b := &bytes.Buffer{} + f := NewWriter(b) + + if err := f.Write(file); err != nil { + t.Errorf("%T: %s", err, err) + } + + r := NewReader(strings.NewReader(b.String())) + _, err := r.Read() + if err != nil { + t.Errorf("%T: %s", err, err) + } + if err = r.File.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestADVReturnWrite writes a ADV Return Return ACH file +func TestADVReturnWrite(t *testing.T) { + file := NewFile().SetHeader(mockFileHeader()) + entry := mockADVEntryDetail() + entry.AddendaRecordIndicator = 1 + entry.Addenda99 = mockAddenda99() + entry.Category = CategoryReturn + advHeader := mockBatchADVHeader() + batch := NewBatchADV(advHeader) + batch.SetHeader(advHeader) + batch.AddADVEntry(entry) + if err := batch.Create(); err != nil { + t.Fatal(err) + } + file.AddBatch(batch) + + if err := file.Create(); err != nil { + t.Errorf("%T: %s", err, err) + } + if err := file.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } + + b := &bytes.Buffer{} + f := NewWriter(b) + + if err := f.Write(file); err != nil { + t.Errorf("%T: %s", err, err) + } + + r := NewReader(strings.NewReader(b.String())) + _, err := r.Read() + if err != nil { + t.Errorf("%T: %s", err, err) + } + if err = r.File.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// testWriteWithCustomLineEnding writes an ACH file with a custom line ending +func testWriteWithCustomLineEnding(t testing.TB) { file := NewFile().SetHeader(mockFileHeader()) entry := mockEntryDetail() - entry.AddAddenda(mockAddenda()) - batch := NewBatchPPD() + entry.AddendaRecordIndicator = 1 + entry.AddAddenda05(mockAddenda05()) + batch := NewBatchPPD(mockBatchPPDHeader()) batch.SetHeader(mockBatchHeader()) batch.AddEntry(entry) - batch.Create() + if err := batch.Create(); err != nil { + t.Fatal(err) + } file.AddBatch(batch) if err := file.Create(); err != nil { @@ -25,10 +708,13 @@ func TestPPDWrite(t *testing.T) { b := &bytes.Buffer{} f := NewWriter(b) + // use crlf as line ending + f.LineEnding = "\r\n" - if err := f.WriteAll([]*File{file}); err != nil { + if err := f.Write(file); err != nil { t.Errorf("%T: %s", err, err) } + r := NewReader(strings.NewReader(b.String())) _, err := r.Read() if err != nil { @@ -38,3 +724,20 @@ func TestPPDWrite(t *testing.T) { t.Errorf("%T: %s", err, err) } } + +// TestWriteWithCustomLineEnding tests writing an ACH file with a custom line ending +func TestWriteWithCustomLineEnding(t *testing.T) { + testWriteWithCustomLineEnding(t) +} + +func TestWriteBypassValidation(t *testing.T) { + file := mockFilePPD() + file.Header.FileCreationDate = "abc" // make the file fail Nacha validation + + writer := NewWriter(&bytes.Buffer{}) + writer.BypassValidation = true + + if err := writer.Write(file); err != nil { + t.Fatal(err) + } +}