ofx_for_ruby 0.1.3 → 0.1.4
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.
- 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 +31 -7
- 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
- metadata +38 -39
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: f5ff318155e5d3faeab49209d7492b3f9ba36400
         | 
| 4 | 
            +
              data.tar.gz: 049c7b99734a9da7a0fcedf0f34dd2b051c63d29
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 0574c03c81ba22a20babfbbba79a4d821ed7f6a5e0a131ed6d4a87f0bbfa6f31d5e3e7ee6fdb8e255fa8d9a9fa4c02755b4412ab08b94d75e905933662ea3231
         | 
| 7 | 
            +
              data.tar.gz: 81a52f06bfc3bcd1280b2057302e0e0882c6b9a468ffcd7e73485a8ae8ad776285c4fa46e7c587fa665059d2961bc12a60c2fff8a82f05aa09b7bb085ee78da6
         | 
    
        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
    
    | @@ -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
         | 
| @@ -57,9 +57,14 @@ module OFX | |
| 57 57 | 
             
                        def from_http_response_body(body)
         | 
| 58 58 | 
             
                            # puts "Raw response:\n#{body}"
         | 
| 59 59 |  | 
| 60 | 
            -
                            header_pattern = /(\w+\:.*\n) | 
| 60 | 
            +
                            #header_pattern = /(\w+\:.*\n)+\n/
         | 
| 61 | 
            +
                            header_pattern = /(\w+\:.*\r*\n)+\r*\n/
         | 
| 61 62 | 
             
                            header_match = header_pattern.match(body)
         | 
| 62 | 
            -
                            
         | 
| 63 | 
            +
                            if header_match.nil?
         | 
| 64 | 
            +
                              raise NotImplementedError, "OFX server returned unmatched ASCII"
         | 
| 65 | 
            +
                              return body
         | 
| 66 | 
            +
                            end
         | 
| 67 | 
            +
             | 
| 63 68 | 
             
                            body = header_match.post_match
         | 
| 64 69 | 
             
                            header = Header.from_ofx_102_s(header_match[0].strip)
         | 
| 65 70 |  | 
| @@ -82,15 +87,34 @@ module OFX | |
| 82 87 | 
             
                end
         | 
| 83 88 | 
             
            end
         | 
| 84 89 |  | 
| 85 | 
            -
            require 'date'
         | 
| 86 90 | 
             
            class Date
         | 
| 87 91 | 
             
                def to_ofx_102_s
         | 
| 88 92 | 
             
                    strftime('%Y%m%d')
         | 
| 89 93 | 
             
                end
         | 
| 90 94 | 
             
            end
         | 
| 91 95 | 
             
            class DateTime
         | 
| 92 | 
            -
                def  | 
| 93 | 
            -
                     | 
| 96 | 
            +
                def to_time
         | 
| 97 | 
            +
                    Time.parse(self.to_s)
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                def to_ofx_102_s_defunct
         | 
| 101 | 
            +
                    strftime('%Y%m%d%H%M%S.') + (sec_fraction * 86400000000).to_i.to_s + '[' + offset.numerator.to_s + ':' + strftime('%Z') + ']'
         | 
| 102 | 
            +
                end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                def to_ofx_102_s(extended=true)
         | 
| 105 | 
            +
                    s = strftime('%Y%m%d%H%M%S')
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                    if extended
         | 
| 108 | 
            +
                        # some servers need exactly 3 decimal places for sec_fraction
         | 
| 109 | 
            +
                        s = s + '.000'
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                        # use Time class to return TZ in correct format for OFX
         | 
| 112 | 
            +
                        #  ie. ("EDT" vs. "-07:00")
         | 
| 113 | 
            +
                        tz = to_time.zone
         | 
| 114 | 
            +
                        s = s + '[0:' + tz + ']' if tz
         | 
| 115 | 
            +
                    end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                    return s
         | 
| 94 118 | 
             
                end
         | 
| 95 119 | 
             
            end
         | 
| 96 120 | 
             
            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
         |