ofx_for_ruby 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/README ADDED
@@ -0,0 +1,47 @@
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/ofxget/. That repository has not been updated since early 2008.
2
+
3
+ ---------------------------------------
4
+
5
+ OFX for Ruby is a pure Ruby implementation of Open Financial Exchange specifications (1.0.2 through 2.1.1) for building both financial
6
+ clients and servers, providing parsers/serializers for each version, and a uniform object model across all versions.
7
+
8
+ Copyright © 2007 Chris Guidry <chrisguidry@gmail.com>.
9
+ OFX for Ruby is distributed under the GNU Public License, version 3. See COPYING for more information.
10
+
11
+
12
+ Discovering Financial Institution connection information:
13
+ There is currently no discovery mechanism within OFX for Ruby for acquiring financial institution connection information from any partner servers (although this is planned in the future). Finding out this information is currently a manual process, but is greatly facilitated with either the ofxconnect utility from libofx <http://libofx.sourceforge.net/> or the information available at <http://wiki.gnucash.org/wiki/OFX_Direct_Connect_Bank_Settings>.
14
+
15
+
16
+ Running Unit Tests:
17
+ There are unit tests in the test directory, and I intend to keep these correct and functional to the best of my abilities. I am, however, limited in the coverage I can generate, since I am currently testing live against my own personal bank and credit card accounts. For the near future, the unit test suite will only cover the read-only aspects of communicating over OFX. So as of now, the unit tests available are a little specific to Capital One and CitiCards accounts.
18
+
19
+ If you have an account with one of these institutions, great! Please help me test more scenarios with these banks.
20
+ If you have an account with any other institution and can extend OFX for Ruby and it's unit test suite, greater!!!
21
+
22
+ In either case, please take care not to leave bank account numbers, and especially user credentials in the source tree when committing. I'm going to try to get a more organized test suite together in the future as we add more financial institutions and automatic discovery to the mix.
23
+
24
+
25
+ OFX Specification Completeness:
26
+ For the current level of OFX support, please see the "OFX Specification completion.ods" file in the planning directory of the release. This file will also be updated periodically on the RubyForge documentation section of the OFX for Ruby site.
27
+
28
+
29
+ Source Code:
30
+ Thanks to RubyForge.org for hosting the code for OFX.
31
+
32
+ This release can be found in the Subversion repository at <svn://rubyforge.org/var/svn/ofx/tags/ofx-0.1.0>.
33
+ The latest version can be found at <svn://rubyforge.org/var/svn/ofx/trunk>.
34
+
35
+ Distribution Contents:
36
+ documentation - (one day) this will house all of the documentation for OFX for Ruby
37
+ lib - the source code for the OFX for Ruby library; (Hint: you can use this folder in your vendor folder in a Rails application)
38
+ lib/ofx.rb - the primary include file for the library
39
+ planning - files that I use to keep on track
40
+ test - the almighty unit test suite (see "Running Unit Tests", above)
41
+ COPYING - the GPLv3
42
+ README - this file
43
+ RELEASE_NOTES - a play-by-play of each OFX for Ruby release (Hint: the 0.1.0 RELEASE_NOTES are the same as this README)
44
+
45
+
46
+ Mailing Lists:
47
+ ofx-commits@rubyforge.org - notifications of each commit to the OFX tree
data/RELEASE_NOTES ADDED
@@ -0,0 +1,45 @@
1
+ OFX for Ruby
2
+ currently at version 0.1.0
3
+
4
+ ==============================================================================
5
+
6
+ version 0.1.0
7
+ 2007.07.15
8
+ first public release
9
+ <svn://rubyforge.org/var/svn/ofx/tags/ofx-0.1.0>
10
+ Chris Guidry <chrisguidry@gmail.com>
11
+
12
+ Discovering Financial Institution connection information:
13
+ There is currently no discovery mechanism within OFX for Ruby for acquiring financial institution connection information from any partner servers (although this is planned in the future). Finding out this information is currently a manual process, but is greatly facilitated with either the ofxconnect utility from libofx <http://libofx.sourceforge.net/> or the information available at <http://wiki.gnucash.org/wiki/OFX_Direct_Connect_Bank_Settings>.
14
+
15
+
16
+ Running Unit Tests:
17
+ There are unit tests in the test directory, and I intend to keep these correct and functional to the best of my abilities. I am, however, limited in the coverage I can generate, since I am currently testing live against my own personal bank and credit card accounts. For the near future, the unit test suite will only cover the read-only aspects of communicating over OFX. So as of now, the unit tests available are a little specific to Capital One and CitiCards accounts.
18
+
19
+ If you have an account with one of these institutions, great! Please help me test more scenarios with these banks.
20
+ If you have an account with any other institution and can extend OFX for Ruby and it's unit test suite, greater!!!
21
+
22
+ In either case, please take care not to leave bank account numbers, and especially user credentials in the source tree when committing. I'm going to try to get a more organized test suite together in the future as we add more financial institutions and automatic discovery to the mix.
23
+
24
+
25
+ OFX Specification Completeness:
26
+ For the current level of OFX support, please see the "OFX Specification completion.ods" file in the planning directory of the release. This file will also be updated periodically on the RubyForge documentation section of the OFX for Ruby site.
27
+
28
+
29
+ Source Code:
30
+ Thanks to RubyForge.org for hosting the code for OFX.
31
+
32
+ This release can be found in the Subversion repository at <svn://rubyforge.org/var/svn/ofx/tags/ofx-0.1.0>.
33
+ The latest version can be found at <svn://rubyforge.org/var/svn/ofx/trunk>.
34
+
35
+ Distribution Contents:
36
+ documentation - (one day) this will house all of the documentation for OFX for Ruby
37
+ lib - the source code for the OFX for Ruby library; (Hint: you can use this folder in your vendor folder in a Rails application)
38
+ lib/ofx.rb - the primary include file for the library
39
+ planning - files that I use to keep on track
40
+ test - the almighty unit test suite (see "Running Unit Tests", above)
41
+ COPYING - the GPLv3
42
+ README - this file
43
+ RELEASE_NOTES - a play-by-play of each OFX for Ruby release (Hint: the 0.1.0 RELEASE_NOTES are the same as this README)
44
+
45
+ ==============================================================================
data/Rakefile ADDED
@@ -0,0 +1,63 @@
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 'bundler/setup'
19
+ Bundler::GemHelper.install_tasks
20
+
21
+ require 'rake'
22
+ require 'rake/clean'
23
+ require 'rake/testtask'
24
+ require 'rake/rdoctask'
25
+
26
+
27
+ Rake::TestTask.new do |t|
28
+ t.test_files = FileList['test/**/test_*.rb']
29
+ t.verbose = true
30
+ end
31
+
32
+ Rake::RDocTask.new do |rd|
33
+ rd.title = "OFX - an OFX implementation for Ruby"
34
+ rd.rdoc_files.include("lib/**/*.rb")
35
+ rd.rdoc_files.exclude("lib/**/parser.rb")
36
+ rd.rdoc_files.exclude("lib/**/lexer.rb")
37
+ rd.rdoc_dir = "documentation/api"
38
+ end
39
+
40
+ # RCOV command, run as though from the commandline. Amend as required or perhaps move to config/environment.rb?
41
+ RCOV = "bundle exec rcov -Ilib --xref --profile"
42
+
43
+ desc "generate a unit coverage report in coverage"
44
+ task :"coverage" do
45
+ sh "#{RCOV} --output coverage test/test_*.rb test/**/test_*.rb"
46
+ end
47
+
48
+ desc "runs coverage and rdoc"
49
+ task :default => [:coverage, :rdoc]
50
+
51
+
52
+ desc "recreates parsers"
53
+ task :parsers do
54
+ sh "cd lib/ofx/1.0.2; bundle exec rex -o lexer.rb ofx_102.rex"
55
+ sh "cd lib/ofx/1.0.2; bundle exec racc -o parser.rb ofx_102.racc"
56
+ end
57
+
58
+ task :test => :parsers
59
+ task :coverage => :parsers
60
+ task :rdoc => :parsers
61
+ task :build => :parsers
62
+ task :release => :parsers
63
+ task :install => :parsers
data/lib/ofx.rb ADDED
@@ -0,0 +1,60 @@
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 'active_support'
19
+ require 'uri'
20
+ require 'cgi'
21
+
22
+ require 'bigdecimal'
23
+ require 'bigdecimal/util'
24
+
25
+ require 'ofx/version'
26
+ require 'ofx/status'
27
+ require 'ofx/file_unique_identifier'
28
+ require 'ofx/transaction_unique_identifier'
29
+ require 'ofx/header'
30
+ require 'ofx/message_set'
31
+ require 'ofx/document'
32
+ require 'ofx/statements'
33
+
34
+ require 'ofx/financial_institution'
35
+ require 'ofx/financial_client'
36
+
37
+ require 'ofx/signon_message_set'
38
+ require 'ofx/signup_message_set'
39
+ require 'ofx/banking_message_set'
40
+ require 'ofx/credit_card_statement_message_set'
41
+ require 'ofx/investment_statement_message_set'
42
+ require 'ofx/interbank_funds_transfer_message_set'
43
+ require 'ofx/wire_funds_transfer_message_set'
44
+ require 'ofx/payment_message_set'
45
+ require 'ofx/email_message_set'
46
+ require 'ofx/investment_security_list_message_set'
47
+ require 'ofx/financial_institution_profile_message_set'
48
+
49
+ require 'ofx/serializer'
50
+
51
+ require 'ofx/http/ofx_http_client.rb'
52
+
53
+ # = Summary
54
+ # An implementation of the OFX specification for financial systems
55
+ # integration.
56
+ #
57
+ # = Specifications
58
+ # OFX 1.0.2:: http://www.ofx.net/ofx/downloads/ofx102spec.zip
59
+ module OFX
60
+ end
@@ -0,0 +1,261 @@
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 BankingMessageSet < MessageSet
20
+ def ofx_102_message_set_name
21
+ 'BANK'
22
+ end
23
+
24
+ def request_or_response_from_ofx_102_tag_name(response_or_request_name)
25
+ case response_or_request_name
26
+ when "STMTTRNRQ" then BankingStatementRequest.new
27
+ when "STMTTRNRS" then BankingStatementResponse.new
28
+ else raise NotImplementedError, response_or_request_name
29
+ end
30
+ end
31
+
32
+ def self.from_ofx_102_hash(message_set_hash)
33
+ message_set = BankingMessageSet.new
34
+
35
+ message_set_hash.each_pair() do |transaction_name, transaction_hash|
36
+ case transaction_name
37
+ when "STMTTRNRQ" then message_set.requests << BankingStatementRequest.from_ofx_102_hash(transaction_hash)
38
+ when "STMTTRNRS" then message_set.responses << BankingStatementResponse.from_ofx_102_hash(transaction_hash)
39
+ else raise NotImplementedError, transaction_name
40
+ end
41
+ end
42
+
43
+ return message_set
44
+ end
45
+ end
46
+
47
+ class BankingMessageSetProfile < MessageSetProfile
48
+ def self.from_ofx_102_hash(message_set_description_hash)
49
+ profile = OFX::BankingMessageSetProfile.new
50
+ profile.message_set_class = OFX::BankingMessageSet
51
+ profile.from_ofx_102_hash(message_set_description_hash)
52
+
53
+ profile.invalid_account_types = []
54
+ if message_set_description_hash['INVALIDACCTTYPE']
55
+ message_set_description_hash['INVALIDACCTTYPE'].each do |invalid_acct_type|
56
+ profile.invalid_account_types << BankingAccount.ofx_102_s_to_account_type(invalid_acct_type)
57
+ end
58
+ end
59
+
60
+ profile.closing_statement_available = message_set_description_hash['CLOSINGAVAIL'] == 'Y'
61
+
62
+ intrabank_profile_hash = message_set_description_hash['XFERPROF']
63
+ if (intrabank_profile_hash)
64
+ profile.intrabank_transfer_profile = OFX::IntrabankTransferProfile.new
65
+
66
+ days_off = intrabank_profile_hash['PROCDAYSOFF']
67
+ profile.intrabank_transfer_profile.processing_days_off = []
68
+ if days_off
69
+ days_off.each { |day| profile.intrabank_transfer_profile.processing_days_off << day }
70
+ end
71
+ profile.intrabank_transfer_profile.processing_end_time_of_day = intrabank_profile_hash['PROCENDTM'].to_time if intrabank_profile_hash['PROCENDTM']
72
+ profile.intrabank_transfer_profile.supports_scheduled_transfers = intrabank_profile_hash['CANSCHED'] == 'Y'
73
+ profile.intrabank_transfer_profile.supports_recurring_transfers = intrabank_profile_hash['CANRECUR'] == 'Y'
74
+ profile.intrabank_transfer_profile.supports_modification_of_transfers = intrabank_profile_hash['CANMODXFERS'] == 'Y'
75
+ profile.intrabank_transfer_profile.supports_modification_of_models = intrabank_profile_hash['CANMODMDLS'] == 'Y'
76
+ profile.intrabank_transfer_profile.model_window = intrabank_profile_hash['MODELWND'].to_i
77
+ profile.intrabank_transfer_profile.number_of_days_early_funds_are_withdrawn = intrabank_profile_hash['DAYSWITH'].to_i
78
+ profile.intrabank_transfer_profile.default_number_of_days_to_pay = intrabank_profile_hash['DFLTDAYSTOPAY'].to_i
79
+ end
80
+
81
+ stop_check_profile_hash = message_set_description_hash['STPCHKPROF']
82
+ if (stop_check_profile_hash)
83
+ profile.stop_check_profile = OFX::StopCheckProfile.new
84
+
85
+ days_off = stop_check_profile_hash['PROCDAYSOFF']
86
+ profile.stop_check_profile.processing_days_off = []
87
+ if days_off
88
+ days_off.each { |day| profile.stop_check_profile.processing_days_off << day }
89
+ end
90
+ profile.stop_check_profile.processing_end_time_of_day = stop_check_profile_hash['PROCENDTM'].to_time if stop_check_profile_hash['PROCENDTM']
91
+ profile.stop_check_profile.can_stop_a_range_of_checks = stop_check_profile_hash['CANUSERANGE'] == 'Y'
92
+ profile.stop_check_profile.can_stop_checks_by_description = stop_check_profile_hash['CANUSEDESC'] == 'Y'
93
+ profile.stop_check_profile.default_stop_check_fee = stop_check_profile_hash['STPCHKFEE']
94
+ end
95
+
96
+ email_profile_hash = message_set_description_hash['EMAILPROF']
97
+ if (email_profile_hash)
98
+ profile.email_profile = OFX::BankingEmailProfile.new
99
+ profile.email_profile.supports_banking_email = email_profile_hash['CANEMAIL'] == 'Y'
100
+ profile.email_profile.supports_notifications = email_profile_hash['CANNOTIFY'] == 'Y'
101
+ end
102
+
103
+ profile
104
+ end
105
+ end
106
+
107
+ class BankingAccount
108
+ def to_ofx_102_request_body
109
+ body = []
110
+ body << " <BANKACCTFROM>"
111
+ body << " <BANKID>#{bank_identifier}"
112
+ body << " <BRANCHID>#{branch_identifier}" if branch_identifier
113
+ body << " <ACCTID>#{account_identifier}"
114
+ body << " <ACCTTYPE>#{BankingAccount.account_type_to_ofx_102_s(account_type)}"
115
+ body << " <ACCTKEY>#{account_key}" if account_key
116
+ body << " </BANKACCTFROM>"
117
+ body.join("\n")
118
+ end
119
+
120
+ def self.ofx_102_s_to_account_type(account_type)
121
+ case account_type
122
+ when 'CHECKING' then :checking
123
+ when 'SAVINGS' then :savings
124
+ when 'MONEYMRKT' then :money_market
125
+ when 'CREDITLINE' then :line_of_credit
126
+ else raise NotImplementedError
127
+ end
128
+ end
129
+ def self.account_type_to_ofx_102_s(account_type)
130
+ case account_type
131
+ when :checking then 'CHECKING'
132
+ when :savings then 'SAVINGS'
133
+ when :money_market then 'MONEYMRKT'
134
+ when :line_of_credit then 'CREDITLINE'
135
+ else raise NotImplementedError
136
+ end
137
+ end
138
+
139
+ def self.from_ofx_102_hash(account_hash)
140
+ account = OFX::BankingAccount.new
141
+
142
+ account.bank_identifier = account_hash['BANKID']
143
+ account.branch_identifier = account_hash['BRANCHID']
144
+ account.account_identifier = account_hash['ACCTID']
145
+ account.account_type = BankingAccount.ofx_102_s_to_account_type(account_hash['ACCTTYPE'])
146
+ account.account_key = account_hash['ACCTKEY']
147
+
148
+ account
149
+ end
150
+ end
151
+
152
+ class BankingStatementRequest < TransactionalRequest
153
+ def ofx_102_name
154
+ 'STMT'
155
+ end
156
+ def ofx_102_request_body
157
+ body = ""
158
+
159
+ body += account.to_ofx_102_request_body
160
+
161
+ body +=
162
+ " <INCTRAN>\n"
163
+
164
+ body +=
165
+ " <DTSTART>#{included_range.begin.to_ofx_102_s}\n" +
166
+ " <DTEND>#{included_range.end.to_ofx_102_s}\n" if included_range
167
+ body +=
168
+ " <INCLUDE>#{include_transactions.to_ofx_102_s}\n" if include_transactions
169
+
170
+ body +=
171
+ " </INCTRAN>" if include_transactions
172
+
173
+ body
174
+ end
175
+
176
+ def self.from_ofx_102_hash(transaction_hash)
177
+ raise NotImplementedError
178
+ end
179
+ end
180
+
181
+ class BankingStatementResponse < TransactionalResponse
182
+ def ofx_102_name
183
+ 'STMT'
184
+ end
185
+
186
+ def ofx_102_response_body
187
+ raise NotImplementedError
188
+ end
189
+
190
+ def self.from_ofx_102_hash(transaction_hash)
191
+ response = BankingStatementResponse.new
192
+
193
+ response.transaction_identifier = transaction_hash['TRNUID']
194
+ response.status = OFX::Status.from_ofx_102_hash(transaction_hash['STATUS'])
195
+
196
+ response_hash = transaction_hash['STMTRS']
197
+ return unless response_hash
198
+
199
+ response.default_currency = response_hash['CURDEF']
200
+ response.account = BankingAccount.from_ofx_102_hash(response_hash['BANKACCTFROM'])
201
+
202
+ response.marketing_information = response_hash['MKTGINFO']
203
+
204
+ response.ledger_balance = Balance.from_ofx_102_hash(response_hash['LEDGERBAL']) if response_hash['LEDGERBAL']
205
+ response.available_balance = Balance.from_ofx_102_hash(response_hash['AVAILBAL']) if response_hash['AVAILBAL']
206
+
207
+ transaction_list_hash = response_hash['BANKTRANLIST']
208
+ if (transaction_list_hash)
209
+ response.transaction_range = transaction_list_hash['DTSTART'].to_datetime..transaction_list_hash['DTEND'].to_datetime
210
+
211
+ response.transactions = []
212
+ transactions = transaction_list_hash['STMTTRN'] if transaction_list_hash['STMTTRN'].kind_of?(Array)
213
+ transactions = [transaction_list_hash['STMTTRN']] if transaction_list_hash['STMTTRN'].kind_of?(Hash)
214
+ transactions = [] unless transactions
215
+
216
+ transactions.each do |transaction_hash|
217
+ transaction = Transaction.new
218
+
219
+ transaction.transaction_type = Transaction.ofx_102_transaction_type_name_to_transaction_type(transaction_hash['TRNTYPE'])
220
+ transaction.date_posted = transaction_hash['DTPOSTED'].to_datetime if transaction_hash['DTPOSTED']
221
+ transaction.date_initiated = transaction_hash['DTUSER'].to_datetime if transaction_hash['DTUSER']
222
+ transaction.date_available = transaction_hash['DTAVAIL'].to_datetime if transaction_hash['DTAVAIL']
223
+
224
+ transaction.amount = transaction_hash['TRNAMT'].to_d
225
+ transaction.currency = transaction_hash['CURRENCY'] || transaction_hash['ORIGCURRENCY'] || response.default_currency
226
+
227
+ transaction.financial_institution_transaction_identifier = transaction_hash['FITID']
228
+ transaction.corrected_financial_institution_transaction_identifier = transaction_hash['CORRECTFITID']
229
+ transaction.correction_action = case transaction_hash['CORRECT_ACTION']
230
+ when 'REPLACE' then :replace
231
+ when 'DELETE' then :delete
232
+ end if transaction_hash['CORRECT_ACTION']
233
+ transaction.server_transaction_identifier = transaction_hash['SRVRTID']
234
+ transaction.check_number = transaction_hash['CHECKNUM']
235
+ transaction.reference_number = transaction_hash['REFNUM']
236
+
237
+ transaction.standard_industrial_code = transaction_hash['SIC']
238
+
239
+ transaction.payee_identifier = transaction_hash['PAYEEID']
240
+ if transaction_hash['PAYEE']
241
+ raise NotImplementedError
242
+ else
243
+ transaction.payee = transaction_hash['NAME']
244
+ end
245
+
246
+ if (transaction_hash['BANKACCTTO'])
247
+ transaction.transfer_destination_account = BankingAccount.from_ofx_102_hash(transaction_hash['BANKACCTTO'])
248
+ elsif (transaction_hash['CCACCTTO'])
249
+ transaction.transfer_destination_account = CreditCardAccount.from_ofx_102_hash(transaction_hash['CCACCTTO'])
250
+ end
251
+
252
+ transaction.memo = transaction_hash['MEMO']
253
+
254
+ response.transactions << transaction
255
+ end
256
+ end
257
+
258
+ response
259
+ end
260
+ end
261
+ end