yodlee-icious 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 366b4521997b5e912f1cf8f45d7c3737b97ad6ed
4
+ data.tar.gz: f4bab7bf2bcd123a4ad18d5235f61a615f68d5ed
5
+ SHA512:
6
+ metadata.gz: 43d586bcf5a8e3bdc918774121fa62709dfd90139c4cee170d4948ad431d717e4b7ad21b0c0d54693ea40a9b0bf23f0d07260c4525c8092d1926f38e1c7316b9
7
+ data.tar.gz: f03c4cbc0a69d67961d14503d2623b345851bf1b36954e8b0b940752660319adf0ccfb0182e5f775cc929cd103bdf0f6eed7c2eaa50a9c371e0b6bd92b821894
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ .env
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --format doc
3
+ --tag ~skip
4
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in yodleeicious.gemspec
4
+ gemspec
5
+
6
+ gem 'faraday', '0.9.1'
7
+ gem 'faraday_middleware', '~>0.9.1'
8
+ gem 'socksify', '~>1.6.0'
9
+ gem 'dotenv'
10
+
11
+ group :development, :test do
12
+ gem "codeclimate-test-reporter"
13
+ gem "guard-rspec"
14
+ gem 'rspec-its'
15
+ end
data/Guardfile ADDED
@@ -0,0 +1,16 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ # Note: The cmd option is now required due to the increasing number of ways
5
+ # rspec may be run, below are examples of the most common uses.
6
+ # * bundler: 'bundle exec rspec'
7
+ # * bundler binstubs: 'bin/rspec'
8
+ # * spring: 'bin/rsspec' (This will use spring if running and you have
9
+ # installed the spring binstubs per the docs)
10
+ # * zeus: 'zeus rspec' (requires the server to be started separetly)
11
+ # * 'just' rspec: 'rspec'
12
+ guard :rspec, cmd: "bundle exec rspec" do
13
+ watch(%r{^spec/(.+)_spec\.rb$})
14
+ watch('spec/spec_helper.rb') { "spec" }
15
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
16
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Drew Nichols
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,173 @@
1
+
2
+ # Yodlee-icious
3
+ [![Gem Version](https://badge.fury.io/rb/yodlicious.svg)](http://badge.fury.io/rb/yodlicious) [![Code Climate](https://codeclimate.com/repos/556dcf7fe30ba00903005872/badges/9398ac76dbcae2084eeb/gpa.svg)](https://codeclimate.com/repos/556dcf7fe30ba00903005872/feed) [![Test Coverage](https://codeclimate.com/repos/556dcf7fe30ba00903005872/badges/9398ac76dbcae2084eeb/coverage.svg)](https://codeclimate.com/repos/556dcf7fe30ba00903005872/coverage)
4
+ [ ![Codeship Status for liftforward/yodlee-icious](https://codeship.com/projects/71603f00-9393-0132-dcd0-1a9a253548c0/status?branch=master)](https://codeship.com/projects/62288)
5
+
6
+ Yodleeicious is a ruby gem wrapping the Yodlee REST(ish) API. We had to build this for our integration with Yodlee which was somewhat more painful than it should have been so we figured we share to be a good neighbor.
7
+
8
+ ![image of yodleeicious](https://github.com/liftforward/yodlee-icious/blob/master/yodlicious.png)
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'yodlee-icious'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ $ bundle install
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install yodlee-icious
25
+
26
+ ## Usage
27
+
28
+ ### Configuration
29
+
30
+ We needed to use the Yodlee API both within a rails app and outside with multiple Yodlee connections concurrently. As such we provided both the option for a global default configuration and a instance specific configuration. For instance specific:
31
+
32
+ ```ruby
33
+ require "yodleeicious"
34
+
35
+ config = {
36
+ base_url: "https://consolidatedsdk.yodlee.com/yodsoap/srest/my-cobranded-path/v1.0",
37
+ cobranded_username: "my-cobranded-user",
38
+ cobranded_password: "my-cobranded-password"
39
+ }
40
+
41
+ yodlee_api = Yodleeicious::YodleeApi.new(config)
42
+
43
+ ```
44
+ When in a Rails app it can be more convenient to use a global default configuration. To use global defaults:
45
+
46
+ ```ruby
47
+ #/<myproject>/config/initializers/yodleeicious.rb
48
+ require 'yodleeicious'
49
+
50
+ #setting default configurations for Yodleeicious
51
+ Yodleeicious::Config.base_url = ENV['YODLEE_BASE_URL']
52
+ Yodleeicious::Config.cobranded_username = ENV['YODLEE_COBRANDED_USERNAME']
53
+ Yodleeicious::Config.cobranded_password = ENV['YODLEE_COBRANDED_PASSWORD']
54
+
55
+ #setting yodleeicious logger to use the Rails logger
56
+ Yodleeicious::Config.logger = Rails.logger
57
+ ```
58
+ and wherever you want to use the api simply create a new one and it will pickup the global defaults.
59
+
60
+ ```ruby
61
+ yodlee_api = Yodleeicious::YodleeApi.new
62
+ ```
63
+ If for any reason you need to, you can pass a hash into the constructor and it will use any provided hash values over the defaults. Note this is done on each value not the entire hash.
64
+
65
+ You can also update an existing instances of the YodleeApi's configuration with the configure method. For example:
66
+
67
+ ```ruby
68
+
69
+ yodlee_api = Yodleeicious::YodleeApi.new { base_url: 'http://yodlee.com/blablabla' }
70
+
71
+ yodlee_api.configure { base_url: 'https://secure.yodlee.com/blablabla }
72
+
73
+ puts yodlee_api.base_url
74
+ ```
75
+ will output
76
+
77
+ ```
78
+ https://secure.yodlee.com/blablabla
79
+ ```
80
+
81
+ ### Configuring the proxy
82
+
83
+ If you're Yodlee account is like ours Yodlee will whitelist certain IPs for access and you'll need to proxy all of your API requests through that IP. You can set the proxy with the proxy_url key. Currently the proxy supports, http, https, and socks proxies. Simply set the proxy_url property in the config hash passed to YodleeApi and it should begin using the proxy. For example:
84
+
85
+ ```
86
+ config = {
87
+ base_url: "https://consolidatedsdk.yodlee.com/yodsoap/srest/my-cobranded-path/v1.0",
88
+ cobranded_username: "my-cobranded-user",
89
+ cobranded_password: "my-cobranded-password",
90
+ proxy_url: "https://my-proxy-server-on-the-whitelist:my=proxy-port/"
91
+ }
92
+
93
+ yodlee_api = Yodleeicious::YodleeApi.new(config)
94
+ ```
95
+
96
+ ## Working with the API
97
+
98
+ The Yodlee Api responses are somewhat varied (especially the errors) and as such we build Yodleeicious as a pretty thin layer around their request/response model. We didn't attempt to map all their JSON responses into models or anything fancy like that. Instead we simply created a method for each API endpoint which takes the required parameters and return a response object. That said, Response object does provide some conveniences to make up for the inconsistent delivery of errors from Yodlee's APIs.
99
+
100
+ ### Starting your cobranded session
101
+
102
+ Once you've configured an instance of the YodleeAPI the first thing you must do is start a Yodlee cobranded session. This is also a good rails console test to see if everything is configured correctly:
103
+
104
+ ```ruby
105
+ pry(main)> yodlee_api = Yodleeicious::YodleeApi.new
106
+ pry(main)> response = yodlee_api.cobranded_login
107
+ pry(main)> response.success?
108
+ => true
109
+ ```
110
+ As you probably suspect the call to cobranded_login wraps the ```/authenticate/coblogin``` endpoint call. If this is a success the YodleeApi instance will cache the cobranded session id yodlee returned and use it on all subsequent api calls. You can also access this value if desired with YodleeApi#cobranded_session_token.
111
+
112
+ ```
113
+ pry(main)> yodlee_api.cobranded_session_token
114
+ => "12162013_1:a0b1ac3e32a2e656f8f5bd21de23ae1721ffd9dab8bee9f29811f5959bbf102f16c98354eba252bb030dc96e267bd2489a40562f18e09ee8ba9038d19280cc43"
115
+ ```
116
+ At this point something has probably gone wrong for you and you want to see the response json from ```/authenticate/coblogin```. To do this simply use ```response#body```.
117
+
118
+ ```
119
+ pry(main)> response.body
120
+ => {"Error"=>[{"errorDetail"=>"Invalid Cobrand Credentials"}]}
121
+ ```
122
+
123
+ ### Starting a user session
124
+
125
+ Once the cobranded session is active a number of API endpoints will work however most of the interesting ones require you to register or login under a user account. It is within these accounts that you can add the user's bank accounts and whatnot to aggregate their financial data. There are 3 methods offered to allow you to #register_user, #login_user, or do either #login_or_register_user. After executing any of these the user session will be started and the user's session token will be cached in the YodleeApi instance and used on subsequent calls to api endpoints. As with all api calls if the call was not successful you'll need to look at the body of the response to determine what went wrong.
126
+
127
+ ### Registering a new user
128
+
129
+ ```
130
+ pry(main)> response = yodlee_api.register_user 'my-username', 'my-password123', 'my-email@my-domain.com'
131
+ pry(main)> yodlee_api.user_session_token
132
+ => "12162013_1:69761d51a4010e6382ccb49b854513dbccad0f835a873d37884b68826acefaa5b8d41b634f4cc83d97d86e7df861f70860a4e4d8a3f08d5b5440eae504af5f19"
133
+ ```
134
+
135
+ ### Login existing user
136
+
137
+ ```
138
+ pry(main)> response = yodlee_api.user_login 'my-username', 'my-password123'
139
+ pry(main)> yodlee_api.user_session_token
140
+ => "12162013_1:69761d51a4010e6382ccb49b854513dbccad0f835a873d37884b68826acefaa5b8d41b634f4cc83d97d86e7df861f70860a4e4d8a3f08d5b5440eae504af5f19"
141
+ ```
142
+
143
+ ### Convenience for doing both
144
+
145
+ In case you have a situation where you don't know if the user is already registered there is #login_or_register_user
146
+
147
+ ```
148
+ pry(main)> response = yodlee_api.login_or_register_user 'my-username', 'my-password123', 'my-email@my-domain.com'
149
+ pry(main)> yodlee_api.user_session_token
150
+ => "12162013_1:69761d51a4010e6382ccb49b854513dbccad0f835a873d37884b68826acefaa5b8d41b634f4cc83d97d86e7df861f70860a4e4d8a3f08d5b5440eae504af5f19"
151
+ ```
152
+ ### other API methods
153
+
154
+ TODO
155
+
156
+ ## Contributing
157
+
158
+ 1. Fork it ( https://github.com/liftforward/yodlee-icious/fork )
159
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
160
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
161
+ 4. Push to the branch (`git push origin my-new-feature`)
162
+ 5. Create a new Pull Request
163
+
164
+ ### Running the integration suite
165
+
166
+ To run the Yodlee-icious integration tests you'll need an approved yodlee account. This is more than the one offered here [https://devnow.yodlee.com/user/register]. (Some of the integration suite will work against the devnow APIs but not all. On my todo list is to separate them out to make testing easier.) The integration suite expects these values to be set in the following environment variables:
167
+
168
+ ```
169
+ YODLEE_BASE_URL="https://consolidatedsdk.yodlee.com/yodsoap/srest/my-cobranded-path/v1.0"
170
+ YODLEE_COBRANDED_USERNAME="my-cobranded-user"
171
+ YODLEE_COBRANDED_PASSWORD="my-cobranded-password"
172
+ YODLEEICIOUS_PROXY_URL="https://my-proxy-server-on-the-whitelist:my=proxy-port/"
173
+ ```
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'logger'
3
+ require 'faraday'
4
+ require 'socksify'
5
+ require 'socksify/http'
6
+
7
+ require File.dirname(__FILE__) + "/yodleeicious/version"
8
+ require File.dirname(__FILE__) + "/yodleeicious/config"
9
+ require File.dirname(__FILE__) + "/yodleeicious/parameter_translator"
10
+ require File.dirname(__FILE__) + "/yodleeicious/response"
11
+ require File.dirname(__FILE__) + "/yodleeicious/yodlee_api"
12
+
13
+ class Faraday::Adapter::NetHttp
14
+ def net_http_connection(env)
15
+ if !(proxy = env[:request][:proxy]).empty?
16
+ if proxy[:socks]
17
+ # TCPSocket.socks_username = proxy[:user] if proxy[:user]
18
+ # TCPSocket.socks_password = proxy[:password] if proxy[:password]
19
+ Net::HTTP::SOCKSProxy(proxy[:uri].host, proxy[:uri].port)
20
+ else
21
+ Net::HTTP::Proxy(proxy[:uri].host, proxy[:uri].port, proxy[:uri].user, proxy[:uri].password)
22
+ end
23
+ else
24
+ Net::HTTP
25
+ end.new(env[:url].host, env[:url].port)
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+
2
+
3
+ module Yodleeicious
4
+ class Config
5
+ class << self
6
+ attr_accessor :base_url, :cobranded_username, :cobranded_password, :proxy_url, :logger
7
+ end
8
+
9
+ self.logger = Logger.new(STDOUT)
10
+ self.logger.level = Logger::WARN
11
+
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ module Yodleeicious
2
+ class ParameterTranslator
3
+ def site_login_form_to_add_site_account_params site_login_form
4
+
5
+ params = { "credentialFields.enclosedType" => "com.yodlee.common.FieldInfoSingle" }
6
+
7
+ i = 0
8
+ site_login_form['componentList'].each { |field|
9
+ # puts "field=#{field}"
10
+ params["credentialFields[#{i}].displayName"] = field['displayName']
11
+ params["credentialFields[#{i}].fieldType.typeName"] = field['fieldType']['typeName']
12
+ params["credentialFields[#{i}].helpText"] = field['helpText']
13
+ params["credentialFields[#{i}].maxlength"] = field['maxlength']
14
+ params["credentialFields[#{i}].name"] = field['name']
15
+ params["credentialFields[#{i}].size"] = field['size']
16
+ params["credentialFields[#{i}].value"] = field['value']
17
+ params["credentialFields[#{i}].valueIdentifier"] = field['valueIdentifier']
18
+ params["credentialFields[#{i}].valueMask"] = field['valueMask']
19
+ params["credentialFields[#{i}].isEditable"] = field['isEditable']
20
+ params["credentialFields[#{i}].value"] = field['fieldValue']
21
+
22
+ i += 1
23
+ }
24
+
25
+ params
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ module Yodleeicious
2
+ class Response
3
+
4
+ def initialize body
5
+ @body = body
6
+ end
7
+
8
+ def success?
9
+ !fail?
10
+ end
11
+
12
+ def fail?
13
+ body.kind_of?(Hash) && (body['errorOccurred'] == 'true' || body.has_key?('Error'))
14
+ end
15
+
16
+ def body
17
+ @body
18
+ end
19
+
20
+ def error
21
+ if body.kind_of?(Hash)
22
+ if body.has_key?('Error')
23
+ body['Error'][0]['errorDetail']
24
+ elsif body['errorOccurred'] == 'true'
25
+ body['exceptionType']
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module Yodleeicious
2
+ VERSION = "0.0.4"
3
+ end
@@ -0,0 +1,368 @@
1
+ require 'json'
2
+
3
+
4
+ module Yodleeicious
5
+ class YodleeApi
6
+ attr_reader :base_url, :cobranded_username, :cobranded_password, :proxy_url, :logger
7
+
8
+ def initialize config = {}
9
+ configure config
10
+ end
11
+
12
+ def configure config = {}
13
+ validate config
14
+ @base_url = config[:base_url] || Yodleeicious::Config.base_url
15
+ @cobranded_username = config[:cobranded_username] || Yodleeicious::Config.cobranded_username
16
+ @cobranded_password = config[:cobranded_password] || Yodleeicious::Config.cobranded_password
17
+ @proxy_url = config[:proxy_url] || Yodleeicious::Config.proxy_url
18
+ @logger = config[:logger] || Yodleeicious::Config.logger
19
+
20
+ info_log "YodleeApi configured with base_url=#{base_url} cobranded_username=#{cobranded_username} proxy_url=#{proxy_url} logger=#{logger}"
21
+ end
22
+
23
+ def validate config
24
+ [:proxy_url, :base_url, :cobranded_username, :cobranded_password, :logger].each { |key|
25
+ if config.has_key?(key) && config[key].nil?
26
+ raise "Invalid config provided to YodleeApi. Values may not be nil or blank."
27
+ end
28
+ }
29
+ end
30
+
31
+ def proxy_opts
32
+ proxy_opts = {}
33
+
34
+ unless proxy_url == nil
35
+ proxy_opts[:uri] = URI.parse(proxy_url)
36
+ proxy_opts[:socks] = use_socks?
37
+ end
38
+
39
+ proxy_opts
40
+ end
41
+
42
+ def use_socks?
43
+ return proxy_url != nil && proxy_url.start_with?('socks')
44
+ end
45
+
46
+ def cobranded_login
47
+ params = {
48
+ cobrandLogin: cobranded_username,
49
+ cobrandPassword: cobranded_password
50
+ }
51
+
52
+ response = execute_api '/authenticate/coblogin', params
53
+
54
+ if response.success?
55
+ @cobranded_auth = response.body
56
+ else
57
+ @cobranded_auth = nil
58
+ end
59
+
60
+ response
61
+ end
62
+
63
+ def user_login login, pass
64
+ params = {
65
+ login: login,
66
+ password: pass
67
+ }
68
+
69
+ response = cobranded_session_execute_api '/authenticate/login', params
70
+
71
+ if response.success?
72
+ @user_auth = response.body
73
+ else
74
+ @user_auth = nil
75
+ end
76
+
77
+ response
78
+ end
79
+
80
+ def logout_user
81
+ user_session_execute_api '/jsonsdk/Login/logout'
82
+ end
83
+
84
+ def register_user username, password, emailAddress, options = {}
85
+ params = {
86
+ 'userCredentials.loginName' => username,
87
+ 'userCredentials.password' => password,
88
+ 'userCredentials.objectInstanceType' => 'com.yodlee.ext.login.PasswordCredentials',
89
+ 'userProfile.emailAddress' => emailAddress
90
+ #todo add in user preferences
91
+ }.merge(options)
92
+
93
+ response = cobranded_session_execute_api "/jsonsdk/UserRegistration/register3", params
94
+
95
+ if response.success?
96
+ @user_auth = response.body
97
+ else
98
+ @user_auth = nil
99
+ end
100
+
101
+ response
102
+ end
103
+
104
+ def login_or_register_user username, password, email
105
+ info_log "attempting to login #{username}"
106
+ response = user_login(username, password)
107
+
108
+ #TODO look into what other errors could occur here
109
+ if response.fail? && response.error == "Invalid User Credentials"
110
+ info_log "invalid credentials for #{username} attempting to register"
111
+ response = register_user username, password, email
112
+ end
113
+
114
+ if response.success?
115
+ @user_auth = response.body
116
+ else
117
+ @user_auth = nil
118
+ end
119
+
120
+ response
121
+ end
122
+
123
+ def unregister_user
124
+ response = user_session_execute_api '/jsonsdk/UserRegistration/unregister'
125
+ if response.success?
126
+ @user_auth = nil
127
+ end
128
+
129
+ response
130
+ end
131
+
132
+ def site_search search_string
133
+ user_session_execute_api "/jsonsdk/SiteTraversal/searchSite", { siteSearchString: search_string }
134
+ end
135
+
136
+ def search_content_services search_string
137
+ user_session_execute_api "/jsonsdk/Search/searchContentServices", { keywords: search_string }
138
+ end
139
+
140
+ def add_site_account site_id, site_login_form
141
+ params = {
142
+ siteId: site_id
143
+ }.merge(translator.site_login_form_to_add_site_account_params(site_login_form))
144
+
145
+ user_session_execute_api '/jsonsdk/SiteAccountManagement/addSiteAccount1', params
146
+ end
147
+
148
+
149
+ def add_site_account_and_wait site_id, site_login_form, refresh_interval = 0.5, max_trys = 5
150
+ add_site_account_response = add_site_account(site_id, site_login_form)
151
+
152
+ if add_site_account_response.success?
153
+ if normal_site_refresh_in_progress?(add_site_account_response.body['siteRefreshInfo'])
154
+ site_account_id = add_site_account_response.body['siteAccountId']
155
+ try = 1
156
+ begin
157
+ debug_log "try #{try} to get refresh_info for #{site_id}"
158
+ try += 1
159
+ sleep(refresh_interval)
160
+ refresh_info_response = get_site_refresh_info site_account_id
161
+ add_site_account_response.body['siteRefreshInfo'] = refresh_info_response.body unless refresh_info_response.fail?
162
+ end while should_retry_get_site_refresh_info? refresh_info_response, try, max_trys
163
+ end
164
+ end
165
+
166
+ add_site_account_response
167
+ end
168
+
169
+ def should_retry_get_site_refresh_info? response, try, max_trys
170
+ return response.success? && try < max_trys && (response.body['code'] == 801 || (response.body['code'] == 0 &&
171
+ response.body['siteRefreshStatus']['siteRefreshStatus'] != 'REFRESH_COMPLETED' &&
172
+ response.body['siteRefreshStatus']['siteRefreshStatus'] != 'REFRESH_TIMED_OUT' &&
173
+ response.body['siteRefreshStatus']['siteRefreshStatus'] != 'LOGIN_SUCCESS' ))
174
+ end
175
+
176
+ def normal_site_refresh_in_progress? site_refresh_info
177
+ return site_refresh_info['siteRefreshMode']['refreshMode'] == 'NORMAL' &&
178
+ ['REFRESH_TRIGGERED','PARTIAL_COMPLETE'].include?(site_refresh_info['siteRefreshStatus']['siteRefreshStatus'])
179
+ end
180
+
181
+ def get_mfa_response_for_site site_account_id
182
+ user_session_execute_api '/jsonsdk/Refresh/getMFAResponseForSite', { memSiteAccId: site_account_id }
183
+ end
184
+
185
+ def get_mfa_response_for_site_and_wait site_account_id, refresh_interval=0.5, max_trys=5
186
+ response = get_mfa_response_for_site site_account_id
187
+
188
+ try = 1
189
+ while should_retry_get_mfa_response? response, try, max_trys
190
+ debug_log "try #{try} to get mfa message for #{site_account_id}"
191
+ try += 1
192
+ sleep(refresh_interval)
193
+ response = get_mfa_response_for_site site_account_id
194
+ end
195
+
196
+ response
197
+ end
198
+
199
+ def should_retry_get_mfa_response? response, try, max_trys
200
+ return response.success? && response.body['errorCode'].nil? && response.body['isMessageAvailable'] != true && try < max_trys
201
+ end
202
+
203
+ def put_mfa_request_for_site site_account_id, mfa_type, field_info
204
+ params = {
205
+ 'memSiteAccId' => site_account_id,
206
+ 'userResponse.objectInstanceType' => "com.yodlee.core.mfarefresh.#{mfa_type}"
207
+ }
208
+
209
+ case mfa_type.to_sym
210
+ when :MFATokenResponse
211
+ params['userResponse.token']=field_info['fieldValue']
212
+ when :MFAImageResponse
213
+ params['userResponse.imageString']=field_info['fieldValue']
214
+ when :MFAQuesAnsResponse
215
+ questionsArray = field_info['questionAndAnswerValues']
216
+ i = 0
217
+ while i < questionsArray.length do
218
+ params["userResponse.quesAnsDetailArray[#{i}].answer"]=questionsArray[i]['fieldValue']
219
+ params["userResponse.quesAnsDetailArray[#{i}].answerFieldType"]=questionsArray[i]['answerFieldType']
220
+ params["userResponse.quesAnsDetailArray[#{i}].metaData"]=questionsArray[i]['metaData']
221
+ params["userResponse.quesAnsDetailArray[#{i}].question"]=questionsArray[i]['question']
222
+ params["userResponse.quesAnsDetailArray[#{i}].questionFieldType"]=questionsArray[i]['questionFieldType']
223
+ i += 1
224
+ end
225
+ end
226
+
227
+ user_session_execute_api '/jsonsdk/Refresh/putMFARequestForSite', params
228
+ end
229
+
230
+ def get_site_refresh_info site_account_id
231
+ user_session_execute_api '/jsonsdk/Refresh/getSiteRefreshInfo', { memSiteAccId: site_account_id }
232
+ end
233
+
234
+ def start_site_refresh site_account_id, refresh_priority = 1
235
+ params = {
236
+ 'memSiteAccId' => site_account_id,
237
+ 'refreshParameters.refreshPriority' => refresh_priority
238
+ }
239
+ user_session_execute_api '/jsonsdk/Refresh/startSiteRefresh', params
240
+ end
241
+
242
+ def get_content_service_info_by_routing_number routing_number, no_trim = true
243
+ params = {
244
+ routingNumber: routing_number,
245
+ notrim: no_trim
246
+ }
247
+
248
+ cobranded_session_execute_api '/jsonsdk/RoutingNumberService/getContentServiceInfoByRoutingNumber', params
249
+ end
250
+
251
+ def get_item_summaries
252
+ user_session_execute_api '/jsonsdk/DataService/getItemSummaries', { 'bridgetAppId' => '10003200' }
253
+ end
254
+
255
+ def get_item_summaries_for_site site_account_id
256
+ user_session_execute_api '/jsonsdk/DataService/getItemSummariesForSite', { memSiteAccId: site_account_id }
257
+ end
258
+
259
+ def get_all_site_accounts
260
+ user_session_execute_api '/jsonsdk/SiteAccountManagement/getAllSiteAccounts'
261
+ end
262
+
263
+ def get_site_login_form site_id
264
+ user_session_execute_api '/jsonsdk/SiteAccountManagement/getSiteLoginForm', { siteId: site_id }
265
+ end
266
+
267
+ def get_site_info site_id
268
+ params = {
269
+ 'siteFilter.siteId' => site_id,
270
+ 'siteFilter.reqSpecifier' => 16
271
+ }
272
+ cobranded_session_execute_api '/jsonsdk/SiteTraversal/getSiteInfo', params
273
+ end
274
+
275
+ def execute_user_search_request options = {}
276
+ params = {
277
+ 'transactionSearchRequest.containerType' => 'All',
278
+ 'transactionSearchRequest.lowerFetchLimit' => 1,
279
+ 'transactionSearchRequest.higherFetchLimit' => 500,
280
+ 'transactionSearchRequest.resultRange.startNumber' => 1,
281
+ 'transactionSearchRequest.resultRange.endNumber' => 500,
282
+ 'transactionSearchRequest.searchClients.clientId' => 1,
283
+ 'transactionSearchRequest.searchClients.clientName' => 'DataSearchService',
284
+ 'transactionSearchRequest.ignoreUserInput' => true,
285
+ #todo make it so that we can pass a simpler hash of arguments
286
+ # 'transactionSearchRequest.userInput' => nil,
287
+ # 'transactionSearchRequest.searchFilter.currencyCode' => nil,
288
+ # 'transactionSearchRequest.searchFilter.postDateRange.fromDate' => nil,
289
+ # 'transactionSearchRequest.searchFilter.postDateRange.toDate' => nil,
290
+ # 'transactionSearchRequest.searchFilter.itemAccountId.identifier' => nil,
291
+ 'transactionSearchRequest.searchFilter.transactionSplitType' => 'ALL_TRANSACTION'
292
+ }.merge(options)
293
+
294
+ user_session_execute_api "/jsonsdk/TransactionSearchService/executeUserSearchRequest", params
295
+ end
296
+
297
+ def get_user_transactions search_identifier, start_number, end_number
298
+ params = {
299
+ 'searchFetchRequest.searchIdentifier.identifier' => search_identifier,
300
+ 'searchFetchRequest.searchResultRange.startNumber' => start_number,
301
+ 'searchFetchRequest.searchResultRange.endNumber' => end_number
302
+ }
303
+ user_session_execute_api '/jsonsdk/TransactionSearchService/getUserTransactions', params
304
+ end
305
+
306
+ def cobranded_session_execute_api uri, params = {}
307
+ params = {
308
+ cobSessionToken: cobranded_session_token,
309
+ }.merge(params)
310
+
311
+ execute_api uri, params
312
+ end
313
+
314
+ def user_session_execute_api uri, params = {}
315
+ params = {
316
+ userSessionToken: user_session_token
317
+ }.merge(params)
318
+
319
+ cobranded_session_execute_api uri, params
320
+ end
321
+
322
+ def execute_api uri, params = {}
323
+ debug_log "calling #{uri} with #{params}"
324
+ ssl_opts = { verify: false }
325
+ connection = Faraday.new(url: base_url, ssl: ssl_opts, request: { proxy: proxy_opts })
326
+
327
+ response = connection.post("#{base_url}#{uri}", params)
328
+ debug_log "response=#{response.status} success?=#{response.success?} body=#{response.body} "
329
+
330
+ case response.status
331
+ when 200
332
+ Response.new(JSON.parse(response.body))
333
+ else
334
+ end
335
+ end
336
+
337
+ def translator
338
+ @translator ||= ParameterTranslator.new
339
+ end
340
+
341
+ def cobranded_auth
342
+ @cobranded_auth
343
+ end
344
+
345
+ def user_auth
346
+ @user_auth
347
+ end
348
+
349
+ def cobranded_session_token
350
+ return nil if cobranded_auth.nil?
351
+ cobranded_auth.fetch('cobrandConversationCredentials',{}).fetch('sessionToken','dude')
352
+ end
353
+
354
+ def user_session_token
355
+ return nil if user_auth.nil?
356
+ user_auth.fetch('userContext',{}).fetch('conversationCredentials',{}).fetch('sessionToken',nil)
357
+ end
358
+
359
+ def debug_log msg
360
+ logger.debug msg
361
+ end
362
+
363
+ def info_log msg
364
+ logger.info msg
365
+ end
366
+
367
+ end
368
+ end