ach_client 1.0.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +11 -6
- data/CHANGELOG.md +32 -0
- data/README.md +22 -0
- data/ach_client.gemspec +2 -3
- data/config/return_codes.yml +8 -0
- data/lib/ach_client/objects/return_code.rb +4 -1
- data/lib/ach_client/providers/soap/fake/ach_batch.rb +11 -0
- data/lib/ach_client/providers/soap/fake/ach_status_checker.rb +42 -0
- data/lib/ach_client/providers/soap/fake/ach_transaction.rb +11 -0
- data/lib/ach_client/providers/soap/fake/fake.rb +4 -0
- data/lib/ach_client/providers/soap/i_check_gateway/ach_transaction.rb +26 -1
- data/lib/ach_client/providers/soap/i_check_gateway/instant_rejection_error.rb +33 -0
- data/lib/ach_client/version.rb +1 -1
- metadata +16 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8e40c40c7ee42a6264c64f512772b254fac48b70cb7bb323ba238594c3d2d50c
|
4
|
+
data.tar.gz: a6d925e6f0a9682e085de0f1d12240c827cf769d521ee66fabe7001ade43fd64
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86303a731c6e1c4de5f4a82fa8a12f2f9f955e3ef70601c423d907282b50e2e5992a762f4736223bab87672b28a86b566fa112c55c0e9a0f7fd2c9c819dbc6c3
|
7
|
+
data.tar.gz: 71299fac7355cd6b0a48e0245e8aa19e01f809f4acdcc00f97afda028c0f18dc0bb8792fabd5b242c6e590317f83385cef7e434fac395d3ad3ac8371897a1cc4
|
data/.travis.yml
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
language: ruby
|
2
|
-
script:
|
3
|
-
- bundle exec rake test
|
4
|
-
- bundle exec codeclimate-test-reporter
|
5
2
|
before_install:
|
6
|
-
- export TZ=US/Eastern
|
7
|
-
- export CODECLIMATE_REPO_TOKEN=1fc324a28beed303e632765cca14487904a23023d85b9127a288f04cc007d28d
|
8
|
-
-
|
3
|
+
- export TZ=US/Eastern
|
4
|
+
- export CODECLIMATE_REPO_TOKEN=1fc324a28beed303e632765cca14487904a23023d85b9127a288f04cc007d28d
|
5
|
+
- export CC_TEST_REPORTER_ID=1fc324a28beed303e632765cca14487904a23023d85b9127a288f04cc007d28d
|
6
|
+
- date
|
7
|
+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
8
|
+
- chmod +x ./cc-test-reporter
|
9
|
+
- ./cc-test-reporter before-build
|
10
|
+
script:
|
11
|
+
- bundle exec rake test
|
12
|
+
after_script:
|
13
|
+
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
|
9
14
|
notifications:
|
10
15
|
email: false
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
### 2.1.0
|
2
|
+
|
3
|
+
* Add AchClient::Fake::AchStatusChecker to facilitate response poller testing
|
4
|
+
|
5
|
+
### 2.0.0
|
6
|
+
|
7
|
+
* Add new AchClient::ICheckGateway::InstantRejectionError to handle API errors raised by ICheckGateway in situations
|
8
|
+
where other providers would accept the transaction and issue a return when polling in the future. This is a breaking
|
9
|
+
change as previous versions would raise a RuntimeError with the message 'ICheckGateway ACH Transaction Failure' and
|
10
|
+
including the API response.
|
11
|
+
|
12
|
+
* Include "internal corrections" (return codes starting with 'XZ') in the AchClient::ReturnCode#correction? predicate
|
13
|
+
|
14
|
+
### 1.1.0
|
15
|
+
|
16
|
+
* Add AchClient::Fake provider to facilitate testing
|
17
|
+
|
18
|
+
### 1.0.3
|
19
|
+
|
20
|
+
* Add presumed description for X09 return code
|
21
|
+
|
22
|
+
### 1.0.2
|
23
|
+
|
24
|
+
* Add previously undocumented X01 internal return code
|
25
|
+
|
26
|
+
### 1.0.1
|
27
|
+
|
28
|
+
* Remove newline characters from fields before generating NACHA files
|
29
|
+
|
30
|
+
### 1.0.0
|
31
|
+
|
32
|
+
* Prior to 1.0.0, ach_client did not have a stable API.
|
data/README.md
CHANGED
@@ -115,6 +115,16 @@ returned by the `#send` method. So you should:
|
|
115
115
|
|
116
116
|
4) When you eventually poll the provider for the status of your transactions, you can use the `external_ach_id` you stored to reconcile the returned data against your records
|
117
117
|
|
118
|
+
### ICheckGateway instant rejection caveat
|
119
|
+
|
120
|
+
ICheckGateway sometimes returns an API error when a valid ACH transaction is sent in a handful of
|
121
|
+
rejection scenarios. This is unusual because most providers will accept the transaction, return an
|
122
|
+
`external_ach_id`, and then supply the rejection info when you poll for responses (see section on response polling
|
123
|
+
below) at a later date. This idiosyncrasy is handled by raising an exception `InstantRejectionError` which contains
|
124
|
+
information about the premature ACH return. In this case, no `external_ach_id` is returned by the `#send` method
|
125
|
+
because ICheck does not return one, nor do they maintain a record of the transaction in their system. See the
|
126
|
+
`InstantRejectionError` class for more details.
|
127
|
+
|
118
128
|
## Batched ACH transactions
|
119
129
|
|
120
130
|
A group of ACH transactions can also be sent in a single batched transaction to
|
@@ -145,6 +155,12 @@ SFTP+NACHA providers take an optional `batch_number` parameter which may be used
|
|
145
155
|
)
|
146
156
|
```
|
147
157
|
|
158
|
+
## Testing
|
159
|
+
|
160
|
+
A fake ACH provider (in the `AchClient::Fake` namespace) is included to facilitate testing on staging environments.
|
161
|
+
This provider behaves the same way as any other without actually sending any transactions. The transaction sending
|
162
|
+
methods return the given `external_ach_id`s with no side-effects.
|
163
|
+
|
148
164
|
## Response Polling - Checking Transaction Status
|
149
165
|
|
150
166
|
None of the providers support querying for transaction status by external_ach_id. Instead, we must query by date and
|
@@ -181,6 +197,12 @@ information has changed. Check the return code on the response object for
|
|
181
197
|
details on what happened. Check the corrections hash on the response object for
|
182
198
|
the new attributes
|
183
199
|
|
200
|
+
### Testing
|
201
|
+
|
202
|
+
The fake ACH provider namespace provides a status checker `AchClient::Fake::AchStatusChecker` that always returns the
|
203
|
+
same responses. The external_ach_id for each response will be one of
|
204
|
+
`['processing', 'settled', 'returned', 'corrected', 'late_returned']`
|
205
|
+
|
184
206
|
## Logging
|
185
207
|
|
186
208
|
For record keeping purposes, there is a log provider that allows you to hook
|
data/ach_client.gemspec
CHANGED
@@ -43,13 +43,12 @@ Gem::Specification.new do |spec|
|
|
43
43
|
# Asynchronocity w/out extra infrastucture dependency (database/redis)
|
44
44
|
spec.add_dependency 'sucker_punch', '~> 2'
|
45
45
|
|
46
|
-
spec.add_development_dependency 'bundler', '~> 1.12'
|
47
46
|
spec.add_development_dependency 'codeclimate-test-reporter'
|
48
47
|
spec.add_development_dependency 'minitest-reporters'
|
49
48
|
spec.add_development_dependency 'minitest', '~> 5'
|
50
|
-
spec.add_development_dependency 'mocha'
|
49
|
+
spec.add_development_dependency 'mocha', '~> 1'
|
51
50
|
spec.add_development_dependency 'pry'
|
52
|
-
spec.add_development_dependency 'rake', '
|
51
|
+
spec.add_development_dependency 'rake', '>= 12.3.3'
|
53
52
|
spec.add_development_dependency 'simplecov'
|
54
53
|
spec.add_development_dependency 'timecop'
|
55
54
|
spec.add_development_dependency 'vcr'
|
data/config/return_codes.yml
CHANGED
@@ -343,6 +343,10 @@
|
|
343
343
|
code: 'X00'
|
344
344
|
description: "Ok for submission"
|
345
345
|
reason: "Ok for submission"
|
346
|
+
-
|
347
|
+
code: 'X01'
|
348
|
+
description: "The available and/or cash reserve balance is not sufficient to cover the dollar value of the debit Entry."
|
349
|
+
reason: "Insufficient Funds"
|
346
350
|
-
|
347
351
|
code: 'X02'
|
348
352
|
description: "A previously active account has been closed by action of the customer or the RDFI."
|
@@ -367,6 +371,10 @@
|
|
367
371
|
code: 'X08'
|
368
372
|
description: "The Receiver has placed a stop payment order on this debit Entry."
|
369
373
|
reason: "Payment Stopped"
|
374
|
+
-
|
375
|
+
code: 'X09'
|
376
|
+
description: "A sufficient ledger balance exists to satisfy the dollar value of the transaction, but the available balance is below the dollar value of the debit Entry."
|
377
|
+
reason: "Uncollected funds due to uncleared deposit"
|
370
378
|
-
|
371
379
|
code: 'X10'
|
372
380
|
description: "The RDFI has been notified by the Receiver that the Entry is unauthorized, improper, or ineligible."
|
@@ -9,6 +9,9 @@ module AchClient
|
|
9
9
|
# The first character in an internal return code
|
10
10
|
INTERNAL_START_CHARACTER = 'X'
|
11
11
|
|
12
|
+
# Returns that are both internal and corrections start with this string
|
13
|
+
INTERNAL_CORRECTION_STRING = 'XZ'
|
14
|
+
|
12
15
|
attr_accessor :code,
|
13
16
|
:description,
|
14
17
|
:reason
|
@@ -25,7 +28,7 @@ module AchClient
|
|
25
28
|
|
26
29
|
# @return Whether or not this return is a correction/notice of change
|
27
30
|
def correction?
|
28
|
-
@code.start_with?(CORRECTION_START_CHARACTER)
|
31
|
+
@code.start_with?(CORRECTION_START_CHARACTER) || @code.start_with?(INTERNAL_CORRECTION_STRING)
|
29
32
|
end
|
30
33
|
|
31
34
|
# @return Whether or not the return is internal
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module AchClient
|
2
|
+
class Fake
|
3
|
+
# Fake ACH polling that always returns the same set of results.
|
4
|
+
class AchStatusChecker < Abstract::AchStatusChecker
|
5
|
+
|
6
|
+
def self.most_recent
|
7
|
+
in_range(start_date: Date.today - 3.days, end_date: Date.today)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.in_range(start_date:, end_date:)
|
11
|
+
{
|
12
|
+
'processing' => [AchClient::ProcessingAchResponse.new(amount: 100.0, date: start_date)],
|
13
|
+
'settled' => [AchClient::SettledAchResponse.new(amount: 100.0, date: start_date)],
|
14
|
+
'returned' => [
|
15
|
+
AchClient::ReturnedAchResponse.new(
|
16
|
+
amount: 100.0,
|
17
|
+
date: start_date,
|
18
|
+
return_code: AchClient::ReturnCodes.find_by(code: 'R01')
|
19
|
+
)
|
20
|
+
],
|
21
|
+
'corrected' => [
|
22
|
+
AchClient::CorrectedAchResponse.new(
|
23
|
+
amount: 100.0,
|
24
|
+
date: start_date,
|
25
|
+
return_code: AchClient::ReturnCodes.find_by(code: 'XZ2'),
|
26
|
+
corrections: '123456789'
|
27
|
+
)
|
28
|
+
],
|
29
|
+
'late_returned' => [
|
30
|
+
AchClient::SettledAchResponse.new(amount: 100.0, date: start_date),
|
31
|
+
AchClient::ReturnedAchResponse.new(
|
32
|
+
amount: 100.0,
|
33
|
+
date: end_date,
|
34
|
+
return_code: AchClient::ReturnCodes.find_by(code: 'R08')
|
35
|
+
)
|
36
|
+
]
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -3,11 +3,23 @@ module AchClient
|
|
3
3
|
# ICheckGateway implementation for AchTransaction
|
4
4
|
class AchTransaction < Abstract::AchTransaction
|
5
5
|
|
6
|
+
# When ICheck API gives us an error response containing a correction, the response field looks like this:
|
7
|
+
# DECLINED - Notice of Change (XXX - Change Data: YYYYYY)
|
8
|
+
# Where X is a three character string representing the return code (example: C01)
|
9
|
+
# Where Y is any number of digits representing the updated information for the correction - the
|
10
|
+
# ACH attribute that should be updated (example: 123456789)
|
11
|
+
# The (\w{3}) capture group matches the return code, while the (\d+) capture group matches the correction data
|
12
|
+
# for later use.
|
13
|
+
NOC_RESPONSE_MATCHER = /DECLINED - Notice of Change \((\w{3}) - Change Data: (\d+)\)/
|
14
|
+
|
6
15
|
# Sends this transaction to ICheckGateway
|
7
16
|
# If successful, returns a string from the response that seems to be
|
8
17
|
# a unique identifier for the transaction from ICheckGateway
|
9
18
|
# Raises an exception with as much info as possible if something goes
|
10
|
-
# wrong
|
19
|
+
# wrong.
|
20
|
+
# ICheck sometimes returns an API error for certain rejection scenarios. In this case we raise a
|
21
|
+
# InstantRejectionError which can be caught to handle any business logic appropriate for this edge case.
|
22
|
+
# The exception contains a method ach_response that returns the information about the return.
|
11
23
|
# @return [String] a string returned by ICheckGateway - external_ach_id
|
12
24
|
def do_send
|
13
25
|
# The response comes back as a | separated list of field values with
|
@@ -21,6 +33,19 @@ module AchClient
|
|
21
33
|
if response[0] == 'APPROVED'
|
22
34
|
# Return the confirmation number
|
23
35
|
response[7]
|
36
|
+
elsif response[0].include?('DECLINED - Notice of Change')
|
37
|
+
return_code, addendum = NOC_RESPONSE_MATCHER.match(response[0]).captures
|
38
|
+
# The API error message incorrectly uses the normal correction return codes when the internal correction
|
39
|
+
# return codes should be used instead (since the transaction is never forwarded through to the NACHA system)
|
40
|
+
# Correcting this involves replacing `C0` with `XZ` (ie C01 becomes XZ1)
|
41
|
+
corrected_return_code = "XZ#{return_code.last}"
|
42
|
+
raise ICheckGateway::InstantRejectionError.new(
|
43
|
+
nacha_return_code: corrected_return_code,
|
44
|
+
addendum: addendum,
|
45
|
+
transaction: self
|
46
|
+
), response[0]
|
47
|
+
elsif response[0].include?('DECLINED - Invalid Routing Number')
|
48
|
+
raise ICheckGateway::InstantRejectionError.new(nacha_return_code: 'X13', transaction: self), response[0]
|
24
49
|
else
|
25
50
|
# Don't have a reliable way of getting the error message, so we will
|
26
51
|
# just raise the whole response.
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module AchClient
|
2
|
+
class ICheckGateway
|
3
|
+
# ICheckGateway sometimes returns an API error when a valid ACH transaction is sent in a handful of
|
4
|
+
# rejection scenarios. This is unusual because most providers will accept the transaction, return an
|
5
|
+
# external_ach_id, and then supply the rejection info when you poll for responses at a later date.
|
6
|
+
# So far we have observed this happening in the following scenarios:
|
7
|
+
# - When an invalid routing number is supplied (X13 - Invalid ACH Routing Number - Entry contains a Receiving DFI
|
8
|
+
# Identification or Gateway Identification that is not a valid ACH routing number.)
|
9
|
+
# - When there is a Notice of Change for the account number (C01 - ACH Change Code. Incorrect Account Number)
|
10
|
+
# - When there is a Notice of Change for the routing number (C02 - ACH Change Code. Incorrect Transit Route)
|
11
|
+
# This exception can be caught to handle the API error in the appropriate manner.
|
12
|
+
# The NACHA return code inferred from the error message is retrievable from the exception instance as well as any
|
13
|
+
# addendum information provided by the API error (ie the correct new account/routing number)
|
14
|
+
class InstantRejectionError < RuntimeError
|
15
|
+
attr_reader :ach_response
|
16
|
+
|
17
|
+
def initialize(message = nil, nacha_return_code:, addendum: nil, transaction:)
|
18
|
+
super(message)
|
19
|
+
return_code = ReturnCodes.find_by(code: nacha_return_code)
|
20
|
+
response_args = {
|
21
|
+
amount: transaction.amount,
|
22
|
+
date: transaction.effective_entry_date,
|
23
|
+
return_code: return_code,
|
24
|
+
}
|
25
|
+
@ach_response = if return_code.correction?
|
26
|
+
CorrectedAchResponse.new(**response_args, corrections: addendum)
|
27
|
+
else
|
28
|
+
ReturnedAchResponse.new(**response_args)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/ach_client/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ach_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zach Cotter
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-06-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ach
|
@@ -80,20 +80,6 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '2'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: bundler
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - "~>"
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '1.12'
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - "~>"
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '1.12'
|
97
83
|
- !ruby/object:Gem::Dependency
|
98
84
|
name: codeclimate-test-reporter
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -140,16 +126,16 @@ dependencies:
|
|
140
126
|
name: mocha
|
141
127
|
requirement: !ruby/object:Gem::Requirement
|
142
128
|
requirements:
|
143
|
-
- - "
|
129
|
+
- - "~>"
|
144
130
|
- !ruby/object:Gem::Version
|
145
|
-
version: '
|
131
|
+
version: '1'
|
146
132
|
type: :development
|
147
133
|
prerelease: false
|
148
134
|
version_requirements: !ruby/object:Gem::Requirement
|
149
135
|
requirements:
|
150
|
-
- - "
|
136
|
+
- - "~>"
|
151
137
|
- !ruby/object:Gem::Version
|
152
|
-
version: '
|
138
|
+
version: '1'
|
153
139
|
- !ruby/object:Gem::Dependency
|
154
140
|
name: pry
|
155
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -168,16 +154,16 @@ dependencies:
|
|
168
154
|
name: rake
|
169
155
|
requirement: !ruby/object:Gem::Requirement
|
170
156
|
requirements:
|
171
|
-
- - "
|
157
|
+
- - ">="
|
172
158
|
- !ruby/object:Gem::Version
|
173
|
-
version:
|
159
|
+
version: 12.3.3
|
174
160
|
type: :development
|
175
161
|
prerelease: false
|
176
162
|
version_requirements: !ruby/object:Gem::Requirement
|
177
163
|
requirements:
|
178
|
-
- - "
|
164
|
+
- - ">="
|
179
165
|
- !ruby/object:Gem::Version
|
180
|
-
version:
|
166
|
+
version: 12.3.3
|
181
167
|
- !ruby/object:Gem::Dependency
|
182
168
|
name: simplecov
|
183
169
|
requirement: !ruby/object:Gem::Requirement
|
@@ -276,6 +262,7 @@ files:
|
|
276
262
|
- ".tool-versions"
|
277
263
|
- ".tool-versions-e"
|
278
264
|
- ".travis.yml"
|
265
|
+
- CHANGELOG.md
|
279
266
|
- Gemfile
|
280
267
|
- README.md
|
281
268
|
- Rakefile
|
@@ -326,12 +313,17 @@ files:
|
|
326
313
|
- lib/ach_client/providers/soap/ach_works/date_formatter.rb
|
327
314
|
- lib/ach_client/providers/soap/ach_works/response_record_processor.rb
|
328
315
|
- lib/ach_client/providers/soap/ach_works/transaction_type_transformer.rb
|
316
|
+
- lib/ach_client/providers/soap/fake/ach_batch.rb
|
317
|
+
- lib/ach_client/providers/soap/fake/ach_status_checker.rb
|
318
|
+
- lib/ach_client/providers/soap/fake/ach_transaction.rb
|
319
|
+
- lib/ach_client/providers/soap/fake/fake.rb
|
329
320
|
- lib/ach_client/providers/soap/i_check_gateway/account_type_transformer.rb
|
330
321
|
- lib/ach_client/providers/soap/i_check_gateway/ach_batch.rb
|
331
322
|
- lib/ach_client/providers/soap/i_check_gateway/ach_status_checker.rb
|
332
323
|
- lib/ach_client/providers/soap/i_check_gateway/ach_transaction.rb
|
333
324
|
- lib/ach_client/providers/soap/i_check_gateway/company_info.rb
|
334
325
|
- lib/ach_client/providers/soap/i_check_gateway/i_check_gateway.rb
|
326
|
+
- lib/ach_client/providers/soap/i_check_gateway/instant_rejection_error.rb
|
335
327
|
- lib/ach_client/providers/soap/i_check_gateway/response_record_processor.rb
|
336
328
|
- lib/ach_client/providers/soap/i_check_gateway/transaction_type_transformer.rb
|
337
329
|
- lib/ach_client/providers/soap/soap_provider.rb
|