finicity-ruby 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.rubocop.yml +28 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +177 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/finicity-ruby.gemspec +34 -0
- data/lib/finicity/client.rb +41 -0
- data/lib/finicity/configurable.rb +22 -0
- data/lib/finicity/fetchers/api.rb +32 -0
- data/lib/finicity/fetchers/base.rb +81 -0
- data/lib/finicity/fetchers/token.rb +50 -0
- data/lib/finicity/fetchers.rb +7 -0
- data/lib/finicity/resources/account.rb +72 -0
- data/lib/finicity/resources/base.rb +23 -0
- data/lib/finicity/resources/customer.rb +25 -0
- data/lib/finicity/resources/institution.rb +18 -0
- data/lib/finicity/resources/transaction.rb +33 -0
- data/lib/finicity/version.rb +3 -0
- data/lib/finicity-ruby.rb +41 -0
- metadata +183 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ff94ad68c52843464a17802b5f6322d226a0d5e9
|
4
|
+
data.tar.gz: 98254830bb29dcab441aecd9d0365fdcd05ac7c3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4adbb085e03e3479435deab13a49986590cfdd609fac99f99e7e41ce18642db812d14cc240618c90b24ccdd9c595e3367990261c68fc1111298307440ff49cd8
|
7
|
+
data.tar.gz: 5537f9ebbec5479ed4de430b76e33e01fc83bcdbdd4eca29dffe1481f34225e8d4d7a137b1f005c1d40b2605608ec37716b7f195a10af5f5530a6135d13ba1e1
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.3
|
3
|
+
Exclude:
|
4
|
+
- "bin/**/*"
|
5
|
+
- "db/**/*"
|
6
|
+
- "*.gemspec"
|
7
|
+
DisplayCopNames: true
|
8
|
+
|
9
|
+
Lint/EndAlignment:
|
10
|
+
AlignWith: variable
|
11
|
+
|
12
|
+
Metrics/LineLength:
|
13
|
+
Max: 120
|
14
|
+
|
15
|
+
Style/StringLiterals:
|
16
|
+
EnforcedStyle: double_quotes
|
17
|
+
|
18
|
+
Documentation:
|
19
|
+
Enabled: false
|
20
|
+
|
21
|
+
Style/FrozenStringLiteralComment:
|
22
|
+
Enabled: false
|
23
|
+
|
24
|
+
Style/FileName:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
Documentation:
|
28
|
+
Enabled: false
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
finicity-ruby
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.3.0
|
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
7
|
+
|
8
|
+
We are committed to making participation in this project a harassment-free
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
12
|
+
|
13
|
+
Examples of unacceptable behavior by participants include:
|
14
|
+
|
15
|
+
* The use of sexualized language or imagery
|
16
|
+
* Personal attacks
|
17
|
+
* Trolling or insulting/derogatory comments
|
18
|
+
* Public or private harassment
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
20
|
+
addresses, without explicit permission
|
21
|
+
* Other unethical or unprofessional conduct
|
22
|
+
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
27
|
+
threatening, offensive, or harmful.
|
28
|
+
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
32
|
+
Conduct may be permanently removed from the project team.
|
33
|
+
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
35
|
+
when an individual is representing the project or its community.
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
38
|
+
reported by contacting a project maintainer at just.azzurri@gmail.com. All
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
42
|
+
incident.
|
43
|
+
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
45
|
+
version 1.3.0, available at
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
47
|
+
|
48
|
+
[homepage]: http://contributor-covenant.org
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Azzurrio
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
# Finicity::Ruby
|
2
|
+
[![Build Status](https://travis-ci.org/Fundthrough/finicity-ruby.svg?branch=master)](https://travis-ci.org/Fundthrough/finicity-ruby)
|
3
|
+
[![Code Climate](https://codeclimate.com/github/Fundthrough/finicity-ruby/badges/gpa.svg)](https://codeclimate.com/github/Fundthrough/finicity-ruby)
|
4
|
+
[![codecov](https://codecov.io/gh/Fundthrough/finicity-ruby/branch/master/graph/badge.svg)](https://codecov.io/gh/Fundthrough/finicity-ruby)
|
5
|
+
[![License](http://img.shields.io/:license-MIT-blue.svg?style=flat)](LICENSE)
|
6
|
+
|
7
|
+
|
8
|
+
Welcome to `finicity-ruby` gem. This gem is built to communicate easily with Finicity's Aggregation API. It only uses the `JSON` representation of the API. Pull requests are welcome to help improving the gem.
|
9
|
+
|
10
|
+
- [Installation](#installation)
|
11
|
+
- [Setup](#setup)
|
12
|
+
- [Usage](#usage)
|
13
|
+
- [Institutions](#1-institutions)
|
14
|
+
- [List](#list)
|
15
|
+
- [Get specific Institution (with login Credentials)](#get-specific-institution-with-login-credentials)
|
16
|
+
- [Customers](#2-customers)
|
17
|
+
- [Add new customer](#add-new-customer)
|
18
|
+
- [List all customers](#list-all-customers)
|
19
|
+
- [Delete customer](#delete-customer)
|
20
|
+
- [Accounts](#3-accounts)
|
21
|
+
- [Discover and add all accounts](#discover-and-add-all-accounts)
|
22
|
+
- [Discover and add all accounts (with MFA)](#discover-and-add-all-accounts-with-mfa)
|
23
|
+
- [List accounts](#list-accounts)
|
24
|
+
- [Get specific account](#get-specific-account)
|
25
|
+
- [Delete specific account](#delete-specific-account)
|
26
|
+
- [Refresh accounts](#refresh-accounts)
|
27
|
+
- [Refresh accounts (with MFA)](#refresh-accounts-with-mfa)
|
28
|
+
- [Activate accounts](#activate-accounts)
|
29
|
+
- [Update account credentials](#update-account-credentials)
|
30
|
+
- [Transactions](#4-transactions)
|
31
|
+
- [List all transactions](#list-all-transactions)
|
32
|
+
- [List all transactions for specific account](#list-all-transactions-for-specific-account)
|
33
|
+
- [Load historic transactions](#load-historic-transactions)
|
34
|
+
- [Contributing](#contributing)
|
35
|
+
- [License](#license)
|
36
|
+
|
37
|
+
## Installation
|
38
|
+
|
39
|
+
The gem is available through `Rubygems` and can be installed via:
|
40
|
+
|
41
|
+
$ gem install finicity-ruby
|
42
|
+
|
43
|
+
or add it to your Gemfile like this:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
gem "finicity-ruby"
|
47
|
+
```
|
48
|
+
|
49
|
+
## Setup
|
50
|
+
Then, in your `config/initializers/` add `finicity.rb` file with corresponding values. Right now we are using redis to store the temp app token which used with each request. In the future, we are going to support multiple options to store token like memory, file & redis. Meanwhile just set the `redis_url` config into your redis host url.
|
51
|
+
|
52
|
+
Finicity.configure do |config|
|
53
|
+
config.redis_url = "redis://127.0.0.1:6379"
|
54
|
+
config.app_key = "xxxxxxxxxxxxxxxx"
|
55
|
+
config.partner_id = "xxxxxxxxxxxxxxxx"
|
56
|
+
config.partner_secret = "xxxxxxxxxxxxxxxx"
|
57
|
+
config.app_type = "testing" # or "active" for production apps.
|
58
|
+
config.verbose = false
|
59
|
+
config.max_retries = 1 # How many times do you want to retry the request in case timeout failures.
|
60
|
+
end
|
61
|
+
|
62
|
+
## Usage
|
63
|
+
### 1. Institutions
|
64
|
+
#### List
|
65
|
+
|
66
|
+
To get all institutions
|
67
|
+
|
68
|
+
response = Finicity::Client.institution.list
|
69
|
+
response.status_code # 200
|
70
|
+
response.success? # true
|
71
|
+
response.body.institutions # returns all institutions [{name: "Royal Bank of Canada", id: "1411", ...]
|
72
|
+
|
73
|
+
To search for specific institution
|
74
|
+
|
75
|
+
Finicity::Client.institution.list(search: "Royal Bank of Canada")
|
76
|
+
|
77
|
+
#### Get specific Institution (with login Credentials)
|
78
|
+
Using the `id` you just got from the list above, you can find any specific institution
|
79
|
+
|
80
|
+
response = Finicity::Client.institution.get("107132")
|
81
|
+
response.body.institution.login_credentials # [{id: 1231, name: "username", description: "Please enter your username", ...]
|
82
|
+
|
83
|
+
### 2. Customers
|
84
|
+
#### Add new customer
|
85
|
+
Finicity requires `username` to add a new customer. The response will contain the customer ID to be used for subsequent calls.
|
86
|
+
|
87
|
+
response = Finicity::Client.customer.add("YetAnotherBatman")
|
88
|
+
customer_id = response.body.customer.id
|
89
|
+
|
90
|
+
#### List all customers
|
91
|
+
To list all created customers from Finicity.
|
92
|
+
|
93
|
+
Finicity::Client.customer.list
|
94
|
+
|
95
|
+
#### Delete customer
|
96
|
+
Using the `customer_id` you just got after you add the customer.
|
97
|
+
|
98
|
+
Finicity::Client.scope(customer_id).delete
|
99
|
+
|
100
|
+
### 3. Accounts
|
101
|
+
#### Discover and add all accounts
|
102
|
+
To discover & add the accounts into your customer, you've to authenticate it using the `institution#login_credentials`.
|
103
|
+
|
104
|
+
institution_id = 101732
|
105
|
+
credentials = [{id: 101732001, name: "Banking Userid", value: "Azzurrio"}, { id: "101732002", name: "Banking Password", value: "LetMePass"]
|
106
|
+
Finicity::Client.scope(customer_id).account.add_all(institution_id, credentials)
|
107
|
+
|
108
|
+
#### Discover and add all accounts (with MFA)
|
109
|
+
In case you get a MFA required. You can submit the answer into `add_all_mfa`. The `mfa_session` will be provided in `add_all` response headers.
|
110
|
+
|
111
|
+
response = Finicity::Client.scope(customer_id).account.add_all(institution_id, credentials)
|
112
|
+
response.status_code # 203
|
113
|
+
mfa_session = response.headers["MFA-session"] # e86jnv923nsas4
|
114
|
+
response.body.questions # [{ text: "What's your super hero?" }]
|
115
|
+
answers = [{ text: "What's your super hero?", answer: "Batman" }]
|
116
|
+
Finicity::Client.scope(customer_id).account.add_all_mfa(institution_id, mfa_sessiom, answers)
|
117
|
+
|
118
|
+
#### List accounts
|
119
|
+
|
120
|
+
Finicity::Client.scope(customer_id).account.list
|
121
|
+
|
122
|
+
#### Get specific account
|
123
|
+
|
124
|
+
Finicity::Client.scope(customer_id).account.get("236534") # using account id
|
125
|
+
|
126
|
+
#### Delete specific account
|
127
|
+
|
128
|
+
Finicity::Client.scope(customer_id).account.delete(account_id)
|
129
|
+
|
130
|
+
#### Refresh accounts
|
131
|
+
After adding the accounts you have to refresh them so you can have access into their transactions.
|
132
|
+
|
133
|
+
Finicity::Client.scope(customer_id).account.refresh(institution_login_id)
|
134
|
+
|
135
|
+
#### Refresh accounts (with MFA)
|
136
|
+
In case you get MFA, just like `as add_all_mfa`
|
137
|
+
|
138
|
+
Finicity::Client.scope(customer_id).account.refresh(institution_login_id, mfa_session, answers)
|
139
|
+
|
140
|
+
#### Activate accounts
|
141
|
+
Sometimes you get accounts with type `unknown`, so you need to activate those accounts with the correct types.
|
142
|
+
|
143
|
+
accounts = [{id: 12412, type: "savings"}, {id: 15434, type: "creditCard"}]
|
144
|
+
Finicity::Client.scope(customer_id).account.activate(institution_id, accounts)
|
145
|
+
|
146
|
+
#### Update account credentials
|
147
|
+
In case account credentials have been changed, you can you get credentials and update them after populating the values correct credentials.
|
148
|
+
|
149
|
+
Finicity::Client.scope(customer_id).account.credentials(account_id) # To be used for updating credentials
|
150
|
+
Finicity::Client.scope(customer_id).account.update_credentials(account_id, credentials)
|
151
|
+
|
152
|
+
### 4. Transactions
|
153
|
+
#### List all transactions
|
154
|
+
This one will get all transactions for the given customer. You have to specify the date range using `from:` and `to:` named parameters.
|
155
|
+
|
156
|
+
Finicity::Client.scope(customer_id).transaction.list(from: 6.months.ago, to: Date.today)
|
157
|
+
|
158
|
+
#### List all transactions for specific account
|
159
|
+
In case you only need transactions for specific account. You have to provide the `account_id` as extra parameter.
|
160
|
+
|
161
|
+
Finicity::Client.scope(customer_id).transaction.list_for_account(account_id, from: 6.months.ago, to: Date.today)
|
162
|
+
|
163
|
+
#### Load historic transactions
|
164
|
+
This's a feature in Finicity API which gives you the ability to access +6 months transactions. This could be only used per account and it's an interactive request, which means MFA challenge could be appeared.
|
165
|
+
|
166
|
+
Finicity::Client.scope(customer_id).transaction.load_historic(account_id)
|
167
|
+
Finicity::Client.scope(customer_id).transaction.load_historic_mfa(account_id, mfa_session, answers)
|
168
|
+
|
169
|
+
## Contributing
|
170
|
+
|
171
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Fundthrough/finicity-ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
172
|
+
|
173
|
+
|
174
|
+
## License
|
175
|
+
|
176
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
177
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "finicity/ruby"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "finicity/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "finicity-ruby"
|
8
|
+
spec.version = Finicity::VERSION
|
9
|
+
spec.authors = ["Azzurrio"]
|
10
|
+
spec.email = ["just.azzurri@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = "Ruby Client for Finicity Aggregation API"
|
13
|
+
spec.description = "Ruby Client for Finicity Aggregation API"
|
14
|
+
spec.homepage = "https://github.com/Fundthrough/finicity-ruby"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.required_ruby_version = ">= 2.3.0"
|
23
|
+
spec.required_rubygems_version = ">= 2.0.0"
|
24
|
+
|
25
|
+
spec.add_runtime_dependency "hashie", "~> 3.4.4"
|
26
|
+
spec.add_runtime_dependency "httparty", "~> 0.14.0"
|
27
|
+
spec.add_runtime_dependency "redis", "~> 3.3.1"
|
28
|
+
spec.add_runtime_dependency "activesupport"
|
29
|
+
|
30
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
31
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
32
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
33
|
+
spec.add_development_dependency "rubocop"
|
34
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "finicity/resources/base"
|
2
|
+
require "finicity/resources/institution"
|
3
|
+
require "finicity/resources/customer"
|
4
|
+
require "finicity/resources/account"
|
5
|
+
require "finicity/resources/transaction"
|
6
|
+
|
7
|
+
module Finicity
|
8
|
+
class Client
|
9
|
+
attr_reader :customer_id
|
10
|
+
|
11
|
+
def self.scope(customer_id)
|
12
|
+
new(customer_id)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.customer
|
16
|
+
Finicity::Resources::Customer
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.institution
|
20
|
+
Finicity::Resources::Institution
|
21
|
+
end
|
22
|
+
|
23
|
+
def customer
|
24
|
+
@customer ||= Finicity::Resources::Customer.new(customer_id)
|
25
|
+
end
|
26
|
+
|
27
|
+
def account
|
28
|
+
@account ||= Finicity::Resources::Account.new(customer_id)
|
29
|
+
end
|
30
|
+
|
31
|
+
def transaction
|
32
|
+
@transaction ||= Finicity::Resources::Transaction.new(customer_id)
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def initialize(customer_id)
|
38
|
+
@customer_id = customer_id
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "hashie"
|
2
|
+
|
3
|
+
module Finicity
|
4
|
+
module Configurable
|
5
|
+
KEYS = [:redis_url, :app_key, :partner_id, :partner_secret, :max_retries, :app_type, :verbose].freeze
|
6
|
+
|
7
|
+
attr_writer(*KEYS)
|
8
|
+
|
9
|
+
def configure
|
10
|
+
yield self
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def configs
|
15
|
+
@configs ||= begin
|
16
|
+
hash = {}
|
17
|
+
KEYS.each { |key| hash[key] = instance_variable_get(:"@#{key}") }
|
18
|
+
Hashie::Mash.new(hash)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "finicity/fetchers/token"
|
2
|
+
|
3
|
+
module Finicity
|
4
|
+
module Fetchers
|
5
|
+
class API < Base
|
6
|
+
class << self
|
7
|
+
def request(*args)
|
8
|
+
response = super(*args)
|
9
|
+
|
10
|
+
return response unless invalid_app_token?(response)
|
11
|
+
|
12
|
+
app_token.refresh
|
13
|
+
request(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def invalid_app_token?(response)
|
19
|
+
response.status_code == 401 && INVALID_APP_TOKEN_CODES.include?(response.body&.code)
|
20
|
+
end
|
21
|
+
|
22
|
+
def default_headers
|
23
|
+
{ "Finicity-App-Token" => app_token.get }
|
24
|
+
end
|
25
|
+
|
26
|
+
def app_token
|
27
|
+
::Finicity::Fetchers::Token
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require "httparty"
|
2
|
+
|
3
|
+
module Finicity
|
4
|
+
module Fetchers
|
5
|
+
class Base
|
6
|
+
include HTTParty
|
7
|
+
|
8
|
+
base_uri "https://api.finicity.com/aggregation"
|
9
|
+
headers "Content-Type" => "application/json"
|
10
|
+
headers "Accept" => "application/json"
|
11
|
+
headers "Finicity-App-Key" => Finicity.configs.app_key
|
12
|
+
debug_output $stdout if Finicity.configs.verbose
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def request(method, endpoint, opts = {})
|
16
|
+
tries = 0
|
17
|
+
loop do
|
18
|
+
begin
|
19
|
+
break fetch(method, endpoint, opts)
|
20
|
+
rescue Net::ReadTimeout, Errno::ECONNREFUSED, Net::OpenTimeout => e
|
21
|
+
raise e if (tries += 1) > Finicity.configs.max_retries.to_i
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def fetch(method, endpoint, opts)
|
29
|
+
request_opts = normalize_request_options(opts)
|
30
|
+
|
31
|
+
response = send(method, endpoint, request_opts)
|
32
|
+
|
33
|
+
raise Finicity::ApiServerError, response.body if server_error?(response)
|
34
|
+
|
35
|
+
Hashie::Mash.new(
|
36
|
+
success?: response.success?,
|
37
|
+
status_code: response.code,
|
38
|
+
body: parse_json(response.body),
|
39
|
+
headers: response.headers
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def normalize_request_options(opts)
|
44
|
+
opts.clone.tap do |o|
|
45
|
+
o[:headers] = default_headers.merge(o[:headers].to_h)
|
46
|
+
o[:body] = jsonify(o[:body]) if o[:body].present?
|
47
|
+
o[:query] = camelcase_keys(o[:query]) if o[:query].present?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def parse_json(body)
|
52
|
+
result = JSON.parse(body.to_s).deep_transform_keys!(&:underscore)
|
53
|
+
Hashie::Mash.new(result)
|
54
|
+
rescue JSON::ParserError
|
55
|
+
body
|
56
|
+
end
|
57
|
+
|
58
|
+
def jsonify(body)
|
59
|
+
camelcase_keys(body).to_json
|
60
|
+
end
|
61
|
+
|
62
|
+
def camelcase_keys(hash)
|
63
|
+
hash.deep_transform_keys! { |k| k.to_s.camelcase(:lower) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def server_error?(response)
|
67
|
+
other_content_type?(response)
|
68
|
+
end
|
69
|
+
|
70
|
+
def other_content_type?(response)
|
71
|
+
content_type = response.headers["Content-Type"]&.downcase
|
72
|
+
content_type.present? && content_type != "application/json"
|
73
|
+
end
|
74
|
+
|
75
|
+
def default_headers
|
76
|
+
{}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "redis"
|
2
|
+
|
3
|
+
module Finicity
|
4
|
+
module Fetchers
|
5
|
+
class Token < Base
|
6
|
+
class << self
|
7
|
+
def get
|
8
|
+
refresh if token_expired?
|
9
|
+
token
|
10
|
+
end
|
11
|
+
|
12
|
+
def refresh
|
13
|
+
response = fetch_new_one
|
14
|
+
|
15
|
+
raise Finicity::TokenRefreshError, response.body unless response.success?
|
16
|
+
|
17
|
+
redis["finicity-token-expires-at"] = 90.minutes.from_now.to_s
|
18
|
+
redis["finicity-token"] = response.body.token
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def fetch_new_one
|
24
|
+
endpoint = "/v2/partners/authentication"
|
25
|
+
body = {
|
26
|
+
partner_id: Finicity.configs.partner_id,
|
27
|
+
partner_secret: Finicity.configs.partner_secret
|
28
|
+
}
|
29
|
+
request(:post, endpoint, body: body)
|
30
|
+
end
|
31
|
+
|
32
|
+
def token_expired?
|
33
|
+
!(token_expired_at.present? && Time.parse(token_expired_at).future?)
|
34
|
+
end
|
35
|
+
|
36
|
+
def token
|
37
|
+
redis["finicity-token"]
|
38
|
+
end
|
39
|
+
|
40
|
+
def token_expired_at
|
41
|
+
redis["finicity-token-expires-at"]
|
42
|
+
end
|
43
|
+
|
44
|
+
def redis
|
45
|
+
Redis.new(url: Finicity.configs.redis_url)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Finicity
|
2
|
+
module Resources
|
3
|
+
class Account < Base
|
4
|
+
def add_all(institution_id, credentials)
|
5
|
+
endpoint = "/v1/customers/#{customer_id}/institutions/#{institution_id}/accounts/addall"
|
6
|
+
body = { credentials: credentials }
|
7
|
+
|
8
|
+
request(:post, endpoint, body: body)
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_all_mfa(institution_id, mfa_session, questions)
|
12
|
+
endpoint = "/v1/customers/#{customer_id}/institutions/#{institution_id}/accounts/addall/mfa"
|
13
|
+
body = { mfa_challenges: { questions: questions } }
|
14
|
+
headers = { "MFA-Session" => mfa_session }
|
15
|
+
|
16
|
+
request(:post, endpoint, body: body, headers: headers)
|
17
|
+
end
|
18
|
+
|
19
|
+
def list
|
20
|
+
endpoint = "/v1/customers/#{customer_id}/accounts"
|
21
|
+
|
22
|
+
request(:get, endpoint)
|
23
|
+
end
|
24
|
+
|
25
|
+
def activate(institution_id, accounts)
|
26
|
+
endpoint = "/v2/customers/#{customer_id}/institutions/#{institution_id}/accounts"
|
27
|
+
|
28
|
+
request(:put, endpoint, body: { accounts: accounts })
|
29
|
+
end
|
30
|
+
|
31
|
+
def refresh(institution_login_id)
|
32
|
+
endpoint = "/v1/customers/#{customer_id}/institutionLogins/#{institution_login_id}/accounts"
|
33
|
+
|
34
|
+
request(:post, endpoint)
|
35
|
+
end
|
36
|
+
|
37
|
+
def refresh_mfa(institution_login_id, mfa_session, questions)
|
38
|
+
endpoint = "/v1/customers/#{customer_id}/institutionLogins/#{institution_login_id}/accounts/mfa"
|
39
|
+
|
40
|
+
body = { questions: questions }
|
41
|
+
headers = { "MFA-Session" => mfa_session }
|
42
|
+
|
43
|
+
request(:post, endpoint, body: body, headers: headers)
|
44
|
+
end
|
45
|
+
|
46
|
+
def get(account_id)
|
47
|
+
endpoint = "/v1/customers/#{customer_id}/accounts/#{account_id}"
|
48
|
+
|
49
|
+
request(:get, endpoint)
|
50
|
+
end
|
51
|
+
|
52
|
+
def delete(account_id)
|
53
|
+
endpoint = "/v1/customers/#{customer_id}/accounts/#{account_id}"
|
54
|
+
|
55
|
+
request(:delete, endpoint)
|
56
|
+
end
|
57
|
+
|
58
|
+
def credentials(account_id)
|
59
|
+
endpoint = "/v1/customers/#{customer_id}/accounts/#{account_id}/loginForm"
|
60
|
+
|
61
|
+
request(:get, endpoint)
|
62
|
+
end
|
63
|
+
|
64
|
+
def update_credentials(account_id, credentials)
|
65
|
+
endpoint = "/v1/customers/#{customer_id}/accounts/#{account_id}/loginForm"
|
66
|
+
body = { login_form: credentials }
|
67
|
+
|
68
|
+
request(:put, endpoint, body: body)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "finicity/fetchers"
|
2
|
+
|
3
|
+
module Finicity
|
4
|
+
module Resources
|
5
|
+
class Base
|
6
|
+
attr_reader :customer_id
|
7
|
+
|
8
|
+
def initialize(customer_id)
|
9
|
+
@customer_id = customer_id
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.request(*args)
|
13
|
+
::Finicity::Fetchers::API.request(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def request(*args)
|
19
|
+
self.class.request(*args)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Finicity
|
2
|
+
module Resources
|
3
|
+
class Customer < Base
|
4
|
+
def self.add(username)
|
5
|
+
endpoint = "/v1/customers/#{Finicity.configs.app_type}"
|
6
|
+
body = { username: username }
|
7
|
+
|
8
|
+
request(:post, endpoint, body: body)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.list(query = {})
|
12
|
+
endpoint = "/v1/customers"
|
13
|
+
query = { query: query } if query.present?
|
14
|
+
|
15
|
+
request(:get, endpoint, query)
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete
|
19
|
+
endpoint = "/v1/customers/#{customer_id}"
|
20
|
+
|
21
|
+
request(:delete, endpoint)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Finicity
|
2
|
+
module Resources
|
3
|
+
class Institution < Base
|
4
|
+
def self.list(query = {})
|
5
|
+
endpoint = "/v1/institutions"
|
6
|
+
query = { query: query } if query.present?
|
7
|
+
|
8
|
+
request(:get, endpoint, query)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.get(institution_id)
|
12
|
+
endpoint = "/v1/institutions/#{institution_id}/details"
|
13
|
+
|
14
|
+
request(:get, endpoint)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Finicity
|
2
|
+
module Resources
|
3
|
+
class Transaction < Base
|
4
|
+
def list(from:, to:, params: {})
|
5
|
+
endpoint = "/v2/customers/#{customer_id}/transactions"
|
6
|
+
query = { from_date: from.to_time.to_i, to_date: to.to_time.to_i }.merge(params)
|
7
|
+
|
8
|
+
request(:get, endpoint, query: query)
|
9
|
+
end
|
10
|
+
|
11
|
+
def list_for_account(account_id, from:, to:, params: {})
|
12
|
+
endpoint = "/v2/customers/#{customer_id}/accounts/#{account_id}/transactions"
|
13
|
+
query = { from_date: from.to_time.to_i, to_date: to.to_time.to_i }.merge(params)
|
14
|
+
|
15
|
+
request(:get, endpoint, query: query)
|
16
|
+
end
|
17
|
+
|
18
|
+
def load_historic(account_id)
|
19
|
+
endpoint = "/v1/customers/#{customer_id}/accounts/#{account_id}/transactions/historic"
|
20
|
+
|
21
|
+
request(:post, endpoint)
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_historic_mfa(account_id, mfa_session, questions)
|
25
|
+
endpoint = "/v1/customers/#{customer_id}/accounts/#{account_id}/transactions/historic/mfa"
|
26
|
+
headers = { "MFA-Session" => mfa_session }
|
27
|
+
body = { questions: questions }
|
28
|
+
|
29
|
+
request(:post, endpoint, body: body, headers: headers)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "finicity/configurable"
|
2
|
+
require "finicity/version"
|
3
|
+
require "finicity/client"
|
4
|
+
require "active_support/all"
|
5
|
+
|
6
|
+
module Finicity
|
7
|
+
extend Finicity::Configurable
|
8
|
+
|
9
|
+
class ApiServerError < StandardError; end
|
10
|
+
class TokenRefreshError < StandardError; end
|
11
|
+
class LoadHistoricTxnError < StandardError; end
|
12
|
+
|
13
|
+
# Description: Expired Finicity app token
|
14
|
+
# Required Action: Renew app token
|
15
|
+
INVALID_APP_TOKEN_CODES = [10_022, 10_023].freeze
|
16
|
+
|
17
|
+
# Description: Retry Error or Problem Connecting to the Institution
|
18
|
+
# Required Action: Nothing. Just try again later.
|
19
|
+
CONNECTION_PROBLEM_CODES = [102, 320, 580].freeze
|
20
|
+
|
21
|
+
# Description: Invalid Credentials
|
22
|
+
# Required Action: Prompt the customer for the correct credentials.
|
23
|
+
INVALID_CREDENTIALS_CODES = [103].freeze
|
24
|
+
|
25
|
+
# Description: User Action Required
|
26
|
+
# Required Action: Prompt the user to login to their account directly at the institution's website
|
27
|
+
# and follow the instructions there
|
28
|
+
USER_ACTION_REQUIRED_CODES = [108, 109].freeze
|
29
|
+
|
30
|
+
# Description: Missing or Incorrect MFA Answer
|
31
|
+
# Required Action: Refresh Customer Account and follow the MFA sequence to prompt the user for the missing information
|
32
|
+
INCORRECT_MFA_ANSWERS_CODES = [185, 187].freeze
|
33
|
+
|
34
|
+
# Description: The account is currently being aggregated.
|
35
|
+
# Required Action: Nothing. This is only an informational message. Try again later.
|
36
|
+
ACCOUNT_BEING_AGG_CODES = [325, 5006].freeze
|
37
|
+
|
38
|
+
# Description: Missing Parameter
|
39
|
+
# Required Action: A required field was left blank, Submit the request again, with valid text
|
40
|
+
MISSING_PARAMS_CODES = [10_005].freeze
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: finicity-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Azzurrio
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-11-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hashie
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.4.4
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.4.4
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: httparty
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.14.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.14.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: redis
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.3.1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.3.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activesupport
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bundler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.11'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.11'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '10.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '10.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rubocop
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: Ruby Client for Finicity Aggregation API
|
126
|
+
email:
|
127
|
+
- just.azzurri@gmail.com
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- ".gitignore"
|
133
|
+
- ".rspec"
|
134
|
+
- ".rubocop.yml"
|
135
|
+
- ".ruby-gemset"
|
136
|
+
- ".ruby-version"
|
137
|
+
- ".travis.yml"
|
138
|
+
- CODE_OF_CONDUCT.md
|
139
|
+
- Gemfile
|
140
|
+
- LICENSE.txt
|
141
|
+
- README.md
|
142
|
+
- Rakefile
|
143
|
+
- bin/console
|
144
|
+
- bin/setup
|
145
|
+
- finicity-ruby.gemspec
|
146
|
+
- lib/finicity-ruby.rb
|
147
|
+
- lib/finicity/client.rb
|
148
|
+
- lib/finicity/configurable.rb
|
149
|
+
- lib/finicity/fetchers.rb
|
150
|
+
- lib/finicity/fetchers/api.rb
|
151
|
+
- lib/finicity/fetchers/base.rb
|
152
|
+
- lib/finicity/fetchers/token.rb
|
153
|
+
- lib/finicity/resources/account.rb
|
154
|
+
- lib/finicity/resources/base.rb
|
155
|
+
- lib/finicity/resources/customer.rb
|
156
|
+
- lib/finicity/resources/institution.rb
|
157
|
+
- lib/finicity/resources/transaction.rb
|
158
|
+
- lib/finicity/version.rb
|
159
|
+
homepage: https://github.com/Fundthrough/finicity-ruby
|
160
|
+
licenses:
|
161
|
+
- MIT
|
162
|
+
metadata: {}
|
163
|
+
post_install_message:
|
164
|
+
rdoc_options: []
|
165
|
+
require_paths:
|
166
|
+
- lib
|
167
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
168
|
+
requirements:
|
169
|
+
- - ">="
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
version: 2.3.0
|
172
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
173
|
+
requirements:
|
174
|
+
- - ">="
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: 2.0.0
|
177
|
+
requirements: []
|
178
|
+
rubyforge_project:
|
179
|
+
rubygems_version: 2.5.1
|
180
|
+
signing_key:
|
181
|
+
specification_version: 4
|
182
|
+
summary: Ruby Client for Finicity Aggregation API
|
183
|
+
test_files: []
|