flinks 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,12 @@
1
+ require 'flinks/client'
2
+
3
+ module Flinks
4
+
5
+ # Alias for Flinks::Client.new
6
+ #
7
+ # @return [Flinks::Client]
8
+ def new(options = {})
9
+ Flinks::Client.new(options)
10
+ end
11
+ module_function :new
12
+ end
@@ -0,0 +1,74 @@
1
+ module Flinks
2
+ module API
3
+ module Account
4
+ AccountSummaryRequestSchema = Dry::Validation.Schema do
5
+ required(:request_id).filled(:str?)
6
+ optional(:direct_refresh).maybe(:bool?)
7
+ optional(:with_balance).maybe(:bool?)
8
+ optional(:with_transactions).maybe(:bool?)
9
+ optional(:with_account_identity).maybe(:bool?)
10
+ optional(:most_recent).maybe(:bool?)
11
+ optional(:most_recent_cached).maybe(:bool?)
12
+ end
13
+
14
+ AccountDetailRequestSchema = Dry::Validation.Schema do
15
+ required(:request_id).filled(:str?)
16
+ optional(:with_account_identity).maybe(:bool?)
17
+ optional(:with_kyc).maybe(:bool?)
18
+ optional(:with_transactions).maybe(:bool?)
19
+ optional(:with_balance).maybe(:bool?)
20
+ optional(:get_mfa_questions_answers).maybe(:bool?)
21
+ optional(:date_from).maybe(:date?)
22
+ optional(:date_to) { date? | date_time? }
23
+ optional(:accounts_filter).each(:str?)
24
+
25
+ optional(:refresh_delta).each do
26
+ schema do
27
+ required(:account_id).filled(:str?)
28
+ required(:transaction_id).filled(:str?)
29
+ end
30
+ end
31
+
32
+ optional(:days_of_transactions).included_in?(['Days90', 'Days360'])
33
+ optional(:most_recent).maybe(:bool?)
34
+ optional(:most_recent_cached).maybe(:bool?)
35
+ end
36
+
37
+ # @see https://sandbox-api.flinks.io/Readme/#get-accounts-summary
38
+ # @param request_id [String]
39
+ # @param options [Hash]
40
+ # @return [Hash]
41
+ def accounts_summary(request_id:, options: {})
42
+ payload = AccountSummaryRequestSchema.call(options.merge(request_id: request_id))
43
+ raise ArgumentError, error_message(payload) unless payload.success?
44
+
45
+ post("#{customer_id}/BankingServices/GetAccountsSummary", body: payload.to_h)
46
+ end
47
+
48
+ # @see https://sandbox-api.flinks.io/Readme/#get-accounts-detail
49
+ # @param request_id [String]
50
+ # @param options [Hash]
51
+ # @return [Hash]
52
+ def accounts_detail(request_id:, options: {})
53
+ payload = AccountDetailRequestSchema.call(options.merge(request_id: request_id))
54
+ raise ArgumentError, error_message(payload) unless payload.success?
55
+
56
+ post("#{customer_id}/BankingServices/GetAccountsDetail", body: payload.to_h)
57
+ end
58
+
59
+ # @see https://sandbox-api.flinks.io/Readme/#get-accounts-summary
60
+ # @param request_id [String]
61
+ # @return [Hash]
62
+ def accounts_summary_async(request_id:)
63
+ get("#{customer_id}/BankingServices/GetAccountsSummaryAsync/#{request_id}")
64
+ end
65
+
66
+ # @see https://sandbox-api.flinks.io/Readme/#get-accounts-detail
67
+ # @param request_id [String]
68
+ # @return [Hash]
69
+ def accounts_detail_async(request_id:)
70
+ get("#{customer_id}/BankingServices/GetAccountsDetailAsync/#{request_id}")
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,13 @@
1
+ module Flinks
2
+ module API
3
+ module Card
4
+
5
+ # https://sandbox-api.flinks.io/Readme/#delete-card-information
6
+ # @param card_id [String]
7
+ # @return [Hash]
8
+ def delete_card(card_id:)
9
+ get("#{customer_id}/BankingServices/DeleteCard/#{card_id}")
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ module Flinks
2
+ module API
3
+ module Refresh
4
+
5
+ # @see https://sandbox-api.flinks.io/Readme/#scheduling-background-refresh
6
+ # @param login_id [String]
7
+ # @return [Hash]
8
+ def activate_scheduled_refresh(login_id:)
9
+ patch("#{customer_id}/BankingServices/ActivateScheduledRefresh/#{login_id}")
10
+ end
11
+
12
+ # @see https://sandbox-api.flinks.io/Readme/#scheduling-background-refresh
13
+ # @param login_id [String]
14
+ # @return [Hash]
15
+ def deactivate_scheduled_refresh(login_id:)
16
+ patch("#{customer_id}/BankingServices/DeactivateScheduledRefresh/#{login_id}")
17
+ end
18
+
19
+ # @see https://sandbox-api.flinks.io/Readme/#scheduling-background-refresh
20
+ # @param activated [Boolean]
21
+ # @param login_id [String]
22
+ # @return [Hash]
23
+ def set_scheduled_refresh(activated, login_id:)
24
+ patch("#{customer_id}/BankingServices/SetScheduledRefresh", params: { login_id: login_id, is_activated: activated })
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ module Flinks
2
+ module API
3
+ module Statement
4
+
5
+ StatementRequestSchema = Dry::Validation.Schema do
6
+ optional(:accounts_filter).each(:str?)
7
+ optional(:number_of_statements).included_in?(['MostRecent', 'Months3', 'Months12'])
8
+ optional(:most_recent).maybe(:bool?)
9
+ optional(:most_recent_cached).maybe(:bool?)
10
+ end
11
+
12
+ # @see https://sandbox-api.flinks.io/Readme/#get-pdf-statements
13
+ # @param options [Hash]
14
+ # @return [Hash]
15
+ def statements(options: {})
16
+ payload = StatementRequestSchema.call(options)
17
+ raise ArgumentError, error_message(payload) unless payload.success?
18
+
19
+ post("#{customer_id}/BankingServices/GetStatements", body: payload.to_h)
20
+ end
21
+
22
+ # @see https://sandbox-api.flinks.io/Readme/#get-pdf-statements
23
+ # @param request_id [String]
24
+ # @return [Hash]
25
+ def statements_async(request_id:)
26
+ get("#{customer_id}/BankingServices/GetStatementsAsync/#{request_id}")
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,36 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext/object'
3
+ require 'dry-initializer'
4
+ require 'dry-validation'
5
+
6
+ require 'flinks/version'
7
+ require 'flinks/request'
8
+ require 'flinks/api/account'
9
+ require 'flinks/api/card'
10
+ require 'flinks/api/refresh'
11
+ require 'flinks/api/statement'
12
+
13
+ module Flinks
14
+ class Client
15
+ extend Dry::Initializer
16
+
17
+ include Flinks::Request
18
+ include Flinks::API::Account
19
+ include Flinks::API::Card
20
+ include Flinks::API::Refresh
21
+ include Flinks::API::Statement
22
+
23
+ option :customer_id
24
+ option :api_endpoint, default: proc { "https://sandbox.flinks.io/v3/" }
25
+ option :user_agent, default: proc { "Flinks Ruby Gem #{Flinks::VERSION}" }
26
+ option :on_error, default: proc { proc {} }
27
+ option :debug, default: proc { false }
28
+
29
+ #
30
+ # @param validation [Dry::Validation::Result]
31
+ # @return [String]
32
+ def error_message(validation)
33
+ validation.messages(full: true).values.flatten.to_sentence
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,107 @@
1
+ module Flinks
2
+ class Error < StandardError
3
+ attr_reader :response, :code
4
+
5
+ # @param response [HTTP::Response]
6
+ # @return [Flinks::Error]
7
+ def self.from_response(response)
8
+ klass = case response.code
9
+ when 400 then Flinks::BadRequest
10
+ when 401 then Flinks::Unauthorized
11
+ when 403 then error_for_403(response)
12
+ when 404 then Flinks::NotFound
13
+ when 405 then Flinks::MethodNotAllowed
14
+ when 406 then Flinks::NotAcceptable
15
+ when 409 then Flinks::Conflict
16
+ when 415 then Flinks::UnsupportedMediaType
17
+ when 422 then Flinks::UnprocessableEntity
18
+ when 400..499 then Flinks::ClientError
19
+ when 500 then Flinks::InternalServerError
20
+ when 501 then Flinks::NotImplemented
21
+ when 502 then Flinks::BadGateway
22
+ when 503 then Flinks::ServiceUnavailable
23
+ when 500..599 then Flinks::ServerError
24
+ else
25
+ self
26
+ end
27
+
28
+ klass.new(response)
29
+ end
30
+
31
+ # @param response [HTTP::Response]
32
+ # @return [Flinks::Error]
33
+ def self.error_for_403(response)
34
+ if response.parse['FlinksCode'] == 'TOO_MANY_REQUESTS'
35
+ Flinks::TooManyRequests
36
+ else
37
+ Flinks::Forbidden
38
+ end
39
+ end
40
+
41
+ # @param response [HTTP::Response]
42
+ # @return [Flinks::Error]
43
+ def initialize(response)
44
+ @response = response
45
+ @code = response.code
46
+
47
+ super(build_message)
48
+ end
49
+
50
+ # @return [String]
51
+ def build_message
52
+ message = response.parse['Message']
53
+ message << " - FlinksCode: #{response.parse['FlinksCode']}"
54
+ rescue HTTP::Error
55
+ response.reason
56
+ end
57
+ end
58
+
59
+ # Raised when API returns a 400..499 HTTP status code
60
+ class ClientError < Error; end
61
+
62
+ # Raised when API returns a 400 HTTP status code
63
+ class BadRequest < ClientError; end
64
+
65
+ # Raised when API returns a 401 HTTP status code
66
+ class Unauthorized < ClientError; end
67
+
68
+ # Raised when API returns a 403 HTTP status code
69
+ class Forbidden < ClientError; end
70
+
71
+ # Raised when API returns a 403 HTTP status code
72
+ # Rate limit exceeded
73
+ class TooManyRequests < ClientError; end
74
+
75
+ # Raised when API returns a 404 HTTP status code
76
+ class NotFound < ClientError; end
77
+
78
+ # Raised when API returns a 405 HTTP status code
79
+ class MethodNotAllowed < ClientError; end
80
+
81
+ # Raised when API returns a 406 HTTP status code
82
+ class NotAcceptable < ClientError; end
83
+
84
+ # Raised when API returns a 409 HTTP status code
85
+ class Conflict < ClientError; end
86
+
87
+ # Raised when API returns a 415 HTTP status code
88
+ class UnsupportedMediaType < ClientError; end
89
+
90
+ # Raised when API returns a 422 HTTP status code
91
+ class UnprocessableEntity < ClientError; end
92
+
93
+ # Raised when API returns a 500..599 HTTP status code
94
+ class ServerError < Error; end
95
+
96
+ # Raised when API returns a 500 HTTP status code
97
+ class InternalServerError < ServerError; end
98
+
99
+ # Raised when API returns a 501 HTTP status code
100
+ class NotImplemented < ServerError; end
101
+
102
+ # Raised when API returns a 502 HTTP status code
103
+ class BadGateway < ServerError; end
104
+
105
+ # Raised when API returns a 503 HTTP status code
106
+ class ServiceUnavailable < ServerError; end
107
+ end
@@ -0,0 +1,66 @@
1
+ require 'http'
2
+
3
+ require 'flinks/error'
4
+
5
+ module Flinks
6
+ module Request
7
+
8
+ # Performs a HTTP Get request
9
+ #
10
+ # @param path [String]
11
+ # @param params [Hash]
12
+ def get(path, params: {})
13
+ request(:get, URI.parse(api_endpoint).merge(path), params: params)
14
+ end
15
+
16
+ # Performs a HTTP Post request
17
+ #
18
+ # @param path [String]
19
+ # @param params [Hash]
20
+ # @param body [Hash]
21
+ def post(path, params: {}, body: {})
22
+ request(:post, URI.parse(api_endpoint).merge(path), params: params, body: body)
23
+ end
24
+
25
+ # Performs a HTTP Patch request
26
+ #
27
+ # @param path [String]
28
+ # @param params [Hash]
29
+ # @param body [Hash]
30
+ def patch(path, params: {}, body: {})
31
+ request(:patch, URI.parse(api_endpoint).merge(path), params: params, body: body)
32
+ end
33
+
34
+
35
+ private
36
+
37
+ # @return [HTTP::Client]
38
+ # @raise [Flinks::Error]
39
+ def request(method, path, params: {}, body: {})
40
+ headers = {
41
+ 'Accept' => "application/json",
42
+ 'User-Agent' => user_agent
43
+ }
44
+
45
+ # Build payload
46
+ payload = body.transform_keys { |k| k.to_s.camelize }
47
+
48
+ # Perform request
49
+ response = Http.headers(headers).send(method, path, params: params, json: payload)
50
+
51
+ if debug
52
+ p response
53
+ end
54
+
55
+ # Pass on errors when HTTP status included in 400 to 599
56
+ if (400..599).include?(response.code)
57
+ raise Error.from_response(response)
58
+
59
+ on_error.call(response.code, response.reason, body)
60
+ end
61
+
62
+ # Return parsed json body
63
+ response.parse
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,3 @@
1
+ module Flinks
2
+ VERSION = File.read('VERSION')
3
+ end
@@ -0,0 +1,131 @@
1
+ require 'spec_helper'
2
+
3
+ describe Flinks::API::Account do
4
+ let(:api_endpoint) { Flinks::Client.dry_initializer.definitions[:api_endpoint].default.call }
5
+ let(:client) { Flinks.new(customer_id: 'customer_id') }
6
+ let(:request_id) { 'request_id' }
7
+
8
+ describe '#accounts_summary' do
9
+ before do
10
+ stub_request(:post, /#{api_endpoint}/)
11
+ .to_return(status: 200, body: "{}", headers: { 'Content-Type'=>'application/json' })
12
+ end
13
+
14
+ it "returns an object" do
15
+ expect(client.accounts_summary(request_id: request_id)).to be_a(Hash)
16
+ end
17
+
18
+ context "with valid options" do
19
+ let(:options) do
20
+ {
21
+ direct_refresh: true,
22
+ with_balance: true,
23
+ with_transactions: true,
24
+ with_account_identity: true,
25
+ most_recent: true,
26
+ most_recent_cached: true
27
+ }
28
+ end
29
+
30
+ it "returns an object" do
31
+ expect(client.accounts_summary(request_id: request_id, options: options)).to be_a(Hash)
32
+ end
33
+ end
34
+
35
+ context "with invalid options" do
36
+ let(:options) do
37
+ {
38
+ direct_refresh: 'invalid',
39
+ with_balance: 'invalid',
40
+ with_transactions: 'invalid',
41
+ with_account_identity: 'invalid',
42
+ most_recent: 'invalid',
43
+ most_recent_cached: 'invalid'
44
+ }
45
+ end
46
+
47
+ it "raises an error" do
48
+ expect {
49
+ client.accounts_summary(request_id: request_id, options: options)
50
+ }.to raise_error(ArgumentError)
51
+ end
52
+ end
53
+ end
54
+
55
+ describe '#accounts_detail' do
56
+ before do
57
+ stub_request(:post, /#{api_endpoint}/)
58
+ .to_return(status: 200, body: "{}", headers: { 'Content-Type'=>'application/json' })
59
+ end
60
+
61
+ context "with valid options" do
62
+ let(:options) do
63
+ {
64
+ with_account_identity: true,
65
+ with_kyc: true,
66
+ with_transactions: true,
67
+ with_balance: true,
68
+ get_mfa_questions_answers: true,
69
+ date_from: Date.today,
70
+ date_to: Date.today,
71
+ accounts_filter: ['string'],
72
+ refresh_delta: [{ account_id: 'id', transaction_id: 'id' }],
73
+ days_of_transactions: 'Days90',
74
+ most_recent: true,
75
+ most_recent_cached: true
76
+ }
77
+ end
78
+
79
+ it "returns an object" do
80
+ expect(client.accounts_detail(request_id: request_id, options: options)).to be_a(Hash)
81
+ end
82
+ end
83
+
84
+ context "with invalid options" do
85
+ let(:options) do
86
+ {
87
+ with_account_identity: 'invalid',
88
+ with_kyc: 'invalid',
89
+ with_transactions: 'invalid',
90
+ with_balance: 'invalid',
91
+ get_mfa_questions_answers: 'invalid',
92
+ date_from: 'invalid',
93
+ date_to: 'invalid',
94
+ accounts_filter: 'invalid',
95
+ refresh_delta: 'invalid',
96
+ days_of_transactions: 'invalid',
97
+ most_recent: 'invalid',
98
+ most_recent_cached: 'invalid'
99
+ }
100
+ end
101
+
102
+ it "raises an error" do
103
+ expect {
104
+ client.accounts_detail(request_id: request_id, options: options)
105
+ }.to raise_error(ArgumentError)
106
+ end
107
+ end
108
+ end
109
+
110
+ describe '#accounts_summary_async' do
111
+ before do
112
+ stub_request(:get, /#{api_endpoint}/)
113
+ .to_return(status: 200, body: "{}", headers: { 'Content-Type'=>'application/json' })
114
+ end
115
+
116
+ it "returns an object" do
117
+ expect(client.accounts_summary_async(request_id: request_id)).to be_a(Hash)
118
+ end
119
+ end
120
+
121
+ describe '#accounts_detail_async' do
122
+ before do
123
+ stub_request(:get, /#{api_endpoint}/)
124
+ .to_return(status: 200, body: "{}", headers: { 'Content-Type'=>'application/json' })
125
+ end
126
+
127
+ it "returns an object" do
128
+ expect(client.accounts_detail_async(request_id: request_id)).to be_a(Hash)
129
+ end
130
+ end
131
+ end