ofx_for_ruby 0.1.3 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README +1 -1
- data/USAGE +17 -0
- data/lib/ofx/1.0.2/banking_message_set.rb +5 -3
- data/lib/ofx/1.0.2/credit_card_statement_message_set.rb +18 -14
- data/lib/ofx/1.0.2/header.rb +1 -1
- data/lib/ofx/1.0.2/serializer.rb +43 -21
- data/lib/ofx/1.0.2/signon_message_set.rb +1 -1
- data/lib/ofx/1.0.2/signup_message_set.rb +26 -3
- data/lib/ofx/financial_client.rb +14 -4
- data/lib/ofx/financial_institution.rb +213 -10
- data/lib/ofx/http/cacert.pem +2629 -2703
- data/lib/ofx/http/ofx_http_client.rb +14 -5
- data/lib/ofx/version.rb +1 -1
- data/lib/ofx_for_ruby.rb +60 -0
- data/ofx_for_ruby.gemspec +3 -2
- data/test/test_serializer.rb +192 -0
- metadata +39 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 41a866d8860d900a7fee63c524916d3d9cc0f771
|
4
|
+
data.tar.gz: 59869261ce4b873f2b10e63502e5d36ad45ba0c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fa7e804432236f4d49c66899a47abe5091a6845beb0898e7419334d67605828bd18f7b9db87f064cbc7ff5ba044647c53ec8467662539a2e68025eb0d72c2416
|
7
|
+
data.tar.gz: 757f22807ba8f6e6924360fa1dd4a607972d5fc201c691d3b505ad8b063d1923be03b21cf4a143e4e514f2c051453696d1f70d4a2b26324f1950ecb737d22054
|
data/.gitignore
CHANGED
data/README
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
This is a fork of the "OFX for Ruby" project developed by Chris Guidry <chrisguidry@gmail.com> and hosted on Rubyforge: http://rubyforge.org/projects/
|
1
|
+
This is a fork of the "OFX for Ruby" project developed by Chris Guidry <chrisguidry@gmail.com> and hosted on Rubyforge: http://rubyforge.org/projects/ofx/. That repository has not been updated since early 2008.
|
2
2
|
|
3
3
|
---------------------------------------
|
4
4
|
|
data/USAGE
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
An easy way to pull your OFX data is with:
|
3
|
+
|
4
|
+
fi = OFX::FinancialInstitution.get_institution('Chase')
|
5
|
+
fi.set_client(<user>, <pass>)
|
6
|
+
id = fi.get_account_id
|
7
|
+
resp = fi.send(fi.create_request_document_for_cc_statement(id))
|
8
|
+
|
9
|
+
|
10
|
+
If making changes, please test the following to ensure no regressions.
|
11
|
+
That is, the following should return data and no HTTP errors.
|
12
|
+
|
13
|
+
OFX::FinancialInstitution.get_institution('Capital One').get_anon_profile
|
14
|
+
OFX::FinancialInstitution.get_institution('Citi').get_anon_profile
|
15
|
+
OFX::FinancialInstitution.get_institution('Chase').get_anon_profile
|
16
|
+
OFX::FinancialInstitution.get_institution('AMEX').get_anon_profile
|
17
|
+
|
@@ -206,7 +206,9 @@ module OFX
|
|
206
206
|
|
207
207
|
transaction_list_hash = response_hash['BANKTRANLIST']
|
208
208
|
if (transaction_list_hash)
|
209
|
-
|
209
|
+
if transaction_list_hash['DTSTART'] && transaction_list_hash['DTEND']
|
210
|
+
response.transaction_range = transaction_list_hash['DTSTART'].to_datetime..transaction_list_hash['DTEND'].to_datetime
|
211
|
+
end
|
210
212
|
|
211
213
|
response.transactions = []
|
212
214
|
transactions = transaction_list_hash['STMTTRN'] if transaction_list_hash['STMTTRN'].kind_of?(Array)
|
@@ -221,7 +223,7 @@ module OFX
|
|
221
223
|
transaction.date_initiated = transaction_hash['DTUSER'].to_datetime if transaction_hash['DTUSER']
|
222
224
|
transaction.date_available = transaction_hash['DTAVAIL'].to_datetime if transaction_hash['DTAVAIL']
|
223
225
|
|
224
|
-
transaction.amount = transaction_hash['TRNAMT'].to_d
|
226
|
+
transaction.amount = transaction_hash['TRNAMT'].to_d if transaction_hash['TRNAMT']
|
225
227
|
transaction.currency = transaction_hash['CURRENCY'] || transaction_hash['ORIGCURRENCY'] || response.default_currency
|
226
228
|
|
227
229
|
transaction.financial_institution_transaction_identifier = transaction_hash['FITID']
|
@@ -258,4 +260,4 @@ module OFX
|
|
258
260
|
response
|
259
261
|
end
|
260
262
|
end
|
261
|
-
end
|
263
|
+
end
|
@@ -84,22 +84,19 @@ module OFX
|
|
84
84
|
def ofx_102_name
|
85
85
|
'CCSTMT'
|
86
86
|
end
|
87
|
+
|
87
88
|
def ofx_102_request_body
|
88
89
|
body = ""
|
89
|
-
|
90
90
|
body += account.to_ofx_102_request_body
|
91
|
-
|
92
91
|
body +=
|
93
|
-
" <INCTRAN>\n"
|
94
|
-
" <INCLUDE>#{include_transactions.to_ofx_102_s}\n" if include_transactions
|
95
|
-
|
92
|
+
" <INCTRAN>\n" if include_transactions
|
96
93
|
body +=
|
97
|
-
"
|
98
|
-
"
|
99
|
-
|
94
|
+
" <DTSTART>#{included_range.begin.to_ofx_102_s}\n" +
|
95
|
+
" <DTEND>#{included_range.end.to_ofx_102_s}\n" if included_range
|
96
|
+
body +=
|
97
|
+
" <INCLUDE>#{include_transactions.to_ofx_102_s}\n" if include_transactions
|
100
98
|
body +=
|
101
99
|
" </INCTRAN>" if include_transactions
|
102
|
-
|
103
100
|
body
|
104
101
|
end
|
105
102
|
|
@@ -136,7 +133,9 @@ module OFX
|
|
136
133
|
|
137
134
|
transaction_list_hash = response_hash['BANKTRANLIST']
|
138
135
|
if (transaction_list_hash)
|
139
|
-
|
136
|
+
if transaction_list_hash['DTSTART'] && transaction_list_hash['DTEND']
|
137
|
+
response.transaction_range = transaction_list_hash['DTSTART'].to_datetime..transaction_list_hash['DTEND'].to_datetime
|
138
|
+
end
|
140
139
|
|
141
140
|
response.transactions = []
|
142
141
|
transactions = transaction_list_hash['STMTTRN'] if transaction_list_hash['STMTTRN'].kind_of?(Array)
|
@@ -151,7 +150,7 @@ module OFX
|
|
151
150
|
transaction.date_initiated = transaction_hash['DTUSER'].to_datetime if transaction_hash['DTUSER']
|
152
151
|
transaction.date_available = transaction_hash['DTAVAIL'].to_datetime if transaction_hash['DTAVAIL']
|
153
152
|
|
154
|
-
transaction.amount = transaction_hash['TRNAMT'].to_d
|
153
|
+
transaction.amount = transaction_hash['TRNAMT'].to_d if transaction_hash['TRNAMT']
|
155
154
|
transaction.currency = transaction_hash['CURRENCY'] || transaction_hash['ORIGCURRENCY'] || response.default_currency
|
156
155
|
|
157
156
|
transaction.financial_institution_transaction_identifier = transaction_hash['FITID']
|
@@ -241,7 +240,10 @@ module OFX
|
|
241
240
|
statement.currency = closing_hash['CURRENCY'] || closing_hash['ORIGCURRENCY'] || response.default_currency
|
242
241
|
|
243
242
|
statement.finanical_institution_transaction_identifier = closing_hash['FITID']
|
244
|
-
|
243
|
+
|
244
|
+
if closing_hash['DTOPEN'] && closing_hash['DTCLOSE']
|
245
|
+
statement.statement_range = closing_hash['DTOPEN'].to_date..closing_hash['DTCLOSE'].to_date
|
246
|
+
end
|
245
247
|
statement.next_statement_close = closing_hash['DTNEXT'].to_date if closing_hash['DTNEXT']
|
246
248
|
|
247
249
|
statement.opening_balance = closing_hash['BALOPEN'].to_d if closing_hash['BALOPEN']
|
@@ -256,7 +258,9 @@ module OFX
|
|
256
258
|
statement.debit_adjustements = closing_hash['DEBADJ'].to_d if closing_hash['DEBADJ']
|
257
259
|
statement.credit_limit = closing_hash['CREDITLIMIT'].to_d if closing_hash['CREDITLIMIT']
|
258
260
|
|
259
|
-
|
261
|
+
if closing_hash['DTPOSTSTART'] && closing_hash['DTPOSTEND']
|
262
|
+
statement.transaction_range = closing_hash['DTPOSTSTART'].to_date..closing_hash['DTPOSTEND'].to_date
|
263
|
+
end
|
260
264
|
|
261
265
|
statement.marketing_information = closing_hash['MKTGINFO']
|
262
266
|
|
@@ -266,4 +270,4 @@ module OFX
|
|
266
270
|
response
|
267
271
|
end
|
268
272
|
end
|
269
|
-
end
|
273
|
+
end
|
data/lib/ofx/1.0.2/header.rb
CHANGED
@@ -37,7 +37,7 @@ module OFX
|
|
37
37
|
header = OFX::Header.new
|
38
38
|
|
39
39
|
header_pattern = /^(\w+)\:(.*)$/
|
40
|
-
header_string.split(
|
40
|
+
header_string.split(%r{\r*\n}).each do |this_header|
|
41
41
|
header_match = header_pattern.match(this_header)
|
42
42
|
header[header_match[1]] = header_match[2]
|
43
43
|
end
|
data/lib/ofx/1.0.2/serializer.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
# Copyright © 2007 Chris Guidry <chrisguidry@gmail.com>
|
2
2
|
#
|
3
3
|
# This file is part of OFX for Ruby.
|
4
|
-
#
|
4
|
+
#
|
5
5
|
# OFX for Ruby is free software; you can redistribute it and/or modify
|
6
6
|
# it under the terms of the GNU General Public License as published by
|
7
7
|
# the Free Software Foundation; either version 3 of the License, or
|
8
8
|
# (at your option) any later version.
|
9
|
-
#
|
9
|
+
#
|
10
10
|
# OFX for Ruby is distributed in the hope that it will be useful,
|
11
11
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
12
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
13
|
# GNU General Public License for more details.
|
14
|
-
#
|
14
|
+
#
|
15
15
|
# You should have received a copy of the GNU General Public License
|
16
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
17
|
|
@@ -20,7 +20,6 @@ require File.dirname(__FILE__) + '/header'
|
|
20
20
|
require File.dirname(__FILE__) + '/message_set'
|
21
21
|
require File.dirname(__FILE__) + '/status'
|
22
22
|
require File.dirname(__FILE__) + '/statements'
|
23
|
-
|
24
23
|
require File.dirname(__FILE__) + '/signon_message_set'
|
25
24
|
require File.dirname(__FILE__) + '/signup_message_set'
|
26
25
|
require File.dirname(__FILE__) + '/banking_message_set'
|
@@ -32,8 +31,9 @@ require File.dirname(__FILE__) + '/payment_message_set'
|
|
32
31
|
require File.dirname(__FILE__) + '/email_message_set'
|
33
32
|
require File.dirname(__FILE__) + '/investment_security_list_message_set'
|
34
33
|
require File.dirname(__FILE__) + '/financial_institution_profile_message_set'
|
35
|
-
|
36
34
|
require File.dirname(__FILE__) + '/parser'
|
35
|
+
require 'date'
|
36
|
+
require 'time'
|
37
37
|
|
38
38
|
module OFX
|
39
39
|
module OFX102
|
@@ -48,32 +48,35 @@ module OFX
|
|
48
48
|
body += message_set.to_ofx_102_s
|
49
49
|
end
|
50
50
|
body += "</OFX>\n"
|
51
|
-
|
52
|
-
# puts body
|
53
51
|
|
54
52
|
body
|
55
53
|
end
|
56
54
|
|
57
55
|
def from_http_response_body(body)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
body =
|
64
|
-
|
65
|
-
|
56
|
+
body = body.lstrip
|
57
|
+
|
58
|
+
end_of_header_index = body.index("<OFX>")
|
59
|
+
|
60
|
+
header_str = body[0...end_of_header_index].strip
|
61
|
+
body = body[end_of_header_index..-1]
|
62
|
+
|
63
|
+
if header_str.nil? || header_str == ""
|
64
|
+
raise NotImplementedError, "OFX server returned unmatched ASCII"
|
65
|
+
end
|
66
|
+
|
67
|
+
header = Header.from_ofx_102_s(header_str)
|
68
|
+
|
66
69
|
parser = OFX::OFX102::Parser.new
|
67
70
|
|
68
71
|
parser.scan_str body
|
69
|
-
|
72
|
+
|
70
73
|
if parser.documents.length > 1
|
71
74
|
raise NotImplementedError, "Multiple response documents"
|
72
75
|
end
|
73
|
-
|
76
|
+
|
74
77
|
# require 'pp'
|
75
78
|
# pp parser.ofx_hashes[0]
|
76
|
-
|
79
|
+
|
77
80
|
document = parser.documents[0]
|
78
81
|
document.header = header
|
79
82
|
document
|
@@ -82,15 +85,34 @@ module OFX
|
|
82
85
|
end
|
83
86
|
end
|
84
87
|
|
85
|
-
require 'date'
|
86
88
|
class Date
|
87
89
|
def to_ofx_102_s
|
88
90
|
strftime('%Y%m%d')
|
89
91
|
end
|
90
92
|
end
|
91
93
|
class DateTime
|
92
|
-
def
|
93
|
-
|
94
|
+
def to_time
|
95
|
+
Time.parse(self.to_s)
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_ofx_102_s_defunct
|
99
|
+
strftime('%Y%m%d%H%M%S.') + (sec_fraction * 86400000000).to_i.to_s + '[' + offset.numerator.to_s + ':' + strftime('%Z') + ']'
|
100
|
+
end
|
101
|
+
|
102
|
+
def to_ofx_102_s(extended=true)
|
103
|
+
s = strftime('%Y%m%d%H%M%S')
|
104
|
+
|
105
|
+
if extended
|
106
|
+
# some servers need exactly 3 decimal places for sec_fraction
|
107
|
+
s = s + '.000'
|
108
|
+
|
109
|
+
# use Time class to return TZ in correct format for OFX
|
110
|
+
# ie. ("EDT" vs. "-07:00")
|
111
|
+
tz = to_time.zone
|
112
|
+
s = s + '[0:' + tz + ']' if tz
|
113
|
+
end
|
114
|
+
|
115
|
+
return s
|
94
116
|
end
|
95
117
|
end
|
96
118
|
class String
|
@@ -113,7 +113,7 @@ module OFX
|
|
113
113
|
response.language = response_hash['LANGUAGE']
|
114
114
|
response.date_of_last_profile_update = response_hash['DTPROFUP'].to_datetime if response_hash['DTPROFUP']
|
115
115
|
response.date_of_last_account_update = response_hash['DTACCTUP'].to_datetime if response_hash['DTACCTUP']
|
116
|
-
response.financial_institution_identification = OFX::FinancialInstitutionIdentification.from_ofx_102_hash(response_hash['FI'])
|
116
|
+
response.financial_institution_identification = OFX::FinancialInstitutionIdentification.from_ofx_102_hash(response_hash['FI']) if response_hash['FI']
|
117
117
|
#TODO: @session_cookie
|
118
118
|
|
119
119
|
response
|
@@ -109,7 +109,14 @@ module OFX
|
|
109
109
|
def ofx_102_response_body
|
110
110
|
raise NotImplementedError
|
111
111
|
end
|
112
|
-
|
112
|
+
|
113
|
+
def account_identifier(account_id=nil)
|
114
|
+
account_id = 0 if not account_id
|
115
|
+
info = accounts[account_id].account_information if accounts and accounts.size > account_id
|
116
|
+
id = info.account.account_identifier if info
|
117
|
+
return id
|
118
|
+
end
|
119
|
+
|
113
120
|
def self.from_ofx_102_hash(transaction_hash)
|
114
121
|
response = AccountInformationResponse.new
|
115
122
|
|
@@ -117,7 +124,10 @@ module OFX
|
|
117
124
|
response.status = OFX::Status.from_ofx_102_hash(transaction_hash['STATUS'])
|
118
125
|
|
119
126
|
response_hash = transaction_hash['ACCTINFORS']
|
120
|
-
|
127
|
+
if not response_hash
|
128
|
+
return response
|
129
|
+
end
|
130
|
+
response.date_of_last_account_update = response_hash['DTACCTUP'].to_datetime if response_hash['DTACCTUP']
|
121
131
|
|
122
132
|
response.accounts = []
|
123
133
|
account_infos = response_hash['ACCTINFO'] if response_hash['ACCTINFO'].kind_of?(Array)
|
@@ -173,6 +183,19 @@ module OFX
|
|
173
183
|
when 'ACTIVE' then :active
|
174
184
|
else raise NotImplementedError
|
175
185
|
end
|
186
|
+
elsif account_info_hash['INVACCTINFO']
|
187
|
+
cc_acct_info_hash = account_info_hash['INVACCTINFO']
|
188
|
+
account_info.account_information = OFX::CreditCardAccountInformation.new
|
189
|
+
|
190
|
+
acct_from_hash = cc_acct_info_hash['INVACCTFROM']
|
191
|
+
account_info.account_information.account = OFX::CreditCardAccount.new
|
192
|
+
account_info.account_information.account.account_identifier = acct_from_hash['ACCTID']
|
193
|
+
account_info.account_information.status = case cc_acct_info_hash['SVCSTATUS']
|
194
|
+
when 'AVAIL' then :available
|
195
|
+
when 'PEND' then :pending
|
196
|
+
when 'ACTIVE' then :active
|
197
|
+
else raise NotImplementedError
|
198
|
+
end
|
176
199
|
else
|
177
200
|
raise NotImplementedError
|
178
201
|
end
|
@@ -183,4 +206,4 @@ module OFX
|
|
183
206
|
response
|
184
207
|
end
|
185
208
|
end
|
186
|
-
end
|
209
|
+
end
|
data/lib/ofx/financial_client.rb
CHANGED
@@ -42,11 +42,21 @@ module OFX
|
|
42
42
|
return nil
|
43
43
|
end
|
44
44
|
|
45
|
-
def application_identification
|
46
|
-
|
45
|
+
def application_identification(ofx_client_spoof=nil)
|
46
|
+
# Reference: http://wiki.mthbuilt.com/Tweaking_OFX_Connections
|
47
|
+
case ofx_client_spoof
|
48
|
+
when 'Money'
|
49
|
+
OFX::ApplicationIdentification.new('Money', '1600')
|
50
|
+
when 'Quicken'
|
51
|
+
OFX::ApplicationIdentification.new('QWIN', '1700')
|
52
|
+
when 'QuickBooks'
|
53
|
+
OFX::ApplicationIdentification.new('QBW', '1800')
|
54
|
+
else
|
55
|
+
OFX::ApplicationIdentification.new('OFX', '0010')
|
56
|
+
end
|
47
57
|
end
|
48
58
|
|
49
|
-
def create_signon_request_message(financial_institution_id)
|
59
|
+
def create_signon_request_message(financial_institution_id, ofx_client_id=nil)
|
50
60
|
signonMessageSet = OFX::SignonMessageSet.new
|
51
61
|
|
52
62
|
signonRequest = OFX::SignonRequest.new
|
@@ -56,7 +66,7 @@ module OFX
|
|
56
66
|
signonRequest.language = "ENG"
|
57
67
|
signonRequest.financial_institution_identification = self.financial_institution_identification_for(financial_institution_id)
|
58
68
|
signonRequest.session_cookie = nil
|
59
|
-
signonRequest.application_identification = self.application_identification
|
69
|
+
signonRequest.application_identification = self.application_identification(ofx_client_id)
|
60
70
|
signonRequest.client_unique_identifier = self.client_unique_identifier
|
61
71
|
signonMessageSet.requests << signonRequest
|
62
72
|
|
@@ -20,16 +20,44 @@ require 'uri'
|
|
20
20
|
module OFX
|
21
21
|
class FinancialInstitution
|
22
22
|
|
23
|
-
def self.get_institution(financial_institution_name)
|
23
|
+
def self.get_institution(financial_institution_name, ofx_client_id=nil, ssl_version=nil)
|
24
24
|
case financial_institution_name
|
25
25
|
when 'Capital One'
|
26
26
|
FinancialInstitution.new('Capital One',
|
27
|
-
URI.parse('https://onlinebanking.capitalone.com/
|
28
|
-
OFX::Version.new("1.0.2")
|
27
|
+
URI.parse('https://onlinebanking.capitalone.com/ofx/process.ofx'),
|
28
|
+
OFX::Version.new("1.0.2"),
|
29
|
+
'Hibernia', '1001', '065002030',
|
30
|
+
ofx_client_id, ssl_version)
|
29
31
|
when 'Citi'
|
30
32
|
FinancialInstitution.new('Citi',
|
31
|
-
URI.parse('https://
|
32
|
-
OFX::Version.new("1.0.2")
|
33
|
+
URI.parse('https://www.accountonline.com/cards/svc/CitiOfxManager.do'),
|
34
|
+
OFX::Version.new("1.0.2"),
|
35
|
+
'Citigroup', '24909', nil,
|
36
|
+
'Quicken', ssl_version)
|
37
|
+
when 'Chase'
|
38
|
+
FinancialInstitution.new('Chase',
|
39
|
+
URI.parse('https://ofx.chase.com'),
|
40
|
+
OFX::Version.new("1.0.3"),
|
41
|
+
'B1', '10898', nil,
|
42
|
+
'Quicken', :TLSv1)
|
43
|
+
when 'AMEX'
|
44
|
+
FinancialInstitution.new('AMEX',
|
45
|
+
URI.parse('https://online.americanexpress.com/myca/ofxdl/desktop/desktopDownload.do?request_type=nl_ofxdownload'),
|
46
|
+
OFX::Version.new("1.0.2"),
|
47
|
+
'AMEX', '3101', nil,
|
48
|
+
'Quicken', ssl_version)
|
49
|
+
when 'Schwab'
|
50
|
+
FinancialInstitution.new('Schwab',
|
51
|
+
URI.parse('https://ofx.schwab.com/bankcgi_dev/ofx_server'),
|
52
|
+
OFX::Version.new("1.0.2"),
|
53
|
+
'ISC', '101', '121202211',
|
54
|
+
'Quicken', ssl_version)
|
55
|
+
when 'Fidelity'
|
56
|
+
FinancialInstitution.new('Fidelity',
|
57
|
+
URI.parse('https://ofx.fidelity.com/ftgw/OFX/clients/download'),
|
58
|
+
OFX::Version.new("1.0.2"),
|
59
|
+
'fidelity.com', '7776', nil,
|
60
|
+
'Quicken', ssl_version)
|
33
61
|
else
|
34
62
|
raise NotImplementedError
|
35
63
|
end
|
@@ -38,11 +66,39 @@ module OFX
|
|
38
66
|
attr :name
|
39
67
|
attr :ofx_uri
|
40
68
|
attr :ofx_version
|
69
|
+
attr :organization_name
|
70
|
+
attr :organization_id
|
71
|
+
attr :bank_identifier
|
72
|
+
attr :client
|
41
73
|
|
42
|
-
def initialize(name, ofx_uri, ofx_version)
|
74
|
+
def initialize(name, ofx_uri, ofx_version, org_name, org_id, bank_id=nil, client_id=nil, ssl_version=nil)
|
43
75
|
@name = name
|
44
76
|
@ofx_uri = ofx_uri
|
45
77
|
@ofx_version = ofx_version
|
78
|
+
@organization_name = org_name
|
79
|
+
@organization_id = org_id
|
80
|
+
@bank_identifier = bank_id
|
81
|
+
@client = nil
|
82
|
+
@ofx_client_id = client_id
|
83
|
+
@ofx_ssl_version = ssl_version
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_client(user_name, password, client_uid=nil)
|
87
|
+
inst_id = OFX::FinancialInstitutionIdentification.new(
|
88
|
+
@organization_name, @organization_id)
|
89
|
+
user_cred = OFX::UserCredentials.new(user_name, password)
|
90
|
+
@client = OFX::FinancialClient.new([[inst_id, user_cred]])
|
91
|
+
# caller can generate one-time with: SecureRandom.hex(16)
|
92
|
+
# see: http://wiki.gnucash.org/wiki/Setting_up_OFXDirectConnect_in_GnuCash_2#Chase_.22username_or_password_are_incorrect.22
|
93
|
+
@client.client_unique_identifier = client_uid
|
94
|
+
@client
|
95
|
+
end
|
96
|
+
|
97
|
+
# anonymous can be used for ProfileRequest
|
98
|
+
def set_client_anon
|
99
|
+
user_name = "anonymous00000000000000000000000"
|
100
|
+
password = "anonymous00000000000000000000000"
|
101
|
+
set_client(user_name, password)
|
46
102
|
end
|
47
103
|
|
48
104
|
def create_request_document()
|
@@ -62,26 +118,173 @@ module OFX
|
|
62
118
|
end
|
63
119
|
|
64
120
|
document.header.security = "NONE"
|
65
|
-
|
66
121
|
document.header.content_encoding = "USASCII"
|
67
122
|
document.header.content_character_set = "1252"
|
68
|
-
|
69
123
|
document.header.compression = "NONE"
|
70
|
-
|
71
124
|
document.header.previous_unique_identifier = "NONE"
|
72
125
|
document.header.unique_identifier = OFX::FileUniqueIdentifier.new
|
73
126
|
|
74
127
|
document
|
75
128
|
end
|
76
129
|
|
130
|
+
def create_request_document_signon
|
131
|
+
return nil if @client.nil?
|
132
|
+
requestDocument = self.create_request_document
|
133
|
+
requestDocument.message_sets << @client.create_signon_request_message(@organization_id, @ofx_client_id)
|
134
|
+
return requestDocument
|
135
|
+
end
|
136
|
+
|
137
|
+
def create_request_document_profile_update(request_date=nil)
|
138
|
+
return nil if @client.nil?
|
139
|
+
if request_date.nil?
|
140
|
+
request_date = DateTime.new(2001, 1, 1)
|
141
|
+
end
|
142
|
+
profileMessageSet = OFX::FinancialInstitutionProfileMessageSet.new
|
143
|
+
profileRequest = OFX::FinancialInstitutionProfileRequest.new
|
144
|
+
profileRequest.transaction_identifier = OFX::TransactionUniqueIdentifier.new
|
145
|
+
profileRequest.client_routing = 'MSGSET'
|
146
|
+
profileRequest.date_of_last_profile_update = request_date
|
147
|
+
profileMessageSet.requests << profileRequest
|
148
|
+
|
149
|
+
requestDocument = self.create_request_document
|
150
|
+
requestDocument.message_sets << @client.create_signon_request_message(@organization_id, @ofx_client_id)
|
151
|
+
requestDocument.message_sets << profileMessageSet
|
152
|
+
return requestDocument
|
153
|
+
end
|
154
|
+
|
155
|
+
def create_request_document_signup(request_date=nil)
|
156
|
+
return nil if @client.nil?
|
157
|
+
if request_date.nil?
|
158
|
+
request_date = DateTime.new(2001, 1, 1)
|
159
|
+
end
|
160
|
+
signup_message_set = OFX::SignupMessageSet.new
|
161
|
+
account_info_request = OFX::AccountInformationRequest.new
|
162
|
+
account_info_request.transaction_identifier = OFX::TransactionUniqueIdentifier.new
|
163
|
+
account_info_request.date_of_last_account_update = request_date
|
164
|
+
signup_message_set.requests << account_info_request
|
165
|
+
|
166
|
+
requestDocument = self.create_request_document
|
167
|
+
requestDocument.message_sets << @client.create_signon_request_message(@organization_id, @ofx_client_id)
|
168
|
+
requestDocument.message_sets << signup_message_set
|
169
|
+
return requestDocument
|
170
|
+
end
|
171
|
+
|
172
|
+
def create_request_document_for_cc_statement(account_id, date_range=nil, include_trans=true)
|
173
|
+
return nil if @client.nil?
|
174
|
+
cc_message_set = OFX::CreditCardStatementMessageSet.new
|
175
|
+
statement_request = OFX::CreditCardStatementRequest.new
|
176
|
+
statement_request.transaction_identifier = OFX::TransactionUniqueIdentifier.new
|
177
|
+
statement_request.account = OFX::CreditCardAccount.new
|
178
|
+
statement_request.account.account_identifier = account_id
|
179
|
+
if include_trans
|
180
|
+
statement_request.included_range = date_range
|
181
|
+
statement_request.include_transactions = include_trans
|
182
|
+
end
|
183
|
+
cc_message_set.requests << statement_request
|
184
|
+
|
185
|
+
requestDocument = self.create_request_document
|
186
|
+
requestDocument.message_sets << @client.create_signon_request_message(@organization_id, @ofx_client_id)
|
187
|
+
requestDocument.message_sets << cc_message_set
|
188
|
+
return requestDocument
|
189
|
+
end
|
190
|
+
|
191
|
+
def create_request_document_for_cc_closing_statement(account_id)
|
192
|
+
create_request_document_for_cc_statement(account_id, nil, false)
|
193
|
+
end
|
194
|
+
|
195
|
+
def create_request_document_for_bank_statement(account_id, date_range=nil, account_type = :checking)
|
196
|
+
return nil if @client.nil?
|
197
|
+
banking_message_set = OFX::BankingMessageSet.new
|
198
|
+
statement_request = OFX::BankingStatementRequest.new
|
199
|
+
statement_request.transaction_identifier = OFX::TransactionUniqueIdentifier.new
|
200
|
+
statement_request.account = OFX::BankingAccount.new
|
201
|
+
statement_request.account.bank_identifier = @bank_identifier
|
202
|
+
statement_request.account.branch_identifier = nil
|
203
|
+
statement_request.account.account_identifier = account_id
|
204
|
+
statement_request.account.account_type = account_type
|
205
|
+
statement_request.account.account_key = nil
|
206
|
+
statement_request.include_transactions = true if date_range
|
207
|
+
statement_request.included_range = date_range # example DateRange (start.to_date)..(end.to_date)
|
208
|
+
banking_message_set.requests << statement_request
|
209
|
+
|
210
|
+
requestDocument = self.create_request_document
|
211
|
+
requestDocument.message_sets << @client.create_signon_request_message(@organization_id, @ofx_client_id)
|
212
|
+
requestDocument.message_sets << banking_message_set
|
213
|
+
return requestDocument
|
214
|
+
end
|
215
|
+
|
216
|
+
def create_request_document_for_inv_statement(account_id, date_range=nil)
|
217
|
+
return nil if @client.nil?
|
218
|
+
inv_message_set = OFX::InvestmentStatementMessageSet.new
|
219
|
+
statement_request = OFX::BankingStatementRequest.new
|
220
|
+
statement_request.transaction_identifier = OFX::TransactionUniqueIdentifier.new
|
221
|
+
statement_request.account = OFX::BankingAccount.new
|
222
|
+
statement_request.account.bank_identifier = @bank_identifier
|
223
|
+
statement_request.account.branch_identifier = nil
|
224
|
+
statement_request.account.account_identifier = account_id
|
225
|
+
statement_request.account.account_type = :money_market
|
226
|
+
statement_request.account.account_key = nil
|
227
|
+
statement_request.include_transactions = true if date_range
|
228
|
+
statement_request.included_range = date_range
|
229
|
+
inv_message_set.requests << statement_request
|
230
|
+
|
231
|
+
requestDocument = self.create_request_document
|
232
|
+
requestDocument.message_sets << @client.create_signon_request_message(@organization_id, @ofx_client_id)
|
233
|
+
requestDocument.message_sets << inv_message_set
|
234
|
+
return requestDocument
|
235
|
+
end
|
236
|
+
|
237
|
+
def get_account_id(account_id=nil)
|
238
|
+
req = create_request_document_signup
|
239
|
+
return nil if req.nil?
|
240
|
+
resp = send(req)
|
241
|
+
id = resp.message_sets[1].responses[0].account_identifier(account_id) if resp
|
242
|
+
return id
|
243
|
+
end
|
244
|
+
|
77
245
|
def send(document)
|
78
246
|
serializer = OFX::Serializer.get(@ofx_version)
|
79
247
|
request_body = serializer.to_http_post_body(document)
|
80
248
|
|
81
249
|
client = OFX::HTTPClient.new(@ofx_uri)
|
82
|
-
response_body = client.send(request_body)
|
250
|
+
response_body = client.send(request_body, @ofx_ssl_version)
|
83
251
|
|
84
252
|
return serializer.from_http_response_body(response_body)
|
85
253
|
end
|
254
|
+
|
255
|
+
##
|
256
|
+
## Debugging routines
|
257
|
+
##
|
258
|
+
def get_anon_profile
|
259
|
+
set_client_anon
|
260
|
+
return send(create_request_document_profile_update)
|
261
|
+
end
|
262
|
+
|
263
|
+
def document_to_post_data(document, no_whitespace=false)
|
264
|
+
serializer = OFX::Serializer.get(@ofx_version)
|
265
|
+
request_body = serializer.to_http_post_body(document)
|
266
|
+
if no_whitespace
|
267
|
+
request_body.delete! " "
|
268
|
+
end
|
269
|
+
return request_body
|
270
|
+
end
|
271
|
+
|
272
|
+
def test_send(data, post_data=nil, serial_resp=true, debug_req=false, debug_resp=false)
|
273
|
+
serializer = OFX::Serializer.get(@ofx_version)
|
274
|
+
if post_data
|
275
|
+
request_body = data
|
276
|
+
else
|
277
|
+
request_body = serializer.to_http_post_body(data)
|
278
|
+
end
|
279
|
+
|
280
|
+
client = OFX::HTTPClient.new(@ofx_uri)
|
281
|
+
response_body = client.send(request_body, @ofx_ssl_version, debug_req, debug_resp)
|
282
|
+
|
283
|
+
if serial_resp
|
284
|
+
return serializer.from_http_response_body(response_body)
|
285
|
+
else
|
286
|
+
return response_body
|
287
|
+
end
|
288
|
+
end
|
86
289
|
end
|
87
290
|
end
|