fintoc 0.1.0

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.
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.1
6
+ before_install: gem install bundler -v 2.1.4
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - 2021-01-18
4
+
5
+ * Up to date with the [2020-11-17](https://docs.fintoc.com/docs/api-changelog#2020-11-17) API version
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in fintoc.gemspec
4
+ gemspec
@@ -0,0 +1,93 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ fintoc (0.1.0)
5
+ http
6
+ tabulate
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ addressable (2.7.0)
12
+ public_suffix (>= 2.0.2, < 5.0)
13
+ ast (2.4.1)
14
+ crack (0.4.3)
15
+ safe_yaml (~> 1.0.0)
16
+ diff-lcs (1.4.4)
17
+ domain_name (0.5.20190701)
18
+ unf (>= 0.0.5, < 1.0.0)
19
+ ffi (1.13.1)
20
+ ffi-compiler (1.0.1)
21
+ ffi (>= 1.0.0)
22
+ rake
23
+ hashdiff (1.0.1)
24
+ http (4.4.1)
25
+ addressable (~> 2.3)
26
+ http-cookie (~> 1.0)
27
+ http-form_data (~> 2.2)
28
+ http-parser (~> 1.2.0)
29
+ http-cookie (1.0.3)
30
+ domain_name (~> 0.5)
31
+ http-form_data (2.3.0)
32
+ http-parser (1.2.1)
33
+ ffi-compiler (>= 1.0, < 2.0)
34
+ jaro_winkler (1.5.4)
35
+ parallel (1.20.1)
36
+ parser (3.0.0.0)
37
+ ast (~> 2.4.1)
38
+ public_suffix (4.0.5)
39
+ rainbow (3.0.0)
40
+ rake (12.3.3)
41
+ rexml (3.2.4)
42
+ rspec (3.9.0)
43
+ rspec-core (~> 3.9.0)
44
+ rspec-expectations (~> 3.9.0)
45
+ rspec-mocks (~> 3.9.0)
46
+ rspec-core (3.9.2)
47
+ rspec-support (~> 3.9.3)
48
+ rspec-expectations (3.9.2)
49
+ diff-lcs (>= 1.2.0, < 2.0)
50
+ rspec-support (~> 3.9.0)
51
+ rspec-mocks (3.9.1)
52
+ diff-lcs (>= 1.2.0, < 2.0)
53
+ rspec-support (~> 3.9.0)
54
+ rspec-support (3.9.3)
55
+ rubocop (0.81.0)
56
+ jaro_winkler (~> 1.5.1)
57
+ parallel (~> 1.10)
58
+ parser (>= 2.7.0.1)
59
+ rainbow (>= 2.2.2, < 4.0)
60
+ rexml
61
+ ruby-progressbar (~> 1.7)
62
+ unicode-display_width (>= 1.4.0, < 2.0)
63
+ rubocop-performance (1.6.1)
64
+ rubocop (>= 0.71.0)
65
+ rubocop-rspec (1.41.0)
66
+ rubocop (>= 0.68.1)
67
+ ruby-progressbar (1.11.0)
68
+ safe_yaml (1.0.5)
69
+ tabulate (0.1.2)
70
+ unf (0.1.4)
71
+ unf_ext
72
+ unf_ext (0.0.7.7)
73
+ unicode-display_width (1.7.0)
74
+ vcr (6.0.0)
75
+ webmock (3.8.3)
76
+ addressable (>= 2.3.6)
77
+ crack (>= 0.3.2)
78
+ hashdiff (>= 0.4.0, < 2.0.0)
79
+
80
+ PLATFORMS
81
+ ruby
82
+
83
+ DEPENDENCIES
84
+ fintoc!
85
+ rspec (~> 3.0)
86
+ rubocop (~> 0.81.0)
87
+ rubocop-performance
88
+ rubocop-rspec
89
+ vcr
90
+ webmock
91
+
92
+ BUNDLED WITH
93
+ 2.1.4
@@ -0,0 +1,27 @@
1
+ Copyright © 2020, [Fintoc](https://fintoc.com/).
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ * Neither the name of the author nor the names of contributors may be used to
15
+ endorse or promote products derived from this software without specific prior
16
+ written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,166 @@
1
+ # Fintoc meets Ruby
2
+
3
+ You have just found the [Ruby](https://www.ruby-lang.org/)-flavored client of [Fintoc](https://fintoc.com). It mainly consists of a port (more of a carbon copy, really) of [fintoc-python](https://github.com/fintoc-com/fintoc-python).
4
+
5
+ ## Why?
6
+
7
+ You can think of [Fintoc API](https://fintoc.com/docs) as a piscola.
8
+ And the key ingredient to a properly made piscola are the ice cubes.
9
+ Sure, you can still have a [piscola without ice cubes](https://curl.haxx.se/).
10
+ But hey… that’s not enjoyable -- why would you do that?
11
+ Do yourself a favor: go grab some ice cubes by installing this refreshing library.
12
+
13
+ ## Table of contents
14
+
15
+ - [Fintoc meets Ruby](#fintoc-meets-ruby)
16
+ - [Table of contents](#table-of-contents)
17
+ - [How to install](#how-to-install)
18
+ - [Quickstart](#quickstart)
19
+ - [Documentation](#documentation)
20
+ - [Examples](#examples)
21
+ - [Get accounts](#get-accounts)
22
+ - [Get movements](#get-movements)
23
+ - [Dependencies](#dependencies)
24
+ - [How to test…](#how-to-test)
25
+ - [Roadmap](#roadmap)
26
+ - [Acknowledgements](#acknowledgements)
27
+
28
+
29
+ ## How to Install
30
+
31
+ Add this line to your application's Gemfile:
32
+
33
+ ```ruby
34
+ gem 'fintoc'
35
+ ```
36
+
37
+ And then execute:
38
+
39
+ $ bundle install
40
+
41
+ Or install it yourself as:
42
+
43
+ $ gem install fintoc
44
+
45
+ ## Quickstart
46
+
47
+ 1. Get your API key and link your bank account using the [Fintoc dashboard](https://app.fintoc.com/login).
48
+ 2. Open your command-line interface.
49
+ 3. Write a few lines of Ruby code to see your bank movements.
50
+
51
+ ```ruby
52
+ require 'fintoc'
53
+
54
+ client = Fintoc::Client.new('sk_test_9c8d8CeyBTx1VcJzuDgpm4H-bywJCeSx')
55
+ link = client.get_link('6n12zLmai3lLE9Dq_token_gvEJi8FrBge4fb3cz7Wp856W')
56
+ account = link.find(type: 'checking_account')
57
+
58
+ # Get the las 30 movements
59
+ movements = account.get_movements
60
+
61
+ # Or get all the movements since a specific date
62
+ movements = account.get_movements(since: '2020-08-15')
63
+
64
+ ```
65
+ And that’s it!
66
+
67
+ ## Documentation
68
+
69
+ This client supports all Fintoc API endpoints. For complete information about the API, head to the [docs](https://docs.fintoc.com/reference).
70
+
71
+ ## Examples
72
+
73
+ ### Get accounts
74
+
75
+ ```ruby
76
+ require 'fintoc'
77
+
78
+ client = Fintoc::Client.new('api_key')
79
+ link = client.get_link('link_token')
80
+ puts link.accounts
81
+
82
+ # Or... you can pretty print all the accounts in a Link
83
+
84
+ link = client.get_link('link_token')
85
+ link.show_accounts
86
+
87
+ ```
88
+
89
+ If you want to find a specific account in a link, you can use **find**. You can search by any account field:
90
+
91
+ ```ruby
92
+ require 'fintoc'
93
+
94
+ client = Fintoc::Client.new('api_key')
95
+ link = client.get_link('link_token')
96
+ account = link.find(type: 'checking_account')
97
+
98
+ # Or by number
99
+ account = link.find(number: '1111111')
100
+
101
+ # Or by account id
102
+ account = link.find(id: 'sdfsdf234')
103
+ ```
104
+
105
+ You can also search for multiple accounts matching a specific criteria with **find_all**:
106
+
107
+ ```ruby
108
+ require 'fintoc'
109
+
110
+ client = Fintoc::Client.new('api_key')
111
+ link = client.get_link('link_token')
112
+ accounts = link.find_all(currency: 'CLP')
113
+ ```
114
+
115
+ To update the account balance you can use **update_balance**:
116
+
117
+ ```ruby
118
+ require 'fintoc'
119
+
120
+ client = Fintoc::Client.new('api_key')
121
+ link = client.get_link('link_token')
122
+ account = link.find(number: '1111111')
123
+ account.update_balance
124
+ ```
125
+
126
+ ### Get movements
127
+
128
+ ```ruby
129
+ require 'fintoc'
130
+ require 'time'
131
+
132
+ client = Fintoc::Client.new('api_key')
133
+ link = client.get_link('link_token')
134
+ account = link.find(type: 'checking_account')
135
+
136
+ # You can get the account movements since a specific DateTime
137
+ yesterday = DateTime.now - 1
138
+ account.get_movements(since: yesterday)
139
+
140
+ # Or you can use an ISO 8601 formatted string representation of the Date
141
+ account.get_movements(since: '2020-01-01')
142
+
143
+ # You can also set how many movements you want per_page
144
+ account.get_movements(since: '2020-01-01', per_page: 100)
145
+ ```
146
+
147
+ Calling **get_movements** without arguments gets the last 30 movements of the account
148
+
149
+ ## Dependencies
150
+
151
+ This project relies on the following packages:
152
+
153
+ - [**http.rb**](https://github.com/httprb/http)
154
+ - [**tabulate**](https://github.com/roylez/tabulate)
155
+
156
+ ## How to test…
157
+
158
+ You can run all the tests just by running:
159
+
160
+ ```
161
+ rspec
162
+ ```
163
+
164
+ ## Contributing
165
+
166
+ Bug reports and pull requests are welcome on GitHub at https://github.com/fintoc-com/fintoc.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "fintoc"
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(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,36 @@
1
+ require_relative 'lib/fintoc/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'fintoc'
5
+ spec.version = Fintoc::VERSION
6
+ spec.authors = ['Juan Ca Sardin']
7
+ spec.email = ['juan.carlos.sardin@gmail.com']
8
+
9
+ spec.summary = 'The official Ruby client for the Fintoc API.'
10
+ spec.description = 'The official Ruby client for the Fintoc API.'
11
+ spec.homepage = 'https://github.com/fintoc-com/fintoc-ruby'
12
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
13
+
14
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
15
+
16
+ spec.metadata['homepage_uri'] = spec.homepage
17
+ spec.metadata['source_code_uri'] = 'https://github.com/fintoc-com/fintoc-ruby'
18
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ end
25
+ spec.bindir = 'exe'
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ['lib']
28
+ spec.add_dependency 'http'
29
+ spec.add_dependency 'tabulate'
30
+ spec.add_development_dependency 'rspec', '~> 3.0'
31
+ spec.add_development_dependency 'rubocop', '~> 0.81.0'
32
+ spec.add_development_dependency 'rubocop-performance'
33
+ spec.add_development_dependency 'rubocop-rspec'
34
+ spec.add_development_dependency 'vcr'
35
+ spec.add_development_dependency 'webmock'
36
+ end
@@ -0,0 +1,6 @@
1
+ require 'fintoc/version'
2
+ require 'fintoc/errors'
3
+ require 'fintoc/client'
4
+
5
+ module Fintoc
6
+ end
@@ -0,0 +1,149 @@
1
+ require 'http'
2
+ require 'fintoc/utils'
3
+ require 'fintoc/errors'
4
+ require 'fintoc/resources/link'
5
+ require 'fintoc/constants'
6
+ require 'fintoc/version'
7
+ require 'json'
8
+
9
+ module Fintoc
10
+ class Client
11
+ include Utils
12
+ def initialize(api_key)
13
+ @api_key = api_key
14
+ @user_agent = "fintoc-ruby/#{Fintoc::VERSION}"
15
+ @headers = { "Authorization": @api_key, "User-Agent": @user_agent }
16
+ @link_headers = nil
17
+ @link_header_pattern = '<(?<url>.*)>;\s*rel="(?<rel>.*)"'
18
+ @default_params = {}
19
+ end
20
+
21
+ def get
22
+ request('get')
23
+ end
24
+
25
+ def delete
26
+ request('delete')
27
+ end
28
+
29
+ def request(method)
30
+ proc do |resource, **kwargs|
31
+ parameters = params(method, **kwargs)
32
+ response = make_request(method, resource, parameters)
33
+ content = JSON.parse(response.body, symbolize_names: true)
34
+
35
+ if response.status.client_error? || response.status.server_error?
36
+ raise_custom_error(content[:error])
37
+ end
38
+
39
+ @link_headers = response.headers.get('link')
40
+ content
41
+ end
42
+ end
43
+
44
+ def fetch_next
45
+ next_ = link_headers['next']
46
+ Enumerator.new do |yielder|
47
+ while next_
48
+ yielder << get.call(next_)
49
+ next_ = link_headers['next']
50
+ end
51
+ end
52
+ end
53
+
54
+ def get_link(link_token)
55
+ data = { **_get_link(link_token), "link_token": link_token }
56
+ build_link(data)
57
+ end
58
+
59
+ def get_links
60
+ _get_links.map { |data| build_link(data) }
61
+ end
62
+
63
+ def delete_link(link_id)
64
+ delete.call("links/#{link_id}")
65
+ end
66
+
67
+ def get_account(link_token, account_id)
68
+ get_link(link_token).find(id: account_id)
69
+ end
70
+
71
+ def to_s
72
+ visible_chars = 4
73
+ hidden_part = '*' * (@api_key.size - visible_chars)
74
+ visible_key = @api_key.slice(0, visible_chars)
75
+ "Client(🔑=#{hidden_part + visible_key}"
76
+ end
77
+
78
+ private
79
+
80
+ def client
81
+ @client ||= HTTP.headers(@headers)
82
+ end
83
+
84
+ def parse_headers(dict, link)
85
+ matches = link.strip.match(@link_header_pattern)
86
+ dict[matches[:rel]] = matches[:url]
87
+ dict
88
+ end
89
+
90
+ def _get_link(link_token)
91
+ get.call("links/#{link_token}")
92
+ end
93
+
94
+ def _get_links
95
+ get.call('links')
96
+ end
97
+
98
+ def build_link(data)
99
+ param = Utils.pick(data, 'link_token')
100
+ @default_params.update(param)
101
+ Link.new(**data, client: self)
102
+ end
103
+
104
+ def make_request(method, resource, parameters)
105
+ # this is to handle url returned in the link headers
106
+ # I'm sure there is a better and more clever way to solve this
107
+ if resource.start_with? 'https'
108
+ client.send(method, resource)
109
+ else
110
+ url = "#{Fintoc::Constants::SCHEME}#{Fintoc::Constants::BASE_URL}#{resource}"
111
+ client.send(method, url, parameters)
112
+ end
113
+ end
114
+
115
+ def params(method, **kwargs)
116
+ if method == 'get'
117
+ { params: { **@default_params, **kwargs } }
118
+ else
119
+ { json: { **@default_params, **kwargs } }
120
+ end
121
+ end
122
+
123
+ def raise_custom_error(error)
124
+ raise error_class(error[:code]).new(error[:message], error[:doc_url])
125
+ end
126
+
127
+ def error_class(snake_code)
128
+ pascal_klass_name = Utils.snake_to_pascal(snake_code)
129
+ # this conditional klass_name is to handle InternalServerError custom error class
130
+ # without this the error class name would be like InternalServerErrorError (^-^)
131
+ klass = pascal_klass_name.end_with?('Error') ? pascal_klass_name : "#{pascal_klass_name}Error"
132
+ Module.const_get("Fintoc::Errors::#{klass}")
133
+ end
134
+
135
+ # This attribute getter parses the link headers using some regex 24K magic in the air...
136
+ # Ex.
137
+ # <https://api.fintoc.com/v1/links?page=1>; rel="first", <https://api.fintoc.com/v1/links?page=1>; rel="last"
138
+ # this helps to handle pagination see: https://fintoc.com/docs#paginacion
139
+ # return a hash like { first:"https://api.fintoc.com/v1/links?page=1" }
140
+ #
141
+ # @param link_headers [String]
142
+ # @return [Hash]
143
+ def link_headers
144
+ return if @link_headers.nil?
145
+
146
+ @link_headers[0].split(',').reduce({}) { |dict, link| parse_headers(dict, link) }
147
+ end
148
+ end
149
+ end