ofx_for_ruby 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. data/.externalToolBuilders/Rebuild parsers.launch +10 -0
  2. data/.gitignore +9 -0
  3. data/.loadpath +5 -0
  4. data/.project +27 -0
  5. data/COPYING +674 -0
  6. data/Gemfile +2 -0
  7. data/README +47 -0
  8. data/RELEASE_NOTES +45 -0
  9. data/Rakefile +63 -0
  10. data/lib/ofx.rb +60 -0
  11. data/lib/ofx/1.0.2/banking_message_set.rb +261 -0
  12. data/lib/ofx/1.0.2/credit_card_statement_message_set.rb +269 -0
  13. data/lib/ofx/1.0.2/document.rb +34 -0
  14. data/lib/ofx/1.0.2/email_message_set.rb +34 -0
  15. data/lib/ofx/1.0.2/financial_institution_profile_message_set.rb +155 -0
  16. data/lib/ofx/1.0.2/header.rb +48 -0
  17. data/lib/ofx/1.0.2/interbank_funds_transfer_message_set.rb +30 -0
  18. data/lib/ofx/1.0.2/investment_security_list_message_set.rb +30 -0
  19. data/lib/ofx/1.0.2/investment_statement_message_set.rb +30 -0
  20. data/lib/ofx/1.0.2/lexer.rb +135 -0
  21. data/lib/ofx/1.0.2/message_set.rb +165 -0
  22. data/lib/ofx/1.0.2/ofx_102.racc +90 -0
  23. data/lib/ofx/1.0.2/ofx_102.rex +65 -0
  24. data/lib/ofx/1.0.2/parser.rb +215 -0
  25. data/lib/ofx/1.0.2/payment_message_set.rb +30 -0
  26. data/lib/ofx/1.0.2/serializer.rb +114 -0
  27. data/lib/ofx/1.0.2/signon_message_set.rb +121 -0
  28. data/lib/ofx/1.0.2/signup_message_set.rb +186 -0
  29. data/lib/ofx/1.0.2/statements.rb +54 -0
  30. data/lib/ofx/1.0.2/status.rb +30 -0
  31. data/lib/ofx/1.0.2/wire_funds_transfer_message_set.rb +30 -0
  32. data/lib/ofx/banking_message_set.rb +125 -0
  33. data/lib/ofx/credit_card_statement_message_set.rb +104 -0
  34. data/lib/ofx/document.rb +30 -0
  35. data/lib/ofx/email_message_set.rb +42 -0
  36. data/lib/ofx/file_unique_identifier.rb +28 -0
  37. data/lib/ofx/financial_client.rb +76 -0
  38. data/lib/ofx/financial_institution.rb +83 -0
  39. data/lib/ofx/financial_institution_profile_message_set.rb +67 -0
  40. data/lib/ofx/header.rb +115 -0
  41. data/lib/ofx/http/cacert.pem +3509 -0
  42. data/lib/ofx/http/ofx_http_client.rb +84 -0
  43. data/lib/ofx/interbank_funds_transfer_message_set.rb +33 -0
  44. data/lib/ofx/investment_security_list_message_set.rb +33 -0
  45. data/lib/ofx/investment_statement_message_set.rb +33 -0
  46. data/lib/ofx/message_set.rb +78 -0
  47. data/lib/ofx/payment_message_set.rb +33 -0
  48. data/lib/ofx/serializer.rb +39 -0
  49. data/lib/ofx/signon_message_set.rb +141 -0
  50. data/lib/ofx/signup_message_set.rb +84 -0
  51. data/lib/ofx/statements.rb +49 -0
  52. data/lib/ofx/status.rb +100 -0
  53. data/lib/ofx/transaction_unique_identifier.rb +28 -0
  54. data/lib/ofx/version.rb +82 -0
  55. data/lib/ofx/wire_funds_transfer_message_set.rb +33 -0
  56. data/ofx_for_ruby.gemspec +25 -0
  57. data/planning/OFX Specification completion.ods +0 -0
  58. data/test/capital_one/capital_one_helper.rb +77 -0
  59. data/test/capital_one/fixtures/README +10 -0
  60. data/test/capital_one/fixtures/fipid-5599.xml +98 -0
  61. data/test/capital_one/test_banking_statement.rb +108 -0
  62. data/test/capital_one/test_financial_institution_profile.rb +295 -0
  63. data/test/capital_one/test_ofx_http_client.rb +72 -0
  64. data/test/capital_one/test_signup_account_information.rb +100 -0
  65. data/test/citi/citi_helper.rb +77 -0
  66. data/test/citi/fixtures/README +9 -0
  67. data/test/citi/fixtures/fipid-6642.xml +98 -0
  68. data/test/citi/test_credit_card_statement.rb +159 -0
  69. data/test/citi/test_financial_institution_profile.rb +231 -0
  70. data/test/citi/test_ofx_http_client.rb +71 -0
  71. data/test/citi/test_signup_account_information.rb +85 -0
  72. data/test/test_helper.rb +4 -0
  73. data/test/test_ofx_version.rb +141 -0
  74. metadata +210 -0
@@ -0,0 +1,65 @@
1
+ # Copyright © 2007 Chris Guidry <chrisguidry@gmail.com>
2
+ #
3
+ # This file is part of OFX for Ruby.
4
+ #
5
+ # OFX for Ruby is free software; you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation; either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # OFX for Ruby is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ module OFX
19
+ module OFX102
20
+ class Parser
21
+ option
22
+
23
+ macro
24
+ TAG_IN \<
25
+ TAG_OUT \>
26
+ ETAG_IN \<\/
27
+
28
+ rule
29
+
30
+ # [:state] pattern [actions]
31
+ {ETAG_IN} { state = :TAG; [:etag_in, text] }
32
+ {TAG_IN} { state = :TAG; [:tag_in, text] }
33
+
34
+ :TAG {TAG_OUT} { state = nil; [:tag_out, text] }
35
+
36
+ :TAG [\w\-\.]+ { [:element, text] }
37
+
38
+
39
+ \s+(?=\S)
40
+ .*?(?=({TAG_IN}|{ETAG_IN})+?) { [:text, text] }
41
+ .*\S(?=\s*$) { [:text, text] }
42
+ \s+(?=$)
43
+
44
+ inner
45
+
46
+ end
47
+
48
+ end # module OFX102
49
+ end # module OFX
50
+
51
+ =begin
52
+ if __FILE__ == $0
53
+ exit if ARGV.size != 1
54
+ filename = ARGV.shift
55
+ rex = OFX::OFX102::Parser.new
56
+ begin
57
+ rex.load_file filename
58
+ while token = rex.next_token
59
+ p token
60
+ end
61
+ rescue
62
+ $stderr.printf "%s:%d:%s\n", rex.filename, rex.lineno, $!.message
63
+ end
64
+ end
65
+ =end
@@ -0,0 +1,215 @@
1
+ #
2
+ # DO NOT MODIFY!!!!
3
+ # This file is automatically generated by Racc 1.4.6
4
+ # from Racc grammer file "".
5
+ #
6
+
7
+ require 'racc/parser.rb'
8
+
9
+ #
10
+ # generated by racc
11
+ #
12
+ require File.dirname(__FILE__) + '/lexer'
13
+
14
+ module OFX
15
+ module OFX102
16
+ class Parser < Racc::Parser
17
+
18
+ module_eval(<<'...end ofx_102.racc/module_eval...', 'ofx_102.racc', 48)
19
+
20
+ attr :documents
21
+ attr :ofx_hashes
22
+
23
+ def initialize
24
+ @documents = []
25
+ @ofx_hashes = []
26
+
27
+ @tags_stack = []
28
+ end
29
+
30
+ private
31
+ def start_tag(tag_name)
32
+ # push an empty aggregate/value tag
33
+ @tags_stack.push({ tag_name => {} })
34
+ end
35
+
36
+ def data_value(value)
37
+ # pop the most recent tag, and use it's name as the key in the next higher tag's hash
38
+ tag = @tags_stack.pop
39
+ @tags_stack.last[@tags_stack.last.keys[0]][tag.keys[0]] = CGI::unescapeHTML(value)
40
+ end
41
+
42
+ def end_tag(tag_name)
43
+ # pop the most recent tag, stop and recognize this as a full document if this is the last tag; otherwise,
44
+ # use this tag's naem as the key in the next higher tag's hash
45
+ tag = @tags_stack.pop
46
+ if (@tags_stack.empty?)
47
+ @ofx_hashes << tag
48
+ @documents << OFX::Document.from_ofx_102_hash(tag)
49
+ else
50
+ previous = @tags_stack.last[@tags_stack.last.keys[0]][tag.keys[0]]
51
+ if previous == nil
52
+ @tags_stack.last[@tags_stack.last.keys[0]][tag.keys[0]] = tag[tag.keys[0]]
53
+ elsif previous.kind_of?(Array)
54
+ previous << tag[tag.keys[0]]
55
+ else
56
+ @tags_stack.last[@tags_stack.last.keys[0]][tag.keys[0]] = [previous, tag[tag.keys[0]]]
57
+ end
58
+ end
59
+ end
60
+
61
+ ...end ofx_102.racc/module_eval...
62
+ ##### State transition tables begin ###
63
+
64
+ racc_action_table = [
65
+ 1, 6, 1, 14, 15, 14, 15, 8, 9, 5,
66
+ 17, 19, 20, 1 ]
67
+
68
+ racc_action_check = [
69
+ 18, 2, 7, 18, 18, 7, 7, 5, 6, 1,
70
+ 14, 16, 17, 0 ]
71
+
72
+ racc_action_pointer = [
73
+ 11, 6, 1, nil, nil, 3, 8, 0, nil, nil,
74
+ nil, nil, nil, nil, 7, nil, 5, 8, -2, nil,
75
+ nil, nil ]
76
+
77
+ racc_action_default = [
78
+ -1, -13, -13, -2, -6, -13, -13, -13, -4, 22,
79
+ -3, -7, -9, -10, -13, -8, -6, -13, -13, -12,
80
+ -5, -11 ]
81
+
82
+ racc_goto_table = [
83
+ 7, 4, 10, 3, 2, nil, nil, nil, nil, nil,
84
+ nil, nil, 18, 21 ]
85
+
86
+ racc_goto_check = [
87
+ 4, 3, 5, 2, 1, nil, nil, nil, nil, nil,
88
+ nil, nil, 4, 5 ]
89
+
90
+ racc_goto_pointer = [
91
+ nil, 4, 3, 1, -4, -5, nil, nil, nil ]
92
+
93
+ racc_goto_default = [
94
+ nil, nil, nil, 16, nil, nil, 11, 12, 13 ]
95
+
96
+ racc_reduce_table = [
97
+ 0, 0, :racc_error,
98
+ 0, 8, :_reduce_none,
99
+ 1, 8, :_reduce_none,
100
+ 3, 9, :_reduce_none,
101
+ 3, 10, :_reduce_4,
102
+ 3, 12, :_reduce_5,
103
+ 0, 11, :_reduce_none,
104
+ 2, 11, :_reduce_none,
105
+ 1, 13, :_reduce_none,
106
+ 1, 13, :_reduce_none,
107
+ 1, 13, :_reduce_none,
108
+ 3, 14, :_reduce_none,
109
+ 2, 15, :_reduce_12 ]
110
+
111
+ racc_reduce_n = 13
112
+
113
+ racc_shift_n = 22
114
+
115
+ racc_token_table = {
116
+ false => 0,
117
+ :error => 1,
118
+ :tag_in => 2,
119
+ :element => 3,
120
+ :tag_out => 4,
121
+ :etag_in => 5,
122
+ :text => 6 }
123
+
124
+ racc_nt_base = 7
125
+
126
+ racc_use_result_var = true
127
+
128
+ Racc_arg = [
129
+ racc_action_table,
130
+ racc_action_check,
131
+ racc_action_default,
132
+ racc_action_pointer,
133
+ racc_goto_table,
134
+ racc_goto_check,
135
+ racc_goto_default,
136
+ racc_goto_pointer,
137
+ racc_nt_base,
138
+ racc_reduce_table,
139
+ racc_token_table,
140
+ racc_shift_n,
141
+ racc_reduce_n,
142
+ racc_use_result_var ]
143
+
144
+ Racc_token_to_s_table = [
145
+ "$end",
146
+ "error",
147
+ "tag_in",
148
+ "element",
149
+ "tag_out",
150
+ "etag_in",
151
+ "text",
152
+ "$start",
153
+ "target",
154
+ "ofx_body",
155
+ "tag_from",
156
+ "contents",
157
+ "tag_to",
158
+ "content",
159
+ "aggregate",
160
+ "value" ]
161
+
162
+ Racc_debug_parser = false
163
+
164
+ ##### State transition tables end #####
165
+
166
+ # reduce 0 omitted
167
+
168
+ # reduce 1 omitted
169
+
170
+ # reduce 2 omitted
171
+
172
+ # reduce 3 omitted
173
+
174
+ module_eval(<<'.,.,', 'ofx_102.racc', 25)
175
+ def _reduce_4(val, _values, result)
176
+ start_tag(val[1])
177
+ result
178
+ end
179
+ .,.,
180
+
181
+ module_eval(<<'.,.,', 'ofx_102.racc', 26)
182
+ def _reduce_5(val, _values, result)
183
+ end_tag(val[1])
184
+ result
185
+ end
186
+ .,.,
187
+
188
+ # reduce 6 omitted
189
+
190
+ # reduce 7 omitted
191
+
192
+ # reduce 8 omitted
193
+
194
+ # reduce 9 omitted
195
+
196
+ # reduce 10 omitted
197
+
198
+ # reduce 11 omitted
199
+
200
+ module_eval(<<'.,.,', 'ofx_102.racc', 36)
201
+ def _reduce_12(val, _values, result)
202
+ data_value(val[1])
203
+ result
204
+ end
205
+ .,.,
206
+
207
+ def _reduce_none(val, _values, result)
208
+ val[0]
209
+ end
210
+
211
+ end # class Parser
212
+ end # module OFX102
213
+ end # module OFX
214
+
215
+
@@ -0,0 +1,30 @@
1
+ # Copyright © 2007 Chris Guidry <chrisguidry@gmail.com>
2
+ #
3
+ # This file is part of OFX for Ruby.
4
+ #
5
+ # OFX for Ruby is free software; you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation; either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # OFX for Ruby is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ module OFX
19
+ class PaymentMessageSet < MessageSet
20
+ end
21
+
22
+ class PaymentMessageSetProfile < MessageSetProfile
23
+ def self.from_ofx_102_hash(message_set_description_hash)
24
+ profile = OFX::PaymentMessageSetProfile.new
25
+ profile.message_set_class = OFX::PaymentMessageSet
26
+ profile.from_ofx_102_hash(message_set_description_hash)
27
+ profile
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,114 @@
1
+ # Copyright © 2007 Chris Guidry <chrisguidry@gmail.com>
2
+ #
3
+ # This file is part of OFX for Ruby.
4
+ #
5
+ # OFX for Ruby is free software; you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation; either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # OFX for Ruby is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require File.dirname(__FILE__) + '/document'
19
+ require File.dirname(__FILE__) + '/header'
20
+ require File.dirname(__FILE__) + '/message_set'
21
+ require File.dirname(__FILE__) + '/status'
22
+ require File.dirname(__FILE__) + '/statements'
23
+
24
+ require File.dirname(__FILE__) + '/signon_message_set'
25
+ require File.dirname(__FILE__) + '/signup_message_set'
26
+ require File.dirname(__FILE__) + '/banking_message_set'
27
+ require File.dirname(__FILE__) + '/credit_card_statement_message_set'
28
+ require File.dirname(__FILE__) + '/investment_statement_message_set'
29
+ require File.dirname(__FILE__) + '/interbank_funds_transfer_message_set'
30
+ require File.dirname(__FILE__) + '/wire_funds_transfer_message_set'
31
+ require File.dirname(__FILE__) + '/payment_message_set'
32
+ require File.dirname(__FILE__) + '/email_message_set'
33
+ require File.dirname(__FILE__) + '/investment_security_list_message_set'
34
+ require File.dirname(__FILE__) + '/financial_institution_profile_message_set'
35
+
36
+ require File.dirname(__FILE__) + '/parser'
37
+
38
+ module OFX
39
+ module OFX102
40
+ class Serializer
41
+ def to_http_post_body(document)
42
+ body = ""
43
+
44
+ body += document.header.to_ofx_102_s
45
+ body += "\n"
46
+ body += "<OFX>\n"
47
+ document.message_sets.each do |message_set|
48
+ body += message_set.to_ofx_102_s
49
+ end
50
+ body += "</OFX>\n"
51
+
52
+ #print body
53
+
54
+ body
55
+ end
56
+
57
+ def from_http_response_body(body)
58
+ header_pattern = /(\w+\:.*\n)+/
59
+ header_match = header_pattern.match(body)
60
+
61
+ body = header_match.post_match
62
+ header = Header.from_ofx_102_s(header_match[0].strip)
63
+
64
+ parser = OFX::OFX102::Parser.new
65
+ parser.scan_str body
66
+
67
+ if parser.documents.length > 1
68
+ raise NotImplementedError, "Multiple response documents"
69
+ end
70
+
71
+ #require 'pp'
72
+ #print body
73
+ #pp parser.ofx_hashes[0]
74
+
75
+ document = parser.documents[0]
76
+ document.header = header
77
+ document
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ require 'date'
84
+ class Date
85
+ def to_ofx_102_s
86
+ strftime('%Y%m%d')
87
+ end
88
+ end
89
+ class DateTime
90
+ def to_ofx_102_s
91
+ strftime('%Y%m%d%H%M%S')
92
+ end
93
+ end
94
+ class String
95
+ def to_date
96
+ Date.parse(self)
97
+ end
98
+ def to_datetime
99
+ DateTime.parse(self)
100
+ end
101
+ def to_time
102
+ ('-47120101' + self).to_datetime
103
+ end
104
+ end
105
+ class TrueClass
106
+ def to_ofx_102_s
107
+ 'Y'
108
+ end
109
+ end
110
+ class FalseClass
111
+ def to_ofx_102_s
112
+ 'Y'
113
+ end
114
+ end
@@ -0,0 +1,121 @@
1
+ # Copyright © 2007 Chris Guidry <chrisguidry@gmail.com>
2
+ #
3
+ # This file is part of OFX for Ruby.
4
+ #
5
+ # OFX for Ruby is free software; you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation; either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # OFX for Ruby is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ module OFX
19
+ class SignonMessageSet < MessageSet
20
+ def ofx_102_message_set_name
21
+ 'SIGNON'
22
+ end
23
+
24
+ def self.from_ofx_102_hash(message_set_hash)
25
+ message_set = SignonMessageSet.new
26
+
27
+ message_set_hash.each_pair() do |response_or_request_name, response_or_request_hash|
28
+ case response_or_request_name
29
+ when "SONRQ" then message_set.requests << SignonRequest.from_ofx_102_hash(response_or_request_hash)
30
+ when "SONRS" then message_set.responses << SignonResponse.from_ofx_102_hash(response_or_request_hash)
31
+ else raise NotImplementedError, response_or_request_name
32
+ end
33
+ end
34
+
35
+ return message_set
36
+ end
37
+ end
38
+
39
+ class SignonMessageSetProfile < MessageSetProfile
40
+ def self.from_ofx_102_hash(message_set_description_hash)
41
+ profile = OFX::SignonMessageSetProfile.new
42
+ profile.message_set_class = OFX::SignonMessageSet
43
+ profile.from_ofx_102_hash(message_set_description_hash)
44
+ profile
45
+ end
46
+ end
47
+
48
+ class SignonRequest < Request
49
+ def ofx_102_name
50
+ 'SON'
51
+ end
52
+ def ofx_102_request_body
53
+ " <DTCLIENT>#{date.to_ofx_102_s}\n" +
54
+ user_identification.to_ofx_102_s + "\n" +
55
+ (" <GENUSERKEY>#{generate_user_key}\n" if generate_user_key).to_s +
56
+ " <LANGUAGE>#{language}\n" +
57
+ financial_institution_identification.to_ofx_102_s + "\n" +
58
+ (" <SESSCOOKIE>#{session_cookie}\n" if session_cookie).to_s +
59
+ application_identification.to_ofx_102_s
60
+ end
61
+ def self.from_ofx_102_hash(request_hash)
62
+ raise NotImplementedError
63
+ end
64
+ end
65
+ class UserCredentials
66
+ def to_ofx_102_s
67
+ " <USERID>#{user_identification}\n" +
68
+ " <USERPASS>#{password}"
69
+ end
70
+ end
71
+ class UserKey
72
+ def to_ofx_102_s
73
+ " <USERKEY>#{user_key}"
74
+ end
75
+ end
76
+
77
+ class FinancialInstitutionIdentification
78
+ def to_ofx_102_s
79
+ " <FI>\n" +
80
+ " <ORG>#{organization}\n" +
81
+ " <FID>#{financial_institution_identifier}\n" +
82
+ " </FI>"
83
+ end
84
+ def self.from_ofx_102_hash(fi_hash)
85
+ return FinancialInstitutionIdentification.new(fi_hash['ORG'],
86
+ fi_hash['FID'])
87
+ end
88
+ end
89
+
90
+ class ApplicationIdentification
91
+ def to_ofx_102_s
92
+ " <APPID>#{application_identification}\n" +
93
+ " <APPVER>#{application_version}"
94
+ end
95
+ end
96
+
97
+ class SignonResponse < Response
98
+ def ofx_102_name
99
+ 'SON'
100
+ end
101
+
102
+ def ofx_102_response_body
103
+ raise NotImplementedError
104
+ end
105
+
106
+ def self.from_ofx_102_hash(response_hash)
107
+ response = SignonResponse.new
108
+
109
+ response.status = OFX::Status.from_ofx_102_hash(response_hash['STATUS'])
110
+ response.date = response_hash['DTSERVER'].to_datetime if response_hash['DTSERVER']
111
+ #TODO: @user_key
112
+ response.language = response_hash['LANGUAGE']
113
+ response.date_of_last_profile_update = response_hash['DTPROFUP'].to_datetime if response_hash['DTPROFUP']
114
+ response.date_of_last_account_update = response_hash['DTACCTUP'].to_datetime if response_hash['DTACCTUP']
115
+ response.financial_institution_identification = OFX::FinancialInstitutionIdentification.from_ofx_102_hash(response_hash['FI'])
116
+ #TODO: @session_cookie
117
+
118
+ response
119
+ end
120
+ end
121
+ end