fractal_api 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.
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: []