├── .ruby-version ├── .tool-versions ├── .tool-versions-e ├── .github └── CODEOWNERS ├── lib ├── ach_client │ ├── providers │ │ ├── soap │ │ │ ├── fake │ │ │ │ ├── fake.rb │ │ │ │ ├── ach_transaction.rb │ │ │ │ ├── ach_batch.rb │ │ │ │ └── ach_status_checker.rb │ │ │ ├── i_check_gateway │ │ │ │ ├── transaction_type_transformer.rb │ │ │ │ ├── ach_batch.rb │ │ │ │ ├── account_type_transformer.rb │ │ │ │ ├── company_info.rb │ │ │ │ ├── i_check_gateway.rb │ │ │ │ ├── instant_rejection_error.rb │ │ │ │ ├── response_record_processor.rb │ │ │ │ ├── ach_transaction.rb │ │ │ │ └── ach_status_checker.rb │ │ │ ├── ach_works │ │ │ │ ├── transaction_type_transformer.rb │ │ │ │ ├── account_type_transformer.rb │ │ │ │ ├── date_formatter.rb │ │ │ │ ├── ach_works.rb │ │ │ │ ├── ach_batch.rb │ │ │ │ ├── company_info.rb │ │ │ │ ├── ach_transaction.rb │ │ │ │ ├── response_record_processor.rb │ │ │ │ ├── ach_status_checker.rb │ │ │ │ └── correction_details_processor.rb │ │ │ └── soap_provider.rb │ │ ├── abstract │ │ │ ├── response_record_processor.rb │ │ │ ├── ach_status_checker.rb │ │ │ ├── company_info.rb │ │ │ ├── ach_batch.rb │ │ │ ├── transformer.rb │ │ │ └── ach_transaction.rb │ │ └── sftp │ │ │ ├── account_type_transformer.rb │ │ │ ├── transaction_type_transformer.rb │ │ │ ├── nacha_provider.rb │ │ │ ├── ach_transaction.rb │ │ │ ├── ach_batch.rb │ │ │ └── sftp_provider.rb │ ├── version.rb │ ├── abstract │ │ ├── abstract_method_error.rb │ │ └── invalid_ach_transaction_error.rb │ ├── objects │ │ ├── responses │ │ │ ├── settled_ach_response.rb │ │ │ ├── processing_ach_response.rb │ │ │ ├── ach_response.rb │ │ │ ├── returned_ach_response.rb │ │ │ └── corrected_ach_response.rb │ │ ├── transaction_types.rb │ │ ├── account_types.rb │ │ ├── return_codes.rb │ │ └── return_code.rb │ ├── logging │ │ ├── log_providers │ │ │ ├── null_log_provider.rb │ │ │ ├── stdout_log_provider.rb │ │ │ └── log_provider.rb │ │ ├── log_provider_job.rb │ │ ├── savon_observer.rb │ │ └── logging.rb │ └── helpers │ │ ├── dollars_to_cents.rb │ │ └── utils.rb └── ach_client.rb ├── Gemfile ├── .gitignore ├── bin ├── setup └── console ├── test ├── lib │ └── ach_client │ │ ├── abstract │ │ ├── sftp │ │ │ └── ach_batch_test.rb │ │ ├── transformer_test.rb │ │ ├── response_record_processor_test.rb │ │ ├── company_info_test.rb │ │ ├── ach_status_checker_test.rb │ │ ├── ach_transaction_test.rb │ │ └── ach_batch_test.rb │ │ ├── logging │ │ ├── xml_logger_test.rb │ │ ├── stdout_xml_logger_test.rb │ │ └── logging_test.rb │ │ ├── i_check_gateway │ │ ├── ach_batch_test.rb │ │ ├── account_type_transformer_test.rb │ │ └── ach_transaction_test.rb │ │ ├── ach_works │ │ ├── response_record_processor_test.rb │ │ ├── collection_details_processor_test.rb │ │ ├── date_formatter_test.rb │ │ ├── input_company_info_test.rb │ │ ├── transaction_type_transformer_test.rb │ │ ├── account_type_transformer_test.rb │ │ └── ach_transaction_test.rb │ │ ├── fake │ │ ├── ach_transaction_test.rb │ │ ├── ach_batch_test.rb │ │ └── ach_status_checker_test.rb │ │ ├── objects │ │ ├── return_codes_test.rb │ │ └── return_code_test.rb │ │ ├── helpers │ │ ├── utils_test.rb │ │ └── dollars_to_cents_test.rb │ │ ├── ach_client_test.rb │ │ └── silicon_valley_bank │ │ ├── sftp_provider_test.rb │ │ ├── transaction_type_transformer_test.rb │ │ ├── account_type_transformer_test.rb │ │ ├── ach_transaction_test.rb │ │ ├── ach_batch_test.rb │ │ └── ach_status_checker_test.rb ├── vcrs │ ├── connection_valid.yml │ ├── logger.yml │ ├── icg_late_returns_b.yml │ ├── icg_late_returns_a.yml │ ├── icg_late_returns_empty.yml │ ├── company_valid.yml │ ├── icg_late_returns_error.yml │ ├── icg_in_range_range_limit.yml │ ├── icg_in_range_rate_limit.yml │ ├── icg_in_range_unexpected.yml │ ├── most_recent_empty.yml │ ├── in_range_empty.yml │ ├── icheckgateway_ach_transaction_failure.yml │ ├── icheckgateway_ach_transaction_x13.yml │ ├── icheckgateway_ach_transaction_success.yml │ ├── icheckgateway_ach_transaction_correction.yml │ ├── icheckgateway_ach_transaction_fault.yml │ ├── ach_works_ach_transaction_success.yml │ ├── icg_in_range_success.yml │ ├── most_recent_invalid.yml │ ├── send_dot_net_error.yml │ └── send_no_company_batch.yml └── fake_sftp_connection.rb ├── Rakefile ├── .codeclimate.yml ├── CHANGELOG.md ├── LICENSE └── ach_client.gemspec /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.1.3 2 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | ruby 3.1.4 2 | -------------------------------------------------------------------------------- /.tool-versions-e: -------------------------------------------------------------------------------- 1 | ruby 2.5.0 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @ForwardFinancing/funding-app-smes 2 | -------------------------------------------------------------------------------- /lib/ach_client/providers/soap/fake/fake.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class Fake 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in ach_client.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /lib/ach_client/version.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | # Increment this when changes are published 3 | VERSION = '5.3.4' 4 | end 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.gem 11 | -------------------------------------------------------------------------------- /lib/ach_client/abstract/abstract_method_error.rb: -------------------------------------------------------------------------------- 1 | # To be raised when someone tries to call an abstract method 2 | class AbstractMethodError < RuntimeError 3 | end 4 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /lib/ach_client/objects/responses/settled_ach_response.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | # The transaction is settled. Huzzah! 3 | class SettledAchResponse < AchResponse 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/ach_client/abstract/invalid_ach_transaction_error.rb: -------------------------------------------------------------------------------- 1 | # To be raised when an ACH transaction can not be sent because it is invalid 2 | class InvalidAchTransactionError < RuntimeError 3 | end -------------------------------------------------------------------------------- /test/lib/ach_client/abstract/sftp/ach_batch_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Abstract 4 | class Sftp 5 | class AchBatchTest < Minitest::Test 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/ach_client/objects/responses/processing_ach_response.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | # The transaction is processing. 3 | # Not much else to see here. 4 | class ProcessingAchResponse < AchResponse 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rake/testtask' 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << 'test' 6 | t.libs << 'lib' 7 | t.test_files = FileList['test/**/*_test.rb'] 8 | end 9 | 10 | task default: :test 11 | -------------------------------------------------------------------------------- /test/lib/ach_client/abstract/transformer_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | class Abstract 3 | class TransformerTest < Minitest::Test 4 | def test_abstractlyness 5 | assert_raises(AbstractMethodError) do 6 | AchClient::Transformer.transformer 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | duplication: 4 | enabled: true 5 | config: 6 | languages: 7 | - ruby 8 | fixme: 9 | enabled: true 10 | rubocop: 11 | enabled: true 12 | ratings: 13 | paths: 14 | - Gemfile.lock 15 | - "**.rb" 16 | exclude_paths: 17 | - bin/ 18 | - test/ 19 | -------------------------------------------------------------------------------- /lib/ach_client/providers/soap/fake/ach_transaction.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class Fake 3 | class AchTransaction < Abstract::AchTransaction 4 | 5 | # Fake ACH sending just returns the provided external_ach_id to indicate success 6 | def do_send 7 | external_ach_id 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/lib/ach_client/logging/xml_logger_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class LogProviderTest < Minitest::Test 4 | def test_abstractness 5 | assert_raises(AbstractMethodError) do 6 | AchClient::Logging::LogProvider.send_logs( 7 | body: 'foo', 8 | name: 'bar' 9 | ) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/lib/ach_client/i_check_gateway/ach_batch_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ICheckGateway 4 | class AchBatchTest < Minitest::Test 5 | def test_not_supported 6 | assert_raises(RuntimeError) do 7 | AchClient::ICheckGateway::AchBatch.new(ach_transactions: []).send_batch 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/ach_client/providers/soap/fake/ach_batch.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class Fake 3 | class AchBatch < Abstract::AchBatch 4 | 5 | # Fake batch ACH sending just returns the provided external_ach_ids to indicate success 6 | def do_send_batch 7 | @ach_transactions.map(&:external_ach_id) 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/ach_client/providers/soap/i_check_gateway/transaction_type_transformer.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class ICheckGateway 3 | # Right now the ICheckGateway transaction type codes are the same as the 4 | # AchWorks transaction type codes 5 | class TransactionTypeTransformer < AchClient::AchWorks::TransactionTypeTransformer 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/lib/ach_client/ach_works/response_record_processor_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AchWorks 4 | class ResponseRecordProcessorTest < Minitest::Test 5 | def test_that_it_handles_method_missing 6 | assert_raises(NoMethodError) do 7 | AchClient::AchWorks::ResponseRecordProcessor.foo 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/ach_client/logging/log_providers/null_log_provider.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class Logging 3 | 4 | # A log provider that does nothing. 5 | class NullLogProvider < LogProvider 6 | 7 | # Does absolutely nothing with the log info 8 | def self.send_logs(*) 9 | # Do nothing 10 | nil 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/lib/ach_client/ach_works/collection_details_processor_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AchWorks 4 | class CorrectionDetailsProcessorTest < Minitest::Test 5 | def test_that_it_handles_method_missing 6 | assert_raises(NoMethodError) do 7 | AchClient::AchWorks::CorrectionDetailsProcessor.foo 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/ach_client/providers/soap/i_check_gateway/ach_batch.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class ICheckGateway 3 | # Implementation of AchBatch for ICheckGateway 4 | class AchBatch < Abstract::AchBatch 5 | 6 | # ICheckGateway does not support ACH batching 7 | def do_send_batch 8 | raise 'ICheckGateway does not support ACH batching' 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/lib/ach_client/abstract/response_record_processor_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Abstract 4 | class ResponseRecordProcessorTest < Minitest::Test 5 | def test_abstractlyness 6 | assert_raises(AbstractMethodError) do 7 | AchClient::Abstract::ResponseRecordProcessor.process_response_record( 8 | 'foo' 9 | ) 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/lib/ach_client/abstract/company_info_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | class Abstract 3 | class CompanyInfoTest < Minitest::Test 4 | def test_abstractlyness 5 | assert_raises(AbstractMethodError) do 6 | AchClient::Abstract::CompanyInfo.build 7 | end 8 | 9 | assert_raises(AbstractMethodError) do 10 | AchClient::Abstract::CompanyInfo.new.to_hash 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/ach_client/helpers/dollars_to_cents.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | # Utility classes shared by all providers 3 | class Helpers 4 | 5 | # Turns dollars into cents 6 | class DollarsToCents 7 | 8 | # @param dollars [Float | BigDecimal | Fixnum | Integer] 9 | # @return [Fixnum] cents 10 | def self.dollars_to_cents(dollars) 11 | (dollars.to_f.round(2) * 100).to_i 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/lib/ach_client/fake/ach_transaction_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Fake 4 | class AchTransactionTest < Minitest::Test 5 | def test_transaction 6 | assert_equal( 7 | AchClient::Fake::AchTransaction.new( 8 | external_ach_id: 'test_external_ach_id', 9 | effective_entry_date: Date.tomorrow 10 | ).send, 11 | 'test_external_ach_id' 12 | ) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/ach_client/logging/log_providers/stdout_log_provider.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class Logging 3 | # Logs to stdout (using puts) 4 | class StdoutLogProvider < LogProvider 5 | # Prints the name and then the body 6 | # @param body [String] the log body 7 | # @param name [String] the log name 8 | def self.send_logs(body:, name:) 9 | puts name 10 | puts body 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/lib/ach_client/objects/return_codes_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ReturnCodesTest < Minitest::Test 4 | def test_unauthorized 5 | unauthorized_codes = AchClient::ReturnCodes.unauthorized 6 | assert(unauthorized_codes.all?(&:unauthorized_return?)) 7 | end 8 | 9 | def test_administrative 10 | administrative_codes = AchClient::ReturnCodes.administrative 11 | assert(administrative_codes.all?(&:administrative_return?)) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/lib/ach_client/helpers/utils_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UtilsTest < Minitest::Test 4 | def test_icheck_date_format 5 | assert_equal( 6 | "wonky/date/format", 7 | AchClient::Helpers::Utils.icheck_date_format('wonky/date/format') 8 | ) 9 | assert_raises do 10 | AchClient::Helpers::Utils.icheck_date_format(1) 11 | end 12 | refute( 13 | AchClient::Helpers::Utils.icheck_date_format(nil) 14 | ) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/ach_client/objects/responses/ach_response.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | # Abstract Response wrapper for the various ACH response statuses 3 | class AchResponse 4 | 5 | 6 | attr_reader :amount, # Amount of the processed ACH 7 | :date # Date the Ach was in this status 8 | 9 | def initialize(amount:, date:) 10 | raise AbstractMethodError if self.class == AchClient::AchResponse 11 | @amount = amount 12 | @date = date 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/lib/ach_client/objects/return_code_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Objects 4 | class ReturnCodeTest < Minitest::Test 5 | def test_correction? 6 | assert(AchClient::ReturnCodes.find_by(code: 'C01').correction?) 7 | refute(AchClient::ReturnCodes.find_by(code: 'R01').correction?) 8 | end 9 | 10 | def test_internal? 11 | assert(AchClient::ReturnCodes.find_by(code: 'X02').internal?) 12 | refute(AchClient::ReturnCodes.find_by(code: 'R01').internal?) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/lib/ach_client/abstract/ach_status_checker_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Abstract 4 | class AchStatusCheckerTest < Minitest::Test 5 | def test_abstractlyness 6 | assert_raises(AbstractMethodError) do 7 | AchClient::Abstract::AchStatusChecker.most_recent 8 | end 9 | assert_raises(AbstractMethodError) do 10 | AchClient::Abstract::AchStatusChecker.in_range( 11 | start_date: 'foo', 12 | end_date: 'foo' 13 | ) 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/ach_client/objects/transaction_types.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | ## Representations of Ach transaction types (debit or credit) 3 | module TransactionTypes 4 | ## Base class for all transaction types 5 | class TransactionType 6 | end 7 | 8 | ## Representation of a credit transaction type 9 | class Credit < AchClient::TransactionTypes::TransactionType 10 | end 11 | 12 | ## Representation of a debit transaction type 13 | class Debit < AchClient::TransactionTypes::TransactionType 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/lib/ach_client/fake/ach_batch_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Fake 4 | class AchBatchTest < Minitest::Test 5 | def test_batch 6 | assert_equal( 7 | AchClient::Fake::AchBatch.new( 8 | ach_transactions: [ 9 | AchClient::Fake::AchTransaction.new( 10 | external_ach_id: 'test_external_ach_id', 11 | effective_entry_date: Date.tomorrow 12 | ) 13 | ] 14 | ).send_batch, 15 | ['test_external_ach_id'] 16 | ) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/lib/ach_client/abstract/ach_transaction_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AchTransactionTest < Minitest::Test 4 | def test_abstractfullness 5 | assert_raises(AbstractMethodError) do 6 | AchClient::Abstract::AchTransaction.new( 7 | effective_entry_date: Date.tomorrow 8 | ).send 9 | end 10 | end 11 | 12 | def test_invalid_send 13 | assert_raises(InvalidAchTransactionError) do 14 | AchClient::Abstract::AchTransaction.new( 15 | effective_entry_date: Date.yesterday 16 | ).send 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/ach_client/objects/responses/returned_ach_response.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | # Representation of an Ach return that failed 3 | class ReturnedAchResponse < AchResponse 4 | 5 | attr_reader :return_code 6 | 7 | ## 8 | # @param date [DateTime] date of correction return 9 | # @param return_code [AchClient::ReturnCode] Ach Return code for the 10 | # correction (ie AchClient::ReturnCodes.find_by(code: 'C01')) 11 | def initialize(amount:, date:, return_code:) 12 | @return_code = return_code 13 | super(amount: amount, date: date) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/ach_client/providers/soap/ach_works/transaction_type_transformer.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class AchWorks 3 | ## 4 | # Transforms TransactionTypes between AchClient class and the string 5 | # that AchWorks expects 6 | class TransactionTypeTransformer < AchClient::Transformer 7 | 8 | # 'C' means Credit, 'D' means Debit 9 | # @return [Hash {String => Class}] the mapping 10 | def self.transformer 11 | { 12 | 'C' => AchClient::TransactionTypes::Credit, 13 | 'D' => AchClient::TransactionTypes::Debit 14 | } 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/lib/ach_client/abstract/ach_batch_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Abstract 4 | class AchBatchTest < Minitest::Test 5 | def test_abstractfulness 6 | assert_raises(AbstractMethodError) do 7 | AchClient::Abstract::AchBatch.new.send_batch 8 | end 9 | 10 | assert_raises(InvalidAchTransactionError) do 11 | AchClient::Abstract::AchBatch.new( 12 | ach_transactions: [ 13 | AchClient::Abstract::AchTransaction.new( 14 | effective_entry_date: Date.yesterday 15 | ) 16 | ] 17 | ).send_batch 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/lib/ach_client/ach_client_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AchClientTest < Minitest::Test 4 | def test_that_it_has_a_version_number 5 | refute_nil ::AchClient::VERSION 6 | end 7 | 8 | def test_it_does_something_useful 9 | assert true 10 | end 11 | 12 | def test_magic 13 | assert(AchClient::MagicBank.include?(AchClient::SftpProvider)) 14 | assert(AchClient::MagicBank.include?(AchClient::NachaProvider)) 15 | assert( 16 | AchClient::MagicBank::AchTransaction < AchClient::Sftp::AchTransaction 17 | ) 18 | assert( 19 | AchClient::MagicBank::AchBatch < AchClient::Sftp::AchBatch 20 | ) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/ach_client/providers/abstract/response_record_processor.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class Abstract 3 | ## Interface for turning a Provider's response representation of an ACH 4 | # status into an instance of AchClient::Response 5 | class ResponseRecordProcessor 6 | ## Interface for turning a Provider's response representation of an ACH 7 | # status into an instance of AchClient::Response 8 | # @param record [Object] the record from the provider 9 | # @return [AchClient::AchResponse] our representation of the response 10 | def self.process_response_record(_record) 11 | raise AbstractMethodError 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/ach_client/providers/sftp/account_type_transformer.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class Sftp 3 | ## 4 | # Transforms AccountTypes between AchClient class and the string 5 | # that NACHA expects 6 | class AccountTypeTransformer < AchClient::Transformer 7 | 8 | # '2' means Checking, '3' means Savings 9 | # The account type string is the first character in the transaction_code 10 | # field. 11 | # @return [Hash {String => Class}] the mapping 12 | def self.transformer 13 | { 14 | '3' => AchClient::AccountTypes::Savings, 15 | '2' => AchClient::AccountTypes::Checking 16 | } 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/ach_client/providers/sftp/transaction_type_transformer.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class Sftp 3 | ## 4 | # Transforms TransactionTypes between AchClient class and the string 5 | # that NACHA expects 6 | class TransactionTypeTransformer < AchClient::Transformer 7 | # '2' means Credit, '7' means Debit 8 | # The account type string is the second character in the transaction_code 9 | # field. 10 | # @return [Hash {String => Class}] the mapping 11 | def self.transformer 12 | { 13 | '2' => AchClient::TransactionTypes::Credit, 14 | '7' => AchClient::TransactionTypes::Debit 15 | } 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/ach_client/providers/soap/ach_works/account_type_transformer.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class AchWorks 3 | ## 4 | # Transforms AccountTypes between AchClient class and the string 5 | # that AchWorks expects 6 | class AccountTypeTransformer < AchClient::Transformer 7 | # 'C' means Checking, 'S' means Savings 8 | # @return [Hash {String => Class}] the mapping 9 | def self.transformer 10 | { 11 | 'S' => AchClient::AccountTypes::Savings, 12 | 'C' => AchClient::AccountTypes::Checking, 13 | '3' => AchClient::AccountTypes::Savings, 14 | '2' => AchClient::AccountTypes::Checking 15 | } 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/lib/ach_client/helpers/dollars_to_cents_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class DollarsToCentsTest < Minitest::Test 4 | def test_dollars_to_cents 5 | assert_equal( 6 | AchClient::Helpers::DollarsToCents.dollars_to_cents(1), 7 | 100 8 | ) 9 | assert_equal( 10 | AchClient::Helpers::DollarsToCents.dollars_to_cents(1.0), 11 | 100 12 | ) 13 | assert_equal( 14 | AchClient::Helpers::DollarsToCents.dollars_to_cents(1.01), 15 | 101 16 | ) 17 | assert_equal( 18 | AchClient::Helpers::DollarsToCents.dollars_to_cents(1.011), 19 | 101 20 | ) 21 | assert_equal( 22 | AchClient::Helpers::DollarsToCents.dollars_to_cents(1.016), 23 | 102 24 | ) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/lib/ach_client/ach_works/date_formatter_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AchWorks 4 | class DateFormatterTest < Minitest::Test 5 | def test_that_it_works 6 | assert_equal( 7 | AchClient::AchWorks::DateFormatter.format("2016-08-11"), 8 | '2016-08-11T00:00:00' 9 | ) 10 | assert_equal( 11 | AchClient::AchWorks::DateFormatter.format(Date.parse("2016-08-11")), 12 | '2016-08-11T00:00:00' 13 | ) 14 | assert_equal( 15 | AchClient::AchWorks::DateFormatter.format( 16 | DateTime.parse('2016-08-11T10:13:05-04:00') 17 | ), 18 | '2016-08-11T10:13:05' 19 | ) 20 | assert_raises(RuntimeError) {AchClient::AchWorks::DateFormatter.format(nil)} 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/ach_client/objects/responses/corrected_ach_response.rb: -------------------------------------------------------------------------------- 1 | require_relative './returned_ach_response.rb' 2 | 3 | module AchClient 4 | # Representation of an Ach return that contains a correction 5 | class CorrectedAchResponse < ReturnedAchResponse 6 | attr_reader :corrections 7 | 8 | ## 9 | # @param date [DateTime] date of correction return 10 | # @param return_code [AchClient::ReturnCode] Ach Return code for the 11 | # correction (ie AchClient::ReturnCodes.find_by(code: 'C01')) 12 | # @param corrections [Hash] A hash of corrected attributes and their values 13 | def initialize(amount:, date:, return_code:, corrections:) 14 | @corrections = corrections 15 | super(amount: amount, date: date, return_code: return_code) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/ach_client/logging/log_providers/log_provider.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class Logging 3 | # Base class for log providers 4 | # Extending classes must implement send_logs 5 | # The consumer may implement their own log provider and assign it to 6 | # AchClient: 7 | # ```ruby 8 | # class MyCustomLogger < AchClient::Logging::LogProvider 9 | # def self.send_logs(body:, name:) 10 | # # Do whatever you want, like send the log data to S3, or whatever 11 | # # logging service you choose 12 | # end 13 | # end 14 | # AchClient::Logging.log_provider = MyCustomLogger 15 | # ``` 16 | class LogProvider 17 | def self.send_logs(body:, name:) 18 | raise AbstractMethodError, "#{body}#{name}" 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/ach_client/providers/soap/i_check_gateway/account_type_transformer.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class ICheckGateway 3 | ## 4 | # Transforms AccountTypes between AchClient class and the string 5 | # that ICheckGateway expects 6 | class AccountTypeTransformer < AchClient::Transformer 7 | # 'B' means Business, 'P' means Personal 8 | # 'C' means Checking, 'S' means Savings 9 | # @return [Hash {String => Class}] the mapping 10 | def self.transformer 11 | { 12 | 'PS' => AchClient::AccountTypes::PersonalSavings, 13 | 'PC' => AchClient::AccountTypes::PersonalChecking, 14 | 'BS' => AchClient::AccountTypes::BusinessSavings, 15 | 'BC' => AchClient::AccountTypes::BusinessChecking 16 | } 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/ach_client/providers/abstract/ach_status_checker.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class Abstract 3 | 4 | # Interface for polling provider for ACH status responses 5 | class AchStatusChecker 6 | # Wrapper for a provider's "most recent bucket" 7 | # @return [Hash{String => [AchClient::AchResponse]}] Hash with provider's 8 | # external ACH id as the key, list of AchResponse objects as values 9 | def self.most_recent 10 | raise AbstractMethodError 11 | end 12 | 13 | # Wrapper for a providers range response endpoint 14 | # @return [Hash{String => [AchClient::AchResponse]}] Hash with provider's 15 | # external ACH id as the key, list of AchResponse objects as values 16 | def self.in_range(*) 17 | raise AbstractMethodError 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/ach_client/providers/abstract/company_info.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class Abstract 3 | # Interface for storing company credentials used with a provider 4 | class CompanyInfo 5 | 6 | ## 7 | # @return [CompanyInfo] instance built from configuration values 8 | def self.build 9 | raise AbstractMethodError 10 | end 11 | 12 | ## 13 | # Build a hash to send to provider 14 | # @return [Hash] hash to send to provider 15 | def to_hash 16 | raise AbstractMethodError 17 | end 18 | 19 | private_class_method def self.build_from_config(args) 20 | args_hash = args.map do |arg| 21 | {arg => self.to_s.deconstantize.constantize.send(arg)} 22 | end.reduce(&:merge) 23 | self.new(**args_hash) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/lib/ach_client/logging/stdout_xml_logger_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class StdoutLogProviderTest < Minitest::Test 4 | def test_that_it_works 5 | AchClient::Logging.stub( 6 | :log_provider, 7 | AchClient::Logging::StdoutLogProvider 8 | ) do 9 | VCR.use_cassette('logger') do 10 | output = capture_subprocess_io do 11 | AchClient::AchWorks.send( 12 | :request, 13 | method: :connection_check, 14 | message: AchClient::AchWorks::CompanyInfo.build.to_hash 15 | ) 16 | end.first 17 | assert_includes(output, 'request-connection_check') 18 | assert_includes(output, 'response-AchWorks-connection_check') 19 | assert_includes(output, '.xml') 20 | assert_includes(output, ' Array})] 8 | def self.hashlist_merge(hashlist) 9 | hashlist.reduce do |map, record| 10 | map.merge(record) do |_key, left_value, right_value| 11 | left_value + right_value 12 | end 13 | end 14 | end 15 | 16 | def self.icheck_date_format(date_string) 17 | return unless date_string 18 | begin 19 | Date.strptime(date_string, "%m/%d/%Y") 20 | rescue Date::Error 21 | return date_string 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/ach_client/objects/account_types.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | ## Representations of merchant account types (checking or savings) 3 | module AccountTypes 4 | ## Base class for all account types 5 | class AccountType 6 | end 7 | 8 | ## Represents a checking account 9 | class Checking < AchClient::AccountTypes::AccountType 10 | end 11 | 12 | ## Represents a savings account 13 | class Savings < AchClient::AccountTypes::AccountType 14 | end 15 | 16 | ## Represents a business checking account 17 | class BusinessChecking < AchClient::AccountTypes::Checking 18 | end 19 | 20 | ## Represents a business savings account 21 | class BusinessSavings < AchClient::AccountTypes::Savings 22 | end 23 | 24 | ## Represents a personal checking account 25 | class PersonalChecking < AchClient::AccountTypes::Checking 26 | end 27 | 28 | ## Represents a personal savings account 29 | class PersonalSavings < AchClient::AccountTypes::Savings 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 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. 33 | -------------------------------------------------------------------------------- /lib/ach_client/providers/soap/ach_works/date_formatter.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class AchWorks 3 | ## 4 | # For formatting dates for AchWorks 5 | class DateFormatter 6 | 7 | ## 8 | # Formats given date in the manner required by AchWorks 9 | # The date can be a String or a Date/DateTime. 10 | # If it is a string it will be given to the DateTime parser 11 | # Will be formatted like 2016-08-11T09:56:24 12 | # @param date [Object] String or Date to format 13 | # @return [String] formatted datetime 14 | def self.format(date) 15 | if date.is_a?(String) 16 | format_string(date) 17 | elsif date.respond_to?(:strftime) 18 | format_date(date) 19 | else 20 | raise 'Cannot format date' 21 | end 22 | end 23 | 24 | private_class_method def self.format_string(string) 25 | format_date(DateTime.parse(string)) 26 | end 27 | 28 | private_class_method def self.format_date(date) 29 | date.strftime('%Y-%m-%dT%H:%M:%S') 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/lib/ach_client/ach_works/input_company_info_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AchWorks 4 | class CompanyInfoTest < Minitest::Test 5 | def test_that_it_works 6 | assert_equal( 7 | AchClient::AchWorks::CompanyInfo.build.to_hash, 8 | { 9 | InpCompanyInfo: { 10 | Company: 'MYCOMPANY', 11 | CompanyKey: 'SASD%!%$DGLJGWYRRDGDDUDFDESDHDD', 12 | LocID: '9505', 13 | SSS: 'TST' 14 | } 15 | } 16 | ) 17 | end 18 | 19 | def test_connection_valid 20 | VCR.use_cassette('connection_valid') do 21 | assert(AchClient::AchWorks::CompanyInfo.build.connection_valid?) 22 | end 23 | end 24 | 25 | def test_company_valid 26 | VCR.use_cassette('company_valid') do 27 | assert(AchClient::AchWorks::CompanyInfo.build.company_valid?) 28 | end 29 | end 30 | 31 | def test_that_connection_check_works 32 | VCR.use_cassette('company_valid') do 33 | VCR.use_cassette('connection_valid') do 34 | assert(AchClient::AchWorks::CompanyInfo.build.valid?) 35 | end 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/ach_client/providers/abstract/ach_batch.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | # Abstract Ach provider, which all other provider's inherit from 3 | class Abstract 4 | 5 | # Base class for sending batched ACH transactions to various providers 6 | class AchBatch 7 | 8 | ## 9 | # @param ach_transactions [Array] List of AchTransaction objects to batch 10 | def initialize(ach_transactions: []) 11 | @ach_transactions = ach_transactions 12 | end 13 | 14 | ## 15 | # Sends the batch to the provider, and returns a tracking identifier 16 | # @return [Array] Identifiers to use to poll for result of batch 17 | # processing later on. 18 | def send_batch 19 | if @ach_transactions.all?(&:sendable?) 20 | do_send_batch 21 | else 22 | raise InvalidAchTransactionError 23 | end 24 | end 25 | 26 | # Implementation of sending the ACH batch to the provider, to be 27 | # implemented by the subclass 28 | # @return [Array] Identifiers to use to poll for result of batch 29 | # processing later on. 30 | def do_send_batch 31 | raise AbstractMethodError 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/ach_client/logging/log_provider_job.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class Logging 3 | # Worker to asynchronously invoke the log provider 4 | class LogProviderJob 5 | include SuckerPunch::Job 6 | 7 | ## Prettifies the xml and sends it asynchronously to the log provider 8 | # @param xml [String] xml body to log 9 | # @param name [String] title for the log 10 | def perform(body:, name:) 11 | # Savon logger does a nice job of XML pretty print 12 | # Takes: message, list of filters, pretty print boolean 13 | AchClient::Logging.log_provider.send_logs( 14 | body: maybe_encrypt_message( 15 | message: Savon::LogMessage.new( 16 | body, 17 | AchClient::Logging.log_filters, 18 | true 19 | ).to_s 20 | ), 21 | name: name 22 | ) 23 | end 24 | 25 | private 26 | def maybe_encrypt_message(message:) 27 | # Only encrypt if the client provided a password and a salt 28 | if AchClient::Logging.should_use_encryption? 29 | AchClient::Logging.codec.encrypt_and_sign(message) 30 | else 31 | message 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /test/lib/ach_client/silicon_valley_bank/sftp_provider_test.rb: -------------------------------------------------------------------------------- 1 | class SiliconValleyBank 2 | class SftpProviderTest < Minitest::Test 3 | def test_retrieve_files 4 | AchClient::Logging.stub( 5 | :log_provider, 6 | AchClient::Logging::StdoutLogProvider 7 | ) do 8 | Net::SFTP.stubs(:start).yields(FakeSFTPConnection) 9 | log_output = capture_subprocess_io do 10 | assert_equal( 11 | AchClient::SiliconValleyBank.retrieve_files( 12 | file_path: './test_directory', 13 | glob: '*' 14 | ), 15 | { 16 | "ACHP08111605" => "test file contents blah blah blah", 17 | "ACHP08111601" => "test file contents blah blah blah", 18 | "ACHP08111602" => "test file contents blah blah blah" 19 | } 20 | ) 21 | end.first 22 | # Make sure logging worked too 23 | assert_equal( 24 | log_output, 25 | "response-2016-08-11T10:13:05-04:00-ACHP08111605\ntest file contents blah blah blah\nresponse-2016-08-11T10:13:05-04:00-ACHP08111601\ntest file contents blah blah blah\nresponse-2016-08-11T10:13:05-04:00-ACHP08111602\ntest file contents blah blah blah\n" 26 | ) 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/ach_client/providers/abstract/transformer.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | ## 3 | # Transforms between client class and the strings that the provider expects 4 | # For example, the TransactionType classes are serialized as 'C' or 'D' for 5 | # AchWorks 6 | class Transformer 7 | 8 | ## 9 | # Convert provider's string representation to AchClient class 10 | # representation 11 | # @param string [String] string to turn into class 12 | # @return [Class] for example AchClient::AccountTypes::Checking or 13 | # AchClient::AccountTypes::Savings 14 | def self.deserialize_provider_value(string) 15 | self.transformer[string] or raise( 16 | "Unknown #{self} string #{string}" 17 | ) 18 | end 19 | 20 | ## 21 | # Convert client class to string representation used by provider 22 | # @param type [Class] the type to convert to a string 23 | # @return [String] string serialization of the input class 24 | def self.serialize_to_provider_value(type) 25 | self.transformer.find do |_, v| 26 | type <= v 27 | end.try(:first) or raise( 28 | "type must be one of #{self.transformer.values.uniq.join(', ')}" 29 | ) 30 | end 31 | 32 | # Mapping of classes to strings, to be overridden 33 | # @return [Hash {String => Class}] the mapping 34 | def self.transformer 35 | raise AbstractMethodError 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/ach_client/providers/soap/i_check_gateway/company_info.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class ICheckGateway 3 | # ICheckGateway credentials for your company 4 | class CompanyInfo < Abstract::CompanyInfo 5 | attr_reader :api_key, 6 | :site_i_d, 7 | :site_key, 8 | :live 9 | 10 | ## 11 | # @param api_key [String] your ICheckGateway API key 12 | # @param site_i_d [String] your ICheckGateway SiteID 13 | # @param site_key [String] your ICheckGateway SiteKey 14 | # @param live [Boolean] "GatewayLiveMode" value 15 | def initialize( 16 | api_key:, 17 | site_i_d:, 18 | site_key:, 19 | live: 20 | ) 21 | @api_key = api_key 22 | @site_i_d = site_i_d 23 | @site_key = site_key 24 | @live = live 25 | end 26 | 27 | ## 28 | # @return [CompanyInfo] instance built from configuration values 29 | def self.build 30 | build_from_config([ 31 | :api_key, 32 | :live, 33 | :site_i_d, 34 | :site_key 35 | ]) 36 | end 37 | 38 | ## 39 | # Build a hash to send to ICheckGateway 40 | # @return [Hash] hash to send to ICheckGateway 41 | def to_hash 42 | { 43 | SiteID: @site_i_d, 44 | SiteKey: @site_key, 45 | APIKey: @api_key, 46 | GatewayLiveMode: @live ? '1' : '0' 47 | } 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/ach_client/objects/return_codes.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | # Finding and listing all Ach return codes 3 | class ReturnCodes 4 | 5 | # The path to the file where the return codes are enumerated 6 | RETURN_CODES_YAML = '../../../config/return_codes.yml' 7 | 8 | class_attribute :_return_codes 9 | 10 | # @return [Array] A list of all return codes. 11 | def self.all 12 | self._return_codes ||= YAML.load_file( 13 | File.expand_path(File.join(File.dirname(__FILE__), RETURN_CODES_YAML)) 14 | ).map do |code| 15 | ReturnCode.new( 16 | code: code['code'], 17 | description: code['description'], 18 | reason: code['reason'], 19 | risk_and_enforcement_category: code['risk_and_enforcement_category'] 20 | ) 21 | end 22 | end 23 | 24 | def self.unauthorized 25 | self.all.select(&:unauthorized_return?) 26 | end 27 | 28 | def self.administrative 29 | self.all.select(&:administrative_return?) 30 | end 31 | 32 | # Finds the first ReturnCode with the given code, or raises an exception. 33 | # @param [String] 3 char code identifier for a return code 34 | # @param [AchClient::ReturnCode] The ReturnCode object with that code 35 | def self.find_by(code:) 36 | self.all.find do |return_code| 37 | return_code.code == code 38 | # For some reason || is bad syntax in this context 39 | end or raise "Could not find return code #{code}" 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/ach_client/providers/soap/i_check_gateway/i_check_gateway.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | # Wrapper class for all things ICheckGateway 3 | class ICheckGateway 4 | include SoapProvider 5 | 6 | # @return [String] Site identifier from ICheckGateway 7 | class_attribute :site_i_d 8 | 9 | # @return [String] Site key from ICheckGateway 10 | class_attribute :site_key 11 | 12 | # @return [String] API key from ICheckGateway 13 | class_attribute :api_key 14 | 15 | # @return [Boolean] ICheckGateway GatewayLiveMode setting 16 | # ICheckGateway uses their production environment for test/sandbox accounts 17 | # Set this to false if you don't want your transactions to actually complete 18 | class_attribute :live 19 | 20 | ## Wraps SOAP request with exception handling and pulls relevent hash out of 21 | # response 22 | # @param method [Symbol] SOAP action to call 23 | # @param message [Hash] SOAP message to send 24 | # @return [Hash] ICheckGateway response 25 | def self.wrap_request(method:, message:) 26 | response = AchClient::ICheckGateway.request( 27 | method: method, 28 | message: message 29 | ) 30 | if response.success? 31 | response.body["#{method}_response".to_sym]["#{method}_result".to_sym] 32 | else 33 | # This happens when something goes wrong within the actual HTTP 34 | # request before it gets to the soap processing. 35 | raise 'Unknown ICheckGateway SOAP fault' 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/ach_client/logging/savon_observer.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | class Logging 3 | # Hooks into every savon request. 4 | # #notify is called before the request is made 5 | class SavonObserver 6 | 7 | ## 8 | # Hooks into every SOAP request and sends the XML body to be logged. 9 | # @param operation_name [Symbol] name of SOAP operation being exectuted 10 | # @param builder [Savon::Builder] Savon wrapper for the request 11 | # @param globals [Savon::GlobalOptions] Savon's global options 12 | # @param locals [Savon::LocalOptions] Savon's global options 13 | # @return [NilClass] returns nothing so the request is not mutated 14 | def notify(operation_name, builder, globals, _locals) 15 | # Since Savon only lets us register observers globally this method is called by any other Savon clients outside 16 | # this library. We don't want to log for those other clients so we check to see that the request came from 17 | # AchClient by comparing the wsdl to our known wsdls 18 | return unless [ 19 | AchClient::ICheckGateway.wsdl, 20 | AchClient::AchWorks.wsdl 21 | ].include?(globals.instance_variable_get(:@options)[:wsdl]) 22 | # Send the xml body to the logger job 23 | AchClient::Logging::LogProviderJob.perform_async( 24 | body: builder.to_s, 25 | name: "request-#{operation_name}-#{DateTime.now}.xml" 26 | ) 27 | 28 | # Must return nil so the request is unaltered 29 | nil 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/ach_client/providers/sftp/nacha_provider.rb: -------------------------------------------------------------------------------- 1 | module AchClient 2 | # Base concern for providers like SVB and BOA that use NACHA format 3 | module NachaProvider 4 | extend ActiveSupport::Concern 5 | 6 | included do 7 | # @return [String] Immediate Destination ID provided by SVB, refers to SVB 8 | class_attribute :immediate_destination 9 | 10 | # @return [String] Immediate Destination Name provided by SVB, refers to SVB 11 | class_attribute :immediate_destination_name 12 | 13 | # @return [String] Immediate Origin ID provided by SVB, refers to you 14 | class_attribute :immediate_origin 15 | 16 | # @return [String] Immediate Origin Name provided by SVB, refers to you 17 | class_attribute :immediate_origin_name 18 | 19 | # @return [String] Company Identification provided by SVB, refers to you 20 | class_attribute :company_identification 21 | 22 | # @return [String] Company Entry Description, whatever that means 23 | class_attribute :company_entry_description 24 | 25 | # @return [String] originating_dfi_identification refers to your bank? 26 | # originating_dfi => "Originating Depository Financial Institution" 27 | class_attribute :originating_dfi_identification 28 | 29 | # @return [Proc