dcas-ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,123 @@
1
+ # __ Documentation __
2
+ # CreditCard returns are pretty straightforward. They're handled by just this class here, DcasResponse.
3
+ # ACH returns come in two stages, and they have to be handled differently, so they're defined in
4
+ # DcasAchResponse and DcasAchReturn.
5
+ #
6
+ # A DcasResponse object has a status. That status can be one of ['I', 'A', 'G', 'D']
7
+ # These map to a GotoTransaction's statuses, which are as follows:
8
+ # 'R' => Received
9
+ # 'A' => Accepted
10
+ # 'G' => Paid
11
+ # 'D' => Declined
12
+ # 'E' => Processing Error
13
+ # Notice that the three with the stars map directly to three of a DcasResponse object's statuses.
14
+ # The fourth status, 'I', is used to denote an Informational record. These records are never intended
15
+ # to state whether a payment was accepted, declined, or paid, but rather to provide important information
16
+ # back into the system about an account, such as information that needs to be updated soon.
17
+
18
+ module DCAS
19
+ class Response
20
+ class << self
21
+ def responses_in(filename_or_content)
22
+ responses = []
23
+
24
+ if filename_or_content !~ /\n/ && File.exists?(filename_or_content)
25
+ filename_or_content = File.open(filename_or_content, 'rb').map {|l| l.gsub(/[\n\r]+/, "\n")}.join
26
+ end
27
+
28
+ CSV::Reader.parse(filecontents) do |ccrow|
29
+ # Could be simply '9999' -- error!
30
+ begin
31
+ next if ccrow == ['9999']
32
+ # Otherwise, it is in this format:
33
+ # CC,AccountNumber,ReturnCode,ReasonDescription,CustTraceCode
34
+ responses << new(ccrow)
35
+ rescue # Rescue errors caused by the data in the csv.
36
+ end
37
+ end
38
+
39
+ responses
40
+ end
41
+
42
+ # Runs the given block for each response in the given response file.
43
+ def each_response_in(filename_or_content)
44
+ raise ArgumentError, "must include a block!" unless block_given?
45
+ responses_in(filename_or_content).each do |response|
46
+ yield response
47
+ end
48
+ end
49
+ end
50
+
51
+ attr_accessor :account_number, :check_number, :client_id, :status, :information, :description, :ach_submitted
52
+ def attributes
53
+ at = {}
54
+ instance_variables.each do |iv|
55
+ iv.gsub!('@', '')
56
+ at[iv] = instance_variable_get("@#{iv}")
57
+ end
58
+ at
59
+ end
60
+ def attributes=(new_attributes)
61
+ return if new_attributes.nil?
62
+ with(new_attributes.dup) do |a|
63
+ a.stringify_keys!
64
+ a.each {|k,v| send(k + "=", a.delete(k)) if respond_to?("#{k}=")}
65
+ end
66
+ end
67
+
68
+ # Tells if the payment was invalid. By default it's just false, but child classes can redefine this.
69
+ def invalid?
70
+ false
71
+ end
72
+
73
+ CC_RET_CODES = {
74
+ '0' => 'D',
75
+ '1' => 'G',
76
+ '2' => 'I', # I think, we should never get this status. (Haven't yet...)
77
+ '99' => 'E' # These are always server errors
78
+ }
79
+
80
+ def initialize(attrs={})
81
+ new_attrs = {}
82
+ nattrs = attrs.dup
83
+ if nattrs.is_a?(Hash) # Is xml-hash
84
+ nattrs.stringify_keys!
85
+ # status, order_number, transacted_at, transaction_id, description
86
+ new_attrs = nattrs
87
+ elsif nattrs.respond_to?('[]') # Is csv row
88
+ # GotoBilling: MerchantID,FirstName,LastName,CustomerID,Amount,SentDate,SettleDate,TransactionID,TransactionType,Status,Description
89
+ # DCAS: CC,AccountNumber,ReturnCode,ReasonDescription,ConfirmationNumber
90
+ # ret could be 0 (denied), 1 (approved), 2 (call for authorization), or 99 (error)
91
+ new_attrs = {
92
+ :status => (nattrs[2].to_s == 'I' ? 'R' : CC_RET_CODES[nattrs[2].to_s]),
93
+ :description => nattrs[3],
94
+ :account_number => nattrs[1],
95
+ :client_id => nattrs[4][4..-1]
96
+ }
97
+ # This is the case where Malibu must call DCAS for authorization. We haven't come across that need yet, but a note should be made.
98
+ # I'll make it an informational and 'Received' record -- but we have no answer other than this. The transaction won't go through without attention.
99
+ new_attrs[:information] = "MUST CALL FOR Credit Card payment authorization! If you have any questions ask your Manager or the tech guy." if nattrs[2].to_s == 'I'
100
+ end
101
+ self.attributes = new_attrs
102
+ end
103
+
104
+ # def transaction
105
+ # @transaction ||= GotoTransaction.find_by_batch_id_and_client_id(self.batch_id, client_id)
106
+ # end
107
+
108
+ # def record_to_transaction!
109
+ # return unless transaction.status != status && transaction.description != description
110
+ # if transaction.transaction_id.to_i != 0 && transaction.status == 'G' && status == 'D'
111
+ # # Was accepted, now declined.
112
+ # # Delete the transaction if it was previously created.
113
+ # puts "Previously accepted, now declined: delete transaction on master, remove association on goto_transaction"
114
+ # # Helios::Transact.update_on_master(self.transaction.transaction_id, :CType => 1, :client_no => self.transaction_id)
115
+ # transaction.transaction_id = 0
116
+ # end
117
+ # transaction.description = description
118
+ # transaction.status = status
119
+ # transaction.ach_submitted = ach_submitted if ach_submitted
120
+ # transaction.save
121
+ # end
122
+ end
123
+ end
data/lib/dcas.rb ADDED
@@ -0,0 +1,140 @@
1
+ require 'ftools'
2
+
3
+ module DCAS
4
+ TESTING = false
5
+ BUCKET_HOST = 'ftp.ezpaycenters.net'
6
+ OUTGOING_BUCKET = 'outgoing'
7
+ INCOMING_BUCKET = 'incoming'
8
+ STAGING_BUCKET = 'staging'
9
+
10
+ class << self
11
+ # Parses the responses from a response file and returns an array of DCAS::Response objects.
12
+ def parse_response_file(filename_or_content)
13
+ end
14
+ end
15
+
16
+ class Client
17
+ # Instantiate a new Client object which can do authenticated actions in a DCAS FTPS bucket.
18
+ def initialize(options={})
19
+ raise ArgumentError, "must include :username, :password, :company_alias, :company_username, :company_password, and :cache_location" if [:username, :password, :company_alias, :company_username, :company_password, :cache_location].any? {|k| !options.has_key?(k)}
20
+ @username = options[:username]
21
+ @password = options[:password]
22
+ @company_alias = options[:company_alias]
23
+ @company_username = options[:company_username]
24
+ @company_password = options[:company_password]
25
+ @cache_location = options[:cache_location]
26
+ end
27
+
28
+ attr_reader :username, :password, :company_alias, :company_username, :company_password, :cache_location
29
+
30
+ # :nodoc:
31
+ def batches
32
+ @batches ||= []
33
+ end
34
+
35
+ # Begin a new batch associated with this client.
36
+ def new_batch(batch_id)
37
+ batches << DCAS::PaymentBatch.new(self, batch_id)
38
+ batches.last
39
+ end
40
+
41
+ # Uploads a single payments file to the DCAS outgoing payments bucket.
42
+ def submit_payments_file!(filename)
43
+ shortname = filename.gsub(/.*[\\\/][^\\\/]+$/,'')
44
+ with_ftp do |ftp|
45
+ # 1) Create the STAGING folder if it's not already there.
46
+ ftp.mkdir(DCAS::STAGING_BUCKET) unless ftp.nlst.include?(DCAS::STAGING_BUCKET)
47
+ ftp.chdir(DCAS::STAGING_BUCKET)
48
+ # 2) Delete the same filename from the STAGING folder if one exists.
49
+ ftp.delete(shortname) if ftp.nlst.include?(shortname)
50
+ # 3) Upload the file into the STAGING folder.
51
+ ftp.put(filename, shortname)
52
+ # 4) If we're still connected, check the file size of the file, then move it out of STAGING and mark file as completed.
53
+ if ftp.nlst.include?(shortname) && ftp.size(shortname) == File.size(filename)
54
+ ftp.rename(shortname, "../#{DCAS::OUTGOING_BUCKET}/#{shortname}") unless DCAS::TESTING
55
+ else
56
+ raise RuntimeError, "FAILED uploading `#{filename}' - incomplete or unsuccessful upload. Please try again."
57
+ end
58
+ end
59
+ true
60
+ end
61
+
62
+ # Writes all batches to file and submits them to the DCAS outgoing payments bucket.
63
+ def submit_batches!
64
+ File.makedirs(cache_location)
65
+ batches_submitted = 0
66
+ with_ftp do
67
+ # 1) Gather all payments for this client.
68
+ batches.each do |batch| # 2) For each file type (ach, cc) yet to be uploaded:
69
+ filename = cache_location + "/#{company_user}_#{batch.type}_#{Time.now.strftime("%Y%m%d")}.csv"
70
+ # 1) Create the file locally.
71
+ File.open(filename) {|f| f << batch.to_csv }
72
+ # 2) Upload it to the DCAS outgoing payments bucket.
73
+ batches_submitted += 1 if submit_payments_file!(filename)
74
+ end
75
+ end
76
+ end
77
+
78
+ # Checks for response files in the DCAS incoming responses bucket.
79
+ def available_response_files
80
+ with_ftp do |ftp|
81
+ # 3) List the *.csv files in the INCOMING bucket.
82
+ result = if ftp.nlst.include?(DCAS::INCOMING_BUCKET)
83
+ ftp.chdir(DCAS::INCOMING_BUCKET)
84
+ ftp.nlst.select {|f| f =~ /\.csv$/}
85
+ else
86
+ []
87
+ end
88
+ end
89
+ end
90
+
91
+ # Downloads all response files in the DCAS incoming responses bucket.
92
+ def download_response_files!
93
+ files_downloaded = []
94
+ File.makedirs(cache_location + '/returns')
95
+ with_ftp do |ftp|
96
+ files = ftp.list('*.csv')
97
+ files.each do |filels|
98
+ size, file = filels.split(/ +/)[4], filels.split(/ +/)[8..-1].join(' ')
99
+ ftp.get(file, cache_location + '/returns/' + user_suffix + '_' + file)
100
+ files_downloaded << file
101
+ end
102
+ end
103
+ files_downloaded
104
+ end
105
+
106
+
107
+ private
108
+ def user_suffix
109
+ company_username.match(/(?:malibu|maltan|malent)?(.*)(?:VT)?/)[1]
110
+ end
111
+
112
+ def ftp_connection
113
+ @ftp ||= Net::FTPS::Implicit.new(DCAS::BUCKET_HOST, username, password, nil, OpenSSL::SSL::VERIFY_NONE)
114
+ end
115
+ # This allows all functionality to share the same connection, then log out after all work is finished.
116
+ def with_ftp(&block)
117
+ @inside_with_ftp = @inside_with_ftp.to_i + 1
118
+ if block.arity == 1
119
+ yield ftp_connection
120
+ else
121
+ yield
122
+ end
123
+ @inside_with_ftp -= 1
124
+ ftp_done
125
+ end
126
+ def ftp_done
127
+ close_ftp if @inside_with_ftp.to_i == 0
128
+ end
129
+ def close_ftp
130
+ if @ftp
131
+ @ftp.quit
132
+ @ftp.close
133
+ @ftp = nil
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ require 'dcas/payment'
140
+ require 'dcas/response'
@@ -0,0 +1,225 @@
1
+
2
+ # Submitted to ruby-lang.org: http://redmine.ruby-lang.org/issues/show/1371
3
+ # Needs Documentation!
4
+ # quote: "I prefer an approach to modify Net::FTP itself to support implicit
5
+ # (and explicit) FTPS. Please see Net::IMAP in Ruby 1.9."
6
+ # - so I probably should go and edit the net/ftp file to include some of the
7
+ # features of FTPS, when TLS is desired over the FTP connection.
8
+
9
+
10
+ require 'socket'
11
+ require 'openssl'
12
+ require 'net/ftp'
13
+
14
+ class Net::FTPS < Net::FTP
15
+ end
16
+
17
+ class Net::FTPS::Implicit < Net::FTP
18
+ FTP_PORT = 990
19
+
20
+ def initialize(host=nil, user=nil, passwd=nil, acct=nil, verify_mode=OpenSSL::SSL::VERIFY_PEER)
21
+ super(host, user, passwd, acct)
22
+ @passive = true
23
+ @binary = false
24
+ @debug_mode = false
25
+ @data_protection = 'P'
26
+ @data_protected = false
27
+ @verify_mode = verify_mode
28
+ end
29
+ attr_accessor :data_protection
30
+
31
+ def open_socket(host, port, data_socket=false)
32
+ tcpsock = if defined? SOCKSsocket and ENV["SOCKS_SERVER"]
33
+ @passive = true
34
+ SOCKSsocket.open(host, port)
35
+ else
36
+ TCPSocket.new(host, port)
37
+ end
38
+ if !data_socket || @data_protection == 'P'
39
+ ssl_context = OpenSSL::SSL::SSLContext.new('SSLv23')
40
+ ssl_context.verify_mode = @verify_mode
41
+ ssl_context.key = nil
42
+ ssl_context.cert = nil
43
+ ssl_context.timeout = 10
44
+
45
+ sock = OpenSSL::SSL::SSLSocket.new(tcpsock, ssl_context)
46
+ sock.connect
47
+ else
48
+ sock = tcpsock
49
+ end
50
+ return sock
51
+ end
52
+ private :open_socket
53
+
54
+ def connect(host, port=FTP_PORT)
55
+ @sock = open_socket(host, port)
56
+ mon_initialize
57
+ getresp
58
+ at_exit {
59
+ if @sock && !@sock.closed?
60
+ voidcmd("ABOR") rescue EOFError
61
+ voidcmd("QUIT") rescue EOFError
62
+ close
63
+ end
64
+ }
65
+ end
66
+
67
+ def abort
68
+ voidcmd("ABOR") rescue EOFError
69
+ end
70
+
71
+ def quit
72
+ voidcmd("QUIT") rescue EOFError
73
+ end
74
+
75
+ def close
76
+ @sock.close # SSL
77
+ @sock.io.close # TCP
78
+ end
79
+
80
+ def retrbinary(cmd, blocksize, rest_offset = nil) # :yield: data
81
+ synchronize do
82
+ voidcmd("TYPE I")
83
+ conn = transfercmd(cmd, rest_offset)
84
+ data = get_data(conn,blocksize)
85
+ yield(data)
86
+ voidresp
87
+ end
88
+ end
89
+
90
+ def get_data(sock,blocksize=1024)
91
+ timeout = 10
92
+ starttime = Time.now
93
+ buffer = ''
94
+ timeouts = 0
95
+ catch :done do
96
+ loop do
97
+ event = select([sock],nil,nil,0.5)
98
+ if event.nil? # nil would be a timeout, we'd do nothing and start loop over. Of course here we really have no timeout...
99
+ timeouts += 0.5
100
+ break if timeouts > timeout
101
+ else
102
+ event[0].each do |sock| # Iterate through all sockets that have pending activity
103
+ if sock.eof? # Socket's been closed by the client
104
+ throw :done
105
+ else
106
+ buffer << sock.readpartial(blocksize)
107
+ if block_given? # we're in line-by-line mode
108
+ lines = buffer.split(/\r?\n/)
109
+ buffer = buffer =~ /\n$/ ? '' : lines.pop
110
+ lines.each do |line|
111
+ yield(line)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ sock.close
120
+ buffer
121
+ end
122
+
123
+ def retrlines(cmd) # :yield: line
124
+ synchronize do
125
+ voidcmd("TYPE A")
126
+ voidcmd("STRU F")
127
+ voidcmd("MODE S")
128
+ conn = transfercmd(cmd)
129
+ get_data(conn) do |line|
130
+ yield(line)
131
+ end
132
+ getresp
133
+ end
134
+ end
135
+
136
+ #
137
+ # Puts the connection into binary (image) mode, issues the given server-side
138
+ # command (such as "STOR myfile"), and sends the contents of the file named
139
+ # +file+ to the server. If the optional block is given, it also passes it
140
+ # the data, in chunks of +blocksize+ characters.
141
+ #
142
+ def storbinary(cmd, file, blocksize, rest_offset = nil, &block) # :yield: data
143
+ if rest_offset
144
+ file.seek(rest_offset, IO::SEEK_SET)
145
+ end
146
+ synchronize do
147
+ voidcmd("TYPE I")
148
+ conn = transfercmd(cmd, rest_offset)
149
+ loop do
150
+ buf = file.read(blocksize)
151
+ break if buf == nil
152
+ conn.write(buf)
153
+ yield(buf) if block
154
+ end
155
+ conn.close # closes the SSL
156
+ conn.io.close # closes the TCP below it
157
+ voidresp
158
+ end
159
+ end
160
+
161
+ #
162
+ # Puts the connection into ASCII (text) mode, issues the given server-side
163
+ # command (such as "STOR myfile"), and sends the contents of the file
164
+ # named +file+ to the server, one line at a time. If the optional block is
165
+ # given, it also passes it the lines.
166
+ #
167
+ def storlines(cmd, file, &block) # :yield: line
168
+ synchronize do
169
+ voidcmd("TYPE A")
170
+ conn = transfercmd(cmd)
171
+ loop do
172
+ buf = file.gets
173
+ break if buf == nil
174
+ if buf[-2, 2] != CRLF
175
+ buf = buf.chomp + CRLF
176
+ end
177
+ conn.write(buf)
178
+ yield(buf) if block
179
+ end
180
+ conn.close # closes the SSL
181
+ conn.io.close # closes the TCP below it
182
+ voidresp
183
+ end
184
+ end
185
+
186
+ def transfercmd(cmd, rest_offset=nil)
187
+ unless @data_protected
188
+ voidcmd('PBSZ 0')
189
+ sendcmd("PROT #{@data_protection}")
190
+ @data_protected = true
191
+ end
192
+
193
+ if @passive
194
+ host, port = makepasv
195
+ if @resume and rest_offset
196
+ resp = sendcmd("REST " + rest_offset.to_s)
197
+ if resp[0] != ?3
198
+ raise FTPReplyError, resp
199
+ end
200
+ end
201
+ putline(cmd)
202
+ conn = open_socket(host, port, true)
203
+ resp = getresp # Should be a 150 response
204
+ if resp[0] != ?1
205
+ raise FTPReplyError, resp
206
+ end
207
+ else
208
+ sock = makeport
209
+ if @resume and rest_offset
210
+ resp = sendcmd("REST " + rest_offset.to_s)
211
+ if resp[0] != ?3
212
+ raise FTPReplyError, resp
213
+ end
214
+ end
215
+ resp = sendcmd(cmd)
216
+ if resp[0] != ?1
217
+ raise FTPReplyError, resp
218
+ end
219
+ conn = sock.accept
220
+ sock.close
221
+ end
222
+ return conn
223
+ end
224
+ private :transfercmd
225
+ end
@@ -0,0 +1,11 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe DCAS::Response do
4
+ before :all do
5
+ end
6
+
7
+ it "should parse a CreditCard response correctly"
8
+
9
+ it "should parse an ACH response correctly"
10
+
11
+ end
data/spec/dcas_spec.rb ADDED
@@ -0,0 +1,50 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Dcas - Comprehensive failure frequency test" do
4
+ before :all do
5
+ DCAS::TESTING = true
6
+ @fake_client = DCAS::Client.new(
7
+ :username => 'none',
8
+ :password => 'none',
9
+ :company_alias => 'tester',
10
+ :company_username => 'tester1',
11
+ :company_password => 'fakeness1',
12
+ :cache_location => 'none'
13
+ )
14
+ end
15
+
16
+ it "should generate Ach payment files correctly" do
17
+ ach_batch = @fake_client.new_batch(1)
18
+ Fixtures[:TestPayments][:Ach].collect {|p| ach_batch << DCAS::AchPayment.new(*p) }
19
+ ach_payments_file = ach_batch.to_csv
20
+ ach_payments_file.should eql(File.read('spec/fixtures/ach_payments.csv'))
21
+ end
22
+
23
+ it "should generate CreditCard payment files correctly" do
24
+ cc_batch = @fake_client.new_batch(1)
25
+ Fixtures[:TestPayments][:CreditCard].collect {|p| cc_batch << DCAS::CreditCardPayment.new(*p) }
26
+ cc_payments_file = cc_batch.to_csv
27
+ cc_payments_file.should eql(File.read('spec/fixtures/credit_card_payments.csv'))
28
+ end
29
+
30
+ it "should be able to complete an entire mock procedure without failing" do
31
+ lambda {
32
+ # Depends: Fixture load of a list of DCAS logins to test
33
+ # Depends: Fixed test files
34
+ Fixtures[:Clients].each do |client|
35
+ cc_batch = client.new_batch(1)
36
+ Fixtures[:TestPayments][:CreditCard].each {|p| cc_batch << DCAS::CreditCardPayment.new(*p) }
37
+
38
+ ach_batch = client.new_batch(1)
39
+ Fixtures[:TestPayments][:Ach].each {|p| ach_batch << DCAS::AchPayment.new(*p) }
40
+
41
+ client.submit_batches!.should eql(Fixtures[:PaymentFiles].length)
42
+ end
43
+ Fixtures[:Clients].each do |client|
44
+ client.download_response_files!
45
+ end
46
+ }.should_not raise_error
47
+ end
48
+
49
+ # I can't fake it failing without too much extra work, so I'm just testing successes for now.
50
+ end
File without changes
@@ -0,0 +1,11 @@
1
+ HD,tester,tester1,fakeness1,Check
2
+ CA,124083517,182045828,2010010,18.88,,Dan Hall,,,,,,,,,100111428,,,Debit,,Checking
3
+ CA,524881452,584218548,2010010,18.88,,Karie Moore,,,,,,,,,100111200,,,Debit,,Checking
4
+ CA,107502429,94217885428,2010010,18.88,,Therese Boon,,,,,,,,,100111829,,,Debit,,Checking
5
+ CA,342882012,24254,2010010,18.88,,Gretchen Gutierrez,,,,,,,,,100111282,,,Debit,,Savings
6
+ CA,547107458,280458548254,2010010,18.88,,Audrice Pitto,,,,,,,,,100111795,,,Debit,,Checking
7
+ CA,125480806,641081508245,2010010,18.88,,Rachel Ruppert,,,,,,,,,100111537,,,Debit,,Checking
8
+ CA,982458912,52488542858,2010010,18.88,,Judith Witlock,,,,,,,,,100111892,,,Debit,,Checking
9
+ CA,108458068,4728458248,2010010,18.88,,Kris Martell,,,,,,,,,100111172,,,Debit,,Savings
10
+ CA,645388043,38184599459,2010010,18.88,,Michell Penderson,,,,,,,,,100111284,,,Debit,,Checking
11
+ CA,543882548,543886028,2010010,18.88,,Polly Clark,,,,,,,,,100111756,,,Debit,,Checking
@@ -0,0 +1,81 @@
1
+ ---
2
+ -
3
+ - '00111428'
4
+ - 'Dan Hall'
5
+ - '18.88'
6
+ - 'Checking'
7
+ - '124083517'
8
+ - '182045828'
9
+ - '2010010'
10
+ -
11
+ - '00111200'
12
+ - 'Karie Moore'
13
+ - '18.88'
14
+ - 'Checking'
15
+ - '524881452'
16
+ - '584218548'
17
+ - '2010010'
18
+ -
19
+ - '00111829'
20
+ - 'Therese Boon'
21
+ - '18.88'
22
+ - 'Checking'
23
+ - '107502429'
24
+ - '94217885428'
25
+ - '2010010'
26
+ -
27
+ - '00111282'
28
+ - 'Gretchen Gutierrez'
29
+ - '18.88'
30
+ - 'Savings'
31
+ - '342882012'
32
+ - '24254'
33
+ - '2010010'
34
+ -
35
+ - '00111795'
36
+ - 'Audrice Pitto'
37
+ - '18.88'
38
+ - 'Checking'
39
+ - '547107458'
40
+ - '280458548254'
41
+ - '2010010'
42
+ -
43
+ - '00111537'
44
+ - 'Rachel Ruppert'
45
+ - '18.88'
46
+ - 'Checking'
47
+ - '125480806'
48
+ - '641081508245'
49
+ - '2010010'
50
+ -
51
+ - '00111892'
52
+ - 'Judith Witlock'
53
+ - '18.88'
54
+ - 'Checking'
55
+ - '982458912'
56
+ - '52488542858'
57
+ - '2010010'
58
+ -
59
+ - '00111172'
60
+ - 'Kris Martell'
61
+ - '18.88'
62
+ - 'Savings'
63
+ - '108458068'
64
+ - '4728458248'
65
+ - '2010010'
66
+ -
67
+ - '00111284'
68
+ - 'Michell Penderson'
69
+ - '18.88'
70
+ - 'Checking'
71
+ - '645388043'
72
+ - '38184599459'
73
+ - '2010010'
74
+ -
75
+ - '00111756'
76
+ - 'Polly Clark'
77
+ - '18.88'
78
+ - 'Checking'
79
+ - '543882548'
80
+ - '543886028'
81
+ - '2010010'
File without changes
File without changes
@@ -0,0 +1,15 @@
1
+ ---
2
+ -
3
+ :username: testclient1
4
+ :password: test1
5
+ :company_alias: tester1
6
+ :company_username: testuser1
7
+ :company_password: fakeness1
8
+ :cache_location: ./EFT
9
+ -
10
+ :username: testclient2
11
+ :password: test2
12
+ :company_alias: tester2
13
+ :company_username: testuser2
14
+ :company_password: fakeness2
15
+ :cache_location: ./EFT
@@ -0,0 +1,11 @@
1
+ HD,tester,tester1,fakeness1,Check
2
+ CC,VISA,4508288028044788,12/2010,18.88,N,,,Kipp Owen,,,,,,,,100111532,Debit,,2,3,1,
3
+ CC,VISA,4979170284809229,12/2011,18.88,N,,,Brooke Racquet,,,,,,,,100111382,Debit,,2,3,1,
4
+ CC,MCRD,5280827180294022,12/2012,18.88,N,,,Pat Sylvester,,,,,,,,100111018,Debit,,2,3,1,
5
+ CC,MCRD,5173179138280429,12/2011,18.88,N,,,Vicki Tolliki,,,,,,,,100111992,Debit,,2,3,1,
6
+ CC,VISA,4247972792081828,12/2010,18.88,N,,,Cheryl Barger,,,,,,,,100111247,Debit,,2,3,1,
7
+ CC,MCRD,5428208180420824,12/2011,18.88,N,,,Melissa Fletcher,,,,,,,,100111028,Debit,,2,3,1,
8
+ CC,MCRD,5425682492949922,12/2012,18.88,N,,,Mack Payne,,,,,,,,100111384,Debit,,2,3,1,
9
+ CC,MCRD,5208372629781292,12/2011,18.88,N,,,Melissa Latcher,,,,,,,,100111742,Debit,,2,3,1,
10
+ CC,MCRD,5181381081538389,12/2012,18.88,N,,,Caroline Sieben,,,,,,,,100111402,Debit,,2,3,1,
11
+ CC,MCRD,5274727290818284,12/2011,18.88,N,,,Charlotte Sue Hiran,,,,,,,,100111208,Debit,,2,3,1,