ach_client 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fadb80869dd7fcd712cce4344a08ddfa1da32e70b7cee237a39c9abcb96ac03a
4
- data.tar.gz: 440ea73e8b1cbaafad65ecc94fa4710a9f8df1daa53a5ad656b7f5c2a4cced72
3
+ metadata.gz: bdcfddb63a2f873af45df29f8fcf6765ec99aff800eb04e8ec2b7500f0827e3c
4
+ data.tar.gz: 3bfa32675ec76f4e31b830b3616cc913b13f865c2c789394d49cc07758e93b89
5
5
  SHA512:
6
- metadata.gz: 19e7c19755a287febc77466ccfda9abe3cc567897462ed3b32d9831e00e754283d1b168d069d4b08223e0e0e6ee56b5b7635fb712d23aa8d2780670b93ff792d
7
- data.tar.gz: 0b5139c86630be28a2432fcf709772cb4bccdf725a724a6718b11b5057e69bcff71e2e0594075126781480e4fe9b494d3537aea816270dbec3afb1e97e77b9b6
6
+ metadata.gz: 25670e16c050eb8248d80f22ffeea4a649cef6f624828118e32c866b7b7e0b0e19f0735150b7b636e5cb88dafc7d57fe7a2ac3225b8c507100b42a3a9fba668e
7
+ data.tar.gz: ed197b466da4182a732f463b7c51f4a0bd24fdfbfa58755bd058d2f830790719e5f6d6c600abf480e045430e186c962f331ee0a7e17534546d93f7ed44cc8819
@@ -0,0 +1,28 @@
1
+ ### 2.0.0
2
+
3
+ * Add new AchClient::ICheckGateway::InstantRejectionError to handle API errors raised by ICheckGateway in situations
4
+ where other providers would accept the transaction and issue a return when polling in the future. This is a breaking
5
+ change as previous versions would raise a RuntimeError with the message 'ICheckGateway ACH Transaction Failure' and
6
+ including the API response.
7
+
8
+ * Include "internal corrections" (return codes starting with 'XZ') in the AchClient::ReturnCode#correction? predicate
9
+
10
+ ### 1.1.0
11
+
12
+ * Add AchClient::Fake provider to facilitate testing
13
+
14
+ ### 1.0.3
15
+
16
+ * Add presumed description for X09 return code
17
+
18
+ ### 1.0.2
19
+
20
+ * Add previously undocumented X01 internal return code
21
+
22
+ ### 1.0.1
23
+
24
+ * Remove newline characters from fields before generating NACHA files
25
+
26
+ ### 1.0.0
27
+
28
+ * 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
@@ -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
@@ -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
@@ -1,4 +1,4 @@
1
1
  module AchClient
2
2
  # Increment this when changes are published
3
- VERSION = '1.1.0'
3
+ VERSION = '2.0.0'
4
4
  end
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.1.0
4
+ version: 2.0.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: 2020-03-03 00:00:00.000000000 Z
11
+ date: 2020-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ach
@@ -262,6 +262,7 @@ files:
262
262
  - ".tool-versions"
263
263
  - ".tool-versions-e"
264
264
  - ".travis.yml"
265
+ - CHANGELOG.md
265
266
  - Gemfile
266
267
  - README.md
267
268
  - Rakefile
@@ -321,6 +322,7 @@ files:
321
322
  - lib/ach_client/providers/soap/i_check_gateway/ach_transaction.rb
322
323
  - lib/ach_client/providers/soap/i_check_gateway/company_info.rb
323
324
  - lib/ach_client/providers/soap/i_check_gateway/i_check_gateway.rb
325
+ - lib/ach_client/providers/soap/i_check_gateway/instant_rejection_error.rb
324
326
  - lib/ach_client/providers/soap/i_check_gateway/response_record_processor.rb
325
327
  - lib/ach_client/providers/soap/i_check_gateway/transaction_type_transformer.rb
326
328
  - lib/ach_client/providers/soap/soap_provider.rb