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.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +159 -0
- data/LICENSE.txt +20 -0
- data/README.md +82 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/flinks.gemspec +96 -0
- data/lib/flinks.rb +12 -0
- data/lib/flinks/api/account.rb +74 -0
- data/lib/flinks/api/card.rb +13 -0
- data/lib/flinks/api/refresh.rb +28 -0
- data/lib/flinks/api/statement.rb +30 -0
- data/lib/flinks/client.rb +36 -0
- data/lib/flinks/error.rb +107 -0
- data/lib/flinks/request.rb +66 -0
- data/lib/flinks/version.rb +3 -0
- data/spec/lib/api/account.rb +131 -0
- data/spec/lib/api/card.rb +18 -0
- data/spec/lib/api/refresh.rb +40 -0
- data/spec/lib/api/statement.rb +61 -0
- data/spec/lib/client_spec.rb +23 -0
- data/spec/spec_helper.rb +12 -0
- metadata +236 -0
data/lib/flinks.rb
ADDED
@@ -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
|
data/lib/flinks/error.rb
ADDED
@@ -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,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
|