flinks 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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