fractal_api 0.1.0

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
+ SHA256:
3
+ metadata.gz: ff9de86ce971b3c40aefed88d6e77ca424eaafe40398464c48419846f4b5868b
4
+ data.tar.gz: f46953463f071c7c92c55ce7f0b2b2f41f1a03e5d1b73ea9cf01d0ffa3dd434a
5
+ SHA512:
6
+ metadata.gz: daa456d15d21972278cb559ada7bfe98c9ef69039c54a99b42c66a28dba38ad866d79e953c2a9786cdbe4287a79050e5712149c5cc1bb791db9c3e8a013a99a7
7
+ data.tar.gz: 99a49eb766f98d98acdf9267a3f14f6d216625d6c6c33e1b9c129ef4f9ecc5255d5d148781ecef5e5e955e1bdf017b5934e9bf807d5895e56c120d955e24659f
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /.rspec_status
data/.rubocop.yml ADDED
@@ -0,0 +1,96 @@
1
+ require: rubocop-rspec
2
+
3
+ AllCops:
4
+ TargetRubyVersion: 2.6
5
+ NewCops: enable
6
+ Exclude:
7
+ - 'db/**/*'
8
+ - 'ansible/**/*'
9
+ - 'vendor/**/*'
10
+ - 'bin/**/*'
11
+ - 'node_modules/**/*'
12
+
13
+ # Don't force top level comments in every class
14
+ Style/Documentation:
15
+ Enabled: false
16
+
17
+ # A good line length is 100 chars, comments are ignored
18
+ Layout/LineLength:
19
+ Max: 100
20
+ AllowURI: true
21
+ IgnoredPatterns: ['\A(\s+)?#']
22
+
23
+ Metrics/BlockLength:
24
+ Enabled: false
25
+
26
+ Metrics/ClassLength:
27
+ Max: 300
28
+
29
+ Metrics/MethodLength:
30
+ Max: 20
31
+
32
+ Metrics/AbcSize:
33
+ Max: 30
34
+
35
+ RSpec/ExampleLength:
36
+ Enabled: false
37
+
38
+ RSpec/MultipleExpectations:
39
+ Max: 10
40
+
41
+ RSpec/LetSetup:
42
+ Enabled: false
43
+
44
+ RSpec/MessageSpies:
45
+ Enabled: false
46
+
47
+ RSpec/RepeatedExample:
48
+ Exclude:
49
+ - 'spec/policies/**/*'
50
+
51
+ RSpec/RepeatedDescription:
52
+ Exclude:
53
+ - 'spec/policies/**/*'
54
+
55
+ Layout/EmptyLinesAroundAttributeAccessor:
56
+ Enabled: true
57
+
58
+ Layout/SpaceAroundMethodCallOperator:
59
+ Enabled: true
60
+
61
+ Lint/DeprecatedOpenSSLConstant:
62
+ Enabled: true
63
+
64
+ Lint/RaiseException:
65
+ Enabled: true
66
+
67
+ Lint/StructNewOverride:
68
+ Enabled: true
69
+
70
+ Style/ExponentialNotation:
71
+ Enabled: true
72
+
73
+ Style/HashEachMethods:
74
+ Enabled: true
75
+
76
+ Style/HashTransformKeys:
77
+ Enabled: true
78
+
79
+ Style/HashTransformValues:
80
+ Enabled: true
81
+
82
+ Style/SlicingWithRange:
83
+ Enabled: true
84
+
85
+ # TODO: we should rename our fields from line_1, address_line_2, etc
86
+ Naming/VariableNumber:
87
+ Enabled: false
88
+
89
+ Metrics/CyclomaticComplexity:
90
+ Max: 9
91
+
92
+ Metrics/PerceivedComplexity:
93
+ Max: 10
94
+
95
+ RSpec/MultipleMemoizedHelpers:
96
+ Max: 8
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ finpoint-app
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.6.6
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ # Specify your gem's dependencies in fractal_api.gemspec
8
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,75 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ fractal_api (0.1.0)
5
+ faraday
6
+ faraday_middleware
7
+ faraday_middleware-multi_json
8
+ multi_json
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ addressable (2.7.0)
14
+ public_suffix (>= 2.0.2, < 5.0)
15
+ coderay (1.1.3)
16
+ crack (0.4.5)
17
+ rexml
18
+ diff-lcs (1.4.4)
19
+ dotenv (2.7.6)
20
+ faraday (1.3.0)
21
+ faraday-net_http (~> 1.0)
22
+ multipart-post (>= 1.2, < 3)
23
+ ruby2_keywords
24
+ faraday-net_http (1.0.1)
25
+ faraday_middleware (1.0.0)
26
+ faraday (~> 1.0)
27
+ faraday_middleware-multi_json (0.0.6)
28
+ faraday_middleware
29
+ multi_json
30
+ hashdiff (1.0.1)
31
+ method_source (1.0.0)
32
+ multi_json (1.15.0)
33
+ multipart-post (2.1.1)
34
+ pry (0.13.1)
35
+ coderay (~> 1.1)
36
+ method_source (~> 1.0)
37
+ public_suffix (4.0.6)
38
+ rake (13.0.3)
39
+ rexml (3.2.4)
40
+ rspec (3.10.0)
41
+ rspec-core (~> 3.10.0)
42
+ rspec-expectations (~> 3.10.0)
43
+ rspec-mocks (~> 3.10.0)
44
+ rspec-core (3.10.1)
45
+ rspec-support (~> 3.10.0)
46
+ rspec-expectations (3.10.1)
47
+ diff-lcs (>= 1.2.0, < 2.0)
48
+ rspec-support (~> 3.10.0)
49
+ rspec-mocks (3.10.2)
50
+ diff-lcs (>= 1.2.0, < 2.0)
51
+ rspec-support (~> 3.10.0)
52
+ rspec-support (3.10.2)
53
+ ruby2_keywords (0.0.4)
54
+ vcr (6.0.0)
55
+ webmock (3.12.1)
56
+ addressable (>= 2.3.6)
57
+ crack (>= 0.3.2)
58
+ hashdiff (>= 0.4.0, < 2.0.0)
59
+
60
+ PLATFORMS
61
+ x86_64-darwin-19
62
+ x86_64-linux
63
+
64
+ DEPENDENCIES
65
+ bundler (~> 2.2)
66
+ dotenv
67
+ fractal_api!
68
+ pry
69
+ rake
70
+ rspec
71
+ vcr
72
+ webmock
73
+
74
+ BUNDLED WITH
75
+ 2.2.11
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # FractalApi
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/fractal_api`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'fractal_api'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install fractal_api
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/fractal_api.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'fractal_api'
5
+ require 'json'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ require 'pry'
12
+ Pry.start
data/bin/setup ADDED
@@ -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,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'fractal_api/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'fractal_api'
9
+ spec.version = FractalApi::VERSION
10
+ spec.authors = ['Finpoint']
11
+ spec.email = ['nenad@finpoint.co.uk']
12
+
13
+ spec.summary = 'FractalApi client'
14
+ spec.description = 'Client for FractalApi https://docs.askfractal.com'
15
+ spec.homepage = 'https://www.finpoint.co.uk'
16
+
17
+ # Specify which files should be added to the gem when it is released.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.bindir = 'exe'
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ['lib']
25
+
26
+ spec.required_ruby_version = '~> 2.0'
27
+
28
+ spec.add_dependency 'faraday'
29
+ spec.add_dependency 'faraday_middleware'
30
+ spec.add_dependency 'faraday_middleware-multi_json'
31
+ spec.add_dependency 'multi_json'
32
+ spec.add_development_dependency 'bundler', '~> 2.2'
33
+ spec.add_development_dependency 'dotenv'
34
+ spec.add_development_dependency 'pry'
35
+ spec.add_development_dependency 'rake'
36
+ spec.add_development_dependency 'rspec'
37
+ spec.add_development_dependency 'vcr'
38
+ spec.add_development_dependency 'webmock'
39
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fractal_api/version'
4
+ require 'fractal_api/authenticate'
5
+ require 'fractal_api/client'
6
+ require 'fractal_api/configuration'
7
+ require 'fractal_api/company'
8
+ require 'fractal_api/bank'
9
+ require 'fractal_api/consent'
10
+ require 'fractal_api/bank_account'
11
+ require 'fractal_api/balance'
12
+ require 'fractal_api/transaction'
13
+
14
+ module FractalApi
15
+ module_function
16
+
17
+ def configure
18
+ yield(configuration)
19
+ end
20
+
21
+ def client
22
+ @client ||= Client.new
23
+ end
24
+
25
+ def configuration
26
+ @configuration ||= Configuration.new
27
+ end
28
+
29
+ # Sets global token for the duration of the block
30
+ # Token is set in Thread local variable,
31
+ # so it's usable in the current Thread only (sidekiq job, rails request, etc.)
32
+ #
33
+ # MUST not be nested in another +with_global_token+ block
34
+ def with_global_token
35
+ Thread.current[:fractal_api_token] = Authenticate.new.call(
36
+ configuration.api_key, configuration.partner_id
37
+ )
38
+
39
+ yield
40
+
41
+ Thread.current[:fractal_api_token] = nil
42
+ end
43
+
44
+ def global_token
45
+ Thread.current[:fractal_api_token]
46
+ end
47
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fractal_api/configuration'
4
+ require 'fractal_api/errors'
5
+ require 'fractal_api/version'
6
+
7
+ module FractalApi
8
+ class Authenticate
9
+ def call(api_key, partner_id)
10
+ response = post(
11
+ '/token',
12
+ api_key: api_key,
13
+ partner_id: partner_id
14
+ )
15
+
16
+ if response.success?
17
+ response.body[:access_token]
18
+ elsif response.status == 403
19
+ raise UnauthorizedError, response.body
20
+ else
21
+ raise GenericError, response.body
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def post(path, api_key:, partner_id:)
28
+ connection.post(path, nil, { 'X-Api-Key' => api_key, 'X-Partner-Id' => partner_id })
29
+ end
30
+
31
+ def connection
32
+ auth_url = FractalApi.configuration.auth_url
33
+
34
+ @connection ||= Faraday.new(url: auth_url) do |conn|
35
+ conn.headers.merge!(
36
+ content_type: 'application/json',
37
+ user_agent: "finpoint/#{FractalApi::VERSION}"
38
+ )
39
+ conn.response :multi_json, symbolize_keys: true
40
+ conn.response :logger if FractalApi.configuration.debug
41
+ conn.adapter Faraday.default_adapter
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'fractal_api/base_model'
5
+ require 'fractal_api/paginator'
6
+
7
+ module FractalApi
8
+ class Balance < BaseModel
9
+ attributes :id, :bank_id, :account_id, :date,
10
+ :amount, :currency, :type, :status,
11
+ :external_id, :source
12
+
13
+ def self.all(company_id, filters = {})
14
+ filters = filters.transform_keys { |key| key_transformer.transform(key) }
15
+
16
+ Paginator.new('/banking/v2/balances', self) do |url|
17
+ get(
18
+ url,
19
+ params: filters,
20
+ headers: { 'X-Company-Id' => company_id }
21
+ )
22
+ end
23
+ end
24
+
25
+ def self.build(json:)
26
+ super.tap do |record|
27
+ record.date = Time.parse(record.date) if record.date
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fractal_api/base_model'
4
+ require 'fractal_api/paginator'
5
+
6
+ module FractalApi
7
+ class Bank < BaseModel
8
+ attributes :id, :name, :logo, :logo_url
9
+
10
+ def self.all(filters = {})
11
+ Paginator.new('/banking/v2/banks', self) do |url|
12
+ get(url, params: filters)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fractal_api/base_model'
4
+ require 'fractal_api/bank_account_scheme'
5
+ require 'fractal_api/paginator'
6
+
7
+ module FractalApi
8
+ class BankAccount < BaseModel
9
+ attributes :id, :bank_id, :currency, :nickname,
10
+ :account, :external_id, :source
11
+
12
+ def self.all(company_id, filters = {})
13
+ filters = filters.transform_keys { |key| key_transformer.transform(key) }
14
+
15
+ Paginator.new('/banking/v2/accounts', self) do |url|
16
+ get(
17
+ url,
18
+ params: filters,
19
+ headers: { 'X-Company-Id' => company_id }
20
+ )
21
+ end
22
+ end
23
+
24
+ def self.build(json:)
25
+ super.tap do |record|
26
+ record.account = (record.account || []).compact.map do |acs|
27
+ BankAccountScheme.build(json: acs)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fractal_api/base_model'
4
+
5
+ module FractalApi
6
+ class BankAccountScheme < BaseModel
7
+ attributes :scheme_name, :name, :identification, :secondary_identification
8
+ end
9
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fractal_api/errors'
4
+ require 'fractal_api/camelizer_lower'
5
+
6
+ module FractalApi
7
+ class BaseModel
8
+ class << self
9
+ def get(path, params: {}, headers: {})
10
+ result = FractalApi.client.get(path.strip, params: params, headers: headers)
11
+
12
+ raise APIKeyError, result.body[:error] if result.status == 401
13
+
14
+ result
15
+ end
16
+
17
+ def post(path, params: {}, headers: {})
18
+ result = FractalApi.client.post(path.strip, params: params, headers: headers)
19
+
20
+ raise APIKeyError, result.body[:error] if result.status == 401
21
+
22
+ result
23
+ end
24
+
25
+ def put(path, params: {}, headers: {})
26
+ result = FractalApi.client.put(path.strip, params: params, headers: headers)
27
+
28
+ raise APIKeyError, result.body[:error] if result.status == 401
29
+
30
+ result
31
+ end
32
+
33
+ def attributes(*attributes)
34
+ @attributes ||= []
35
+
36
+ return @attributes unless attributes
37
+
38
+ attr_accessor(*attributes)
39
+
40
+ @attributes += attributes
41
+ end
42
+
43
+ def attribute_aliases(aliases = nil)
44
+ @attribute_aliases ||= {}
45
+
46
+ return @attribute_aliases unless aliases
47
+
48
+ @attribute_aliases.merge!(aliases)
49
+ end
50
+
51
+ def format_url(url, params)
52
+ formatted = url.dup.strip
53
+
54
+ params.each { |key, value| formatted.sub!(":#{key}", value) }
55
+
56
+ formatted
57
+ end
58
+
59
+ def key_transformer
60
+ CamelizerLower
61
+ end
62
+
63
+ # Sets all the instance variables by reading the JSON from FractalApi and converting the keys from
64
+ # PascalCase to snake_case, as it's the standard in Ruby.
65
+ def build(json: {}, key_transformer: self.key_transformer)
66
+ new.tap do |record|
67
+ attributes.each do |attr|
68
+ key = attribute_aliases.key?(attr) ? attribute_aliases[attr] : attr
69
+ record.public_send("#{attr}=", json[key_transformer.transform(key)])
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ def initialize(attributes = {})
76
+ attributes.each do |attr, value|
77
+ public_send("#{attr}=", value)
78
+ end
79
+ end
80
+
81
+ def get(path, params: {}, headers: {})
82
+ self.class.get(path, params: params, headers: headers)
83
+ end
84
+
85
+ def post(path, params: {}, headers: {})
86
+ self.class.post(path, params: params, headers: headers)
87
+ end
88
+
89
+ def put(path, params: {}, headers: {})
90
+ self.class.put(path, params: params, headers: headers)
91
+ end
92
+
93
+ def format_url(url, params)
94
+ self.class.format_url(url, params)
95
+ end
96
+
97
+ def as_json
98
+ self.class.attributes.each_with_object({}) do |attr, hash|
99
+ value = public_send(attr)
100
+
101
+ value = value.as_json if value.respond_to?(:as_json)
102
+ if value.is_a?(Array)
103
+ value = value.map { |v| v.respond_to?(:as_json) ? v.as_json : v }
104
+ end
105
+ value = value.iso8601 if value.respond_to?(:iso8601)
106
+
107
+ key = self.class.key_transformer.transform(attr)
108
+
109
+ hash[key] = value
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FractalApi
4
+ class CamelizerLower
5
+ # Converts this_is_my_string to thisIsMyString and returns it as a symbol.
6
+ def self.transform(underscore_string)
7
+ parts = underscore_string.to_s.split('_')
8
+
9
+ rest = parts[1..-1].map(&:capitalize)
10
+
11
+ rest.unshift(parts[0]).join.to_sym
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fractal_api/base_model'
4
+
5
+ module FractalApi
6
+ class Category < BaseModel
7
+ attributes :id, :name, :details
8
+ end
9
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'faraday_middleware/multi_json'
5
+ require 'fractal_api/configuration'
6
+ require 'fractal_api/faraday_auth'
7
+
8
+ module FractalApi
9
+ class Client
10
+ def initialize
11
+ Faraday::Request.register_middleware fractal_api_auth: -> { FractalApi::FaradayAuth }
12
+ end
13
+
14
+ def get(path, params: {}, headers: {})
15
+ connection.get(path, params, headers).tap do |response|
16
+ handle_response(response)
17
+ end
18
+ end
19
+
20
+ def post(path, params: {}, headers: {})
21
+ connection.post(path, MultiJson.dump(params), headers).tap do |response|
22
+ handle_response(response)
23
+ end
24
+ end
25
+
26
+ def put(path, params: {}, headers: {})
27
+ connection.put(path, MultiJson.dump(params), headers).tap do |response|
28
+ handle_response(response)
29
+ end
30
+ end
31
+
32
+ def connection
33
+ @connection ||= Faraday.new(url: api_url) do |conn|
34
+ conn.request :fractal_api_auth,
35
+ FractalApi.configuration.api_key,
36
+ FractalApi.configuration.partner_id
37
+ conn.response :multi_json, symbolize_keys: true
38
+ conn.response :logger if FractalApi.configuration.debug
39
+ conn.adapter Faraday.default_adapter
40
+ end
41
+ end
42
+
43
+ def api_url
44
+ FractalApi.configuration.base_url
45
+ end
46
+
47
+ def handle_response(response)
48
+ return if response.success?
49
+
50
+ case response.status
51
+ when 400
52
+ raise InvalidRequestError, response.body
53
+ when 401
54
+ raise APIKeyError, response.body
55
+ when 403
56
+ raise UnauthorizedError, response.body
57
+ else
58
+ raise GenericError, response.body
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'fractal_api/base_model'
5
+
6
+ module FractalApi
7
+ class Company < BaseModel
8
+ attributes :id, :name, :description, :website, :industry,
9
+ :address, :external_id, :crn, :created_at
10
+
11
+ def self.create(company)
12
+ result = post('/company/v2/companies', params: company.as_json)
13
+
14
+ build(json: result.body)
15
+ end
16
+
17
+ def self.build(json:)
18
+ super.tap do |record|
19
+ record.created_at = Time.parse(record.created_at) if record.created_at
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FractalApi
4
+ class Configuration
5
+ BASE_URLS = {
6
+ production: '',
7
+ sandbox: 'https://apis.julia-laces.co.uk'
8
+ }.freeze
9
+ AUTH_URLS = {
10
+ production: '',
11
+ sandbox: 'https://auth.julia-laces.co.uk'
12
+ }.freeze
13
+
14
+ attr_accessor :api_key, :partner_id, :environment, :debug
15
+
16
+ def initialize
17
+ @api_key = ''
18
+ @partner_id = ''
19
+ @environment = :sandbox # can be either :sandbox or :production
20
+ @debug = false
21
+ end
22
+
23
+ def base_url
24
+ BASE_URLS[environment.to_sym]
25
+ end
26
+
27
+ def auth_url
28
+ AUTH_URLS[environment.to_sym]
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'base64'
5
+ require 'json'
6
+ require 'fractal_api/base_model'
7
+
8
+ module FractalApi
9
+ class Consent < BaseModel
10
+ attributes :consent_id, :company_id, :bank_id, :permission,
11
+ :date_created, :consent_type, :status, :type, :signin_url
12
+
13
+ def self.create(bank_id, company_id, redirect_url)
14
+ params = { redirect: redirect_url, permission: 'ReadAllBankData' }
15
+ url = format_url(
16
+ '/banking/v2/banks/:bank_id/consents',
17
+ bank_id: bank_id.to_s
18
+ )
19
+ result = post(url, params: params, headers: { 'X-Company-Id' => company_id })
20
+
21
+ build(json: result.body)
22
+ end
23
+
24
+ def self.build(json:)
25
+ super.tap do |record|
26
+ record.date_created = Time.parse(record.date_created) if record.date_created
27
+ end
28
+ end
29
+
30
+ def self.build_from_state(state)
31
+ decoded = JSON.parse(Base64.decode64(state), symbolize_names: true)
32
+
33
+ new(
34
+ consent_id: decoded[:accReq],
35
+ company_id: decoded[:cId],
36
+ bank_id: decoded[:bId]
37
+ )
38
+ rescue JSON::ParserError
39
+ nil
40
+ end
41
+
42
+ def update(code:, id_token:, state:)
43
+ url = format_url(
44
+ '/banking/v2/banks/:bank_id/consents/:consent_id',
45
+ bank_id: bank_id.to_s,
46
+ consent_id: consent_id
47
+ )
48
+
49
+ result = put(
50
+ url,
51
+ params: { code: code, id_token: id_token, state: state },
52
+ headers: { 'X-Company-Id' => company_id }
53
+ )
54
+
55
+ result.body
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FractalApi
4
+ class APIKeyError < StandardError; end
5
+
6
+ class UnauthorizedError < StandardError; end
7
+
8
+ class GenericError < StandardError; end
9
+
10
+ class InvalidRequestError < StandardError; end
11
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fractal_api/authenticate'
4
+ require 'fractal_api/version'
5
+ require 'fractal_api/errors'
6
+
7
+ module FractalApi
8
+ class FaradayAuth < Faraday::Middleware
9
+ def initialize(app, api_key, partner_id, options = {})
10
+ super(app)
11
+ @api_key = api_key
12
+ @partner_id = partner_id
13
+ @options = options
14
+ end
15
+
16
+ def call(env)
17
+ token = FractalApi.global_token || authenticate
18
+
19
+ env[:request_headers]['X-Api-Key'] = @api_key
20
+ env[:request_headers]['X-Partner-Id'] = @partner_id
21
+ env[:request_headers]['Authorization'] = "Bearer #{token}"
22
+ env[:request_headers]['Content-Type'] = 'application/json'
23
+ env[:request_headers]['User-Agent'] = "finpoint/#{FractalApi::VERSION}"
24
+
25
+ @app.call(env)
26
+ end
27
+
28
+ private
29
+
30
+ def authenticate
31
+ Authenticate.new.call(@api_key, @partner_id)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fractal_api/base_model'
4
+
5
+ module FractalApi
6
+ class Merchant < BaseModel
7
+ attributes :id, :name, :category_code, :address_line, :details
8
+ end
9
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FractalApi
4
+ class PagedResponse
5
+ attr_accessor :results, :links, :result_class
6
+
7
+ def initialize(json:, result_class:)
8
+ @result_class = result_class
9
+
10
+ @links = json[:links] || {}
11
+ @results = json.fetch(:results, []).map do |result|
12
+ result_class.build(json: result)
13
+ end
14
+ end
15
+
16
+ def next_page_url
17
+ return unless links[:next]
18
+
19
+ links[:next]
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fractal_api/paged_response'
4
+
5
+ module FractalApi
6
+ class Paginator
7
+ attr_reader :initial_url, :result_class
8
+
9
+ def initialize(initial_url, result_class, &block)
10
+ @initial_url = initial_url
11
+ @result_class = result_class
12
+ @next_page_block = block
13
+ end
14
+
15
+ def first_page
16
+ load_page(initial_url)
17
+ end
18
+
19
+ def each_page(&block)
20
+ response = first_page
21
+
22
+ loop do
23
+ block.call(response.results)
24
+
25
+ next_page_url = response.next_page_url
26
+
27
+ break unless next_page_url
28
+
29
+ response = load_page(next_page_url)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :next_page_block
36
+
37
+ def load_page(url)
38
+ raw_response = next_page_block.call(url)
39
+
40
+ PagedResponse.new(
41
+ json: raw_response.body,
42
+ result_class: result_class
43
+ )
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'fractal_api/base_model'
5
+ require 'fractal_api/category'
6
+ require 'fractal_api/merchant'
7
+ require 'fractal_api/paginator'
8
+
9
+ module FractalApi
10
+ class Transaction < BaseModel
11
+ attributes :id, :bank_id, :account_id, :booking_date, :value_date,
12
+ :reference, :transaction_code, :transaction_sub_code,
13
+ :proprietary_code, :proprietary_sub_code, :description, :amount, :currency,
14
+ :type, :status, :merchant, :category, :external_id, :source
15
+
16
+ def self.all(company_id, filters = {})
17
+ filters = filters.transform_keys { |key| key_transformer.transform(key) }
18
+
19
+ Paginator.new('/banking/v2/transactions', self) do |url|
20
+ get(
21
+ url,
22
+ params: filters,
23
+ headers: { 'X-Company-Id' => company_id }
24
+ )
25
+ end
26
+ end
27
+
28
+ def self.build(json:)
29
+ super.tap do |record|
30
+ record.merchant = Merchant.build(json: (record.merchant || {}))
31
+ record.category = Category.build(json: (record.category || {}))
32
+ record.booking_date = Time.parse(record.booking_date) if record.booking_date
33
+ record.value_date = Time.parse(record.value_date) if record.value_date
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FractalApi
4
+ VERSION = '0.1.0'
5
+ end
metadata ADDED
@@ -0,0 +1,227 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fractal_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Finpoint
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-03-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday_middleware
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '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'
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday_middleware-multi_json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: multi_json
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: '2.2'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.2'
83
+ - !ruby/object:Gem::Dependency
84
+ name: dotenv
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
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
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: vcr
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: webmock
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description: Client for FractalApi https://docs.askfractal.com
168
+ email:
169
+ - nenad@finpoint.co.uk
170
+ executables: []
171
+ extensions: []
172
+ extra_rdoc_files: []
173
+ files:
174
+ - ".gitignore"
175
+ - ".rubocop.yml"
176
+ - ".ruby-gemset"
177
+ - ".ruby-version"
178
+ - Gemfile
179
+ - Gemfile.lock
180
+ - README.md
181
+ - Rakefile
182
+ - bin/console
183
+ - bin/setup
184
+ - fractal_api.gemspec
185
+ - lib/fractal_api.rb
186
+ - lib/fractal_api/authenticate.rb
187
+ - lib/fractal_api/balance.rb
188
+ - lib/fractal_api/bank.rb
189
+ - lib/fractal_api/bank_account.rb
190
+ - lib/fractal_api/bank_account_scheme.rb
191
+ - lib/fractal_api/base_model.rb
192
+ - lib/fractal_api/camelizer_lower.rb
193
+ - lib/fractal_api/category.rb
194
+ - lib/fractal_api/client.rb
195
+ - lib/fractal_api/company.rb
196
+ - lib/fractal_api/configuration.rb
197
+ - lib/fractal_api/consent.rb
198
+ - lib/fractal_api/errors.rb
199
+ - lib/fractal_api/faraday_auth.rb
200
+ - lib/fractal_api/merchant.rb
201
+ - lib/fractal_api/paged_response.rb
202
+ - lib/fractal_api/paginator.rb
203
+ - lib/fractal_api/transaction.rb
204
+ - lib/fractal_api/version.rb
205
+ homepage: https://www.finpoint.co.uk
206
+ licenses: []
207
+ metadata: {}
208
+ post_install_message:
209
+ rdoc_options: []
210
+ require_paths:
211
+ - lib
212
+ required_ruby_version: !ruby/object:Gem::Requirement
213
+ requirements:
214
+ - - "~>"
215
+ - !ruby/object:Gem::Version
216
+ version: '2.0'
217
+ required_rubygems_version: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - ">="
220
+ - !ruby/object:Gem::Version
221
+ version: '0'
222
+ requirements: []
223
+ rubygems_version: 3.2.11
224
+ signing_key:
225
+ specification_version: 4
226
+ summary: FractalApi client
227
+ test_files: []