virementmaitrise 0.6.2
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/.gitignore +23 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +59 -0
- data/LICENSE.txt +674 -0
- data/README.md +404 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/exemples/ais.rb +53 -0
- data/exemples/pis.rb +148 -0
- data/exemples/ressources.rb +23 -0
- data/lib/virementmaitrise/ais_client.rb +112 -0
- data/lib/virementmaitrise/api/ais/account_holders.rb +60 -0
- data/lib/virementmaitrise/api/ais/accounts.rb +62 -0
- data/lib/virementmaitrise/api/ais/authorize.rb +71 -0
- data/lib/virementmaitrise/api/ais/authorize_decoupled.rb +67 -0
- data/lib/virementmaitrise/api/ais/connect.rb +66 -0
- data/lib/virementmaitrise/api/ais/delete_customer.rb +53 -0
- data/lib/virementmaitrise/api/ais/transactions.rb +62 -0
- data/lib/virementmaitrise/api/auth/authentication.rb +80 -0
- data/lib/virementmaitrise/api/pis/connect.rb +80 -0
- data/lib/virementmaitrise/api/pis/initiate.rb +54 -0
- data/lib/virementmaitrise/api/pis/payments.rb +55 -0
- data/lib/virementmaitrise/api/pis/refund.rb +67 -0
- data/lib/virementmaitrise/api/pis/request_to_pay.rb +61 -0
- data/lib/virementmaitrise/api/pis/settlements.rb +50 -0
- data/lib/virementmaitrise/api/ressources/applications.rb +57 -0
- data/lib/virementmaitrise/api/ressources/providers.rb +63 -0
- data/lib/virementmaitrise/api/ressources/test_accounts.rb +62 -0
- data/lib/virementmaitrise/base_url.rb +23 -0
- data/lib/virementmaitrise/endpoints/ais.rb +16 -0
- data/lib/virementmaitrise/endpoints/authentication.rb +13 -0
- data/lib/virementmaitrise/endpoints/pis.rb +16 -0
- data/lib/virementmaitrise/endpoints/ressources.rb +13 -0
- data/lib/virementmaitrise/exceptions.rb +62 -0
- data/lib/virementmaitrise/faraday/authentication/connection.rb +177 -0
- data/lib/virementmaitrise/pis_client.rb +118 -0
- data/lib/virementmaitrise/utils/constants.rb +11 -0
- data/lib/virementmaitrise/utils/crypto.rb +75 -0
- data/lib/virementmaitrise/utils/date.rb +15 -0
- data/lib/virementmaitrise/utils/json_parser.rb +37 -0
- data/lib/virementmaitrise/utils/query_builder.rb +18 -0
- data/lib/virementmaitrise/utils/validation.rb +32 -0
- data/lib/virementmaitrise/version.rb +5 -0
- data/lib/virementmaitrise.rb +66 -0
- data/virementmaitrise.gemspec +46 -0
- metadata +157 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'virementmaitrise/utils/json_parser'
|
|
5
|
+
|
|
6
|
+
module VirementMaitrise
|
|
7
|
+
class ValidationException < StandardError; end
|
|
8
|
+
|
|
9
|
+
class CryptoException < StandardError; end
|
|
10
|
+
|
|
11
|
+
class ApiException < StandardError
|
|
12
|
+
attr_reader :status, :code, :log_id, :errors
|
|
13
|
+
|
|
14
|
+
def initialize(status:, code: nil, log_id: nil, errors: [])
|
|
15
|
+
@status = status
|
|
16
|
+
@code = code
|
|
17
|
+
@log_id = log_id
|
|
18
|
+
@errors = errors
|
|
19
|
+
|
|
20
|
+
error_message = build_message
|
|
21
|
+
super(error_message)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def build_message
|
|
27
|
+
msg = "API Error (status: #{@status})"
|
|
28
|
+
msg += ", code: #{@code}" if @code
|
|
29
|
+
msg += ", log_id: #{@log_id}" if @log_id
|
|
30
|
+
|
|
31
|
+
if @errors.any?
|
|
32
|
+
formatted_errors = @errors.map do |error|
|
|
33
|
+
if error.is_a?(Hash)
|
|
34
|
+
error.map { |key, value| " #{key}: #{value}" }.join("\n")
|
|
35
|
+
else
|
|
36
|
+
error.to_s
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
msg += "\nErrors:\n#{formatted_errors.join("\n")}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
msg
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
module ApiExceptionHelper
|
|
47
|
+
def self.error(res)
|
|
48
|
+
body = VirementMaitrise::Utils::JsonParser.safe_parse(res.body, context: 'error response')
|
|
49
|
+
|
|
50
|
+
code = body['code'] unless body['code'].to_s.strip.empty?
|
|
51
|
+
log_id = body['log_id'] unless body['log_id'].to_s.strip.empty?
|
|
52
|
+
errors_array = body['errors'] || body['meta'] || []
|
|
53
|
+
|
|
54
|
+
raise VirementMaitrise::ApiException.new(
|
|
55
|
+
status: res.status,
|
|
56
|
+
code: code,
|
|
57
|
+
log_id: log_id,
|
|
58
|
+
errors: errors_array.compact
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
require 'faraday'
|
|
5
|
+
require 'uri'
|
|
6
|
+
require 'virementmaitrise/utils/crypto'
|
|
7
|
+
require 'virementmaitrise/utils/date'
|
|
8
|
+
|
|
9
|
+
module VirementMaitrise
|
|
10
|
+
module Faraday
|
|
11
|
+
module Authentication
|
|
12
|
+
class Connection
|
|
13
|
+
class << self
|
|
14
|
+
def connection(url)
|
|
15
|
+
::Faraday.new(url: url) do |faraday|
|
|
16
|
+
faraday.adapter ::Faraday.default_adapter
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def post(url:, req_body: nil, client: nil, custom_content_type: nil, bearer: nil, secure_headers: false, additional_headers: nil, disableAuthorization: nil)
|
|
21
|
+
@client = client
|
|
22
|
+
conn = connection(url)
|
|
23
|
+
|
|
24
|
+
res = conn.post do |req|
|
|
25
|
+
req.options.params_encoder = Faraday::DisabledEncoder
|
|
26
|
+
|
|
27
|
+
encoded_body =
|
|
28
|
+
if custom_content_type == 'application/json'
|
|
29
|
+
req_body.is_a?(String) ? req_body : req_body.to_json
|
|
30
|
+
else
|
|
31
|
+
URI.encode_www_form(req_body || {})
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
req.headers = req_headers(custom_content_type, bearer, secure_headers, additional_headers, disableAuthorization,
|
|
35
|
+
method: 'post', body: encoded_body, url: url)
|
|
36
|
+
req.body = encoded_body
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
!res.success? ? VirementMaitrise::ApiExceptionHelper.error(res) : res
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def get(url:, req_body: nil, client: nil, custom_content_type: nil, bearer: nil, secure_headers: false, additional_headers: nil, disableAuthorization: nil)
|
|
43
|
+
@client = client
|
|
44
|
+
conn = connection(url)
|
|
45
|
+
|
|
46
|
+
res = conn.get do |req|
|
|
47
|
+
req.options.params_encoder = Faraday::DisabledEncoder
|
|
48
|
+
|
|
49
|
+
encoded_body =
|
|
50
|
+
if req_body.nil?
|
|
51
|
+
nil
|
|
52
|
+
elsif custom_content_type == 'application/json'
|
|
53
|
+
req_body.is_a?(String) ? req_body : req_body.to_json
|
|
54
|
+
else
|
|
55
|
+
URI.encode_www_form(req_body)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
req.headers = req_headers(custom_content_type, bearer, secure_headers, additional_headers, disableAuthorization,
|
|
59
|
+
method: 'get', body: encoded_body, url: url)
|
|
60
|
+
req.body = encoded_body
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
!res.success? ? VirementMaitrise::ApiExceptionHelper.error(res) : res
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def delete(url:, req_body: nil, client: nil, custom_content_type: nil, bearer: nil, secure_headers: false, additional_headers: nil, disableAuthorization: nil)
|
|
67
|
+
@client = client
|
|
68
|
+
conn = connection(url)
|
|
69
|
+
|
|
70
|
+
res = conn.delete do |req|
|
|
71
|
+
req.options.params_encoder = Faraday::DisabledEncoder
|
|
72
|
+
|
|
73
|
+
encoded_body =
|
|
74
|
+
if req_body.nil?
|
|
75
|
+
nil
|
|
76
|
+
elsif custom_content_type == 'application/json'
|
|
77
|
+
req_body.is_a?(String) ? req_body : req_body.to_json
|
|
78
|
+
else
|
|
79
|
+
URI.encode_www_form(req_body)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
req.headers = req_headers(custom_content_type, bearer, secure_headers, additional_headers, disableAuthorization,
|
|
83
|
+
method: 'delete', body: encoded_body, url: url)
|
|
84
|
+
req.body = encoded_body
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
!res.success? ? VirementMaitrise::ApiExceptionHelper.error(res) : res
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def req_headers(custom_content_type, bearer, secure_headers, additional_headers, disableAuthorization, url:, method: '', body: {})
|
|
91
|
+
if !additional_headers.nil? && !additional_headers.is_a?(Hash)
|
|
92
|
+
raise VirementMaitrise::ValidationException, 'additional_headers must be an object'
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
client_token = Base64.strict_encode64("#{@client.app_id}:#{@client.app_secret}")
|
|
96
|
+
|
|
97
|
+
headers = {
|
|
98
|
+
'Accept' => 'application/json',
|
|
99
|
+
'User-Agent' => "Virement Maitrisé Ruby SDK v #{VirementMaitrise::VERSION}",
|
|
100
|
+
'Content-Type' => custom_content_type || 'application/x-www-form-urlencoded'
|
|
101
|
+
}
|
|
102
|
+
headers['Authorization'] = bearer || "Basic #{client_token}" unless disableAuthorization
|
|
103
|
+
headers = headers.merge(additional_headers) unless additional_headers.nil?
|
|
104
|
+
headers.merge(secure_headers ? req_secure_headers(body: body, url: url, method: method) : {})
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def req_secure_headers(body: nil, url: '', method: '')
|
|
108
|
+
payload = if body.nil?
|
|
109
|
+
''
|
|
110
|
+
elsif body.is_a?(String)
|
|
111
|
+
body
|
|
112
|
+
else
|
|
113
|
+
body.to_json
|
|
114
|
+
end
|
|
115
|
+
path_name = URI(url).path
|
|
116
|
+
search_params = URI(url).query
|
|
117
|
+
|
|
118
|
+
path_and_query = [path_name, search_params].compact.join('?')
|
|
119
|
+
request_target = "#{method.downcase} #{path_and_query}"
|
|
120
|
+
date = VirementMaitrise::Utils::Date.header_time.to_s
|
|
121
|
+
x_request_id = VirementMaitrise::Utils::Crypto.generate_uuid
|
|
122
|
+
|
|
123
|
+
should_include_digest = %w[post put patch].include?(method.downcase) && body && !payload.empty?
|
|
124
|
+
digest = should_include_digest ? load_digest(payload) : {}
|
|
125
|
+
|
|
126
|
+
headers = {
|
|
127
|
+
'Date' => date,
|
|
128
|
+
'X-Request-ID' => x_request_id
|
|
129
|
+
}.merge(digest)
|
|
130
|
+
|
|
131
|
+
headers['Signature'] =
|
|
132
|
+
VirementMaitrise::Utils::Crypto.create_signature_header(
|
|
133
|
+
{ '(request-target)' => request_target }.merge(headers), @client
|
|
134
|
+
)
|
|
135
|
+
headers
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def load_digest(payload)
|
|
139
|
+
{ 'Digest' => "SHA-256=#{VirementMaitrise::Utils::Crypto.hash_base64(payload)}" }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
module DisabledEncoder
|
|
147
|
+
class << self
|
|
148
|
+
extend Forwardable
|
|
149
|
+
def_delegators :'Faraday::Utils', :escape, :unescape
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def self.encode(params)
|
|
153
|
+
return nil if params.nil?
|
|
154
|
+
|
|
155
|
+
new_params = {}
|
|
156
|
+
params.each do |key, value|
|
|
157
|
+
if value.instance_of?(Hash)
|
|
158
|
+
value.each do |subkey, subvalue|
|
|
159
|
+
new_params["#{key}[#{subkey}]"] = subvalue
|
|
160
|
+
end
|
|
161
|
+
else
|
|
162
|
+
new_params[key] = value
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
new_params.map { |key, value| "#{key}=#{value}" }.join('&').to_s
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
class << self
|
|
170
|
+
attr_accessor :sort_params
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Useful default for OAuth and caching.
|
|
174
|
+
@sort_params = true
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'virementmaitrise/api/pis/connect'
|
|
4
|
+
require 'virementmaitrise/api/pis/request_to_pay'
|
|
5
|
+
require 'virementmaitrise/api/pis/payments'
|
|
6
|
+
require 'virementmaitrise/api/pis/initiate'
|
|
7
|
+
require 'virementmaitrise/api/pis/refund'
|
|
8
|
+
require 'virementmaitrise/api/pis/settlements'
|
|
9
|
+
|
|
10
|
+
require 'virementmaitrise/api/ressources/providers'
|
|
11
|
+
require 'virementmaitrise/api/ressources/applications'
|
|
12
|
+
require 'virementmaitrise/api/ressources/test_accounts'
|
|
13
|
+
require 'virementmaitrise/utils/json_parser'
|
|
14
|
+
|
|
15
|
+
module VirementMaitrise
|
|
16
|
+
class PisClient
|
|
17
|
+
DEFAULT_ENVIRONMENT = 'sandbox'
|
|
18
|
+
ENVIRONMENTS = %w[test sandbox production].freeze
|
|
19
|
+
|
|
20
|
+
def initialize(config)
|
|
21
|
+
raise ArgumentError, "app_id is required" if config[:app_id].nil?
|
|
22
|
+
@app_id = config[:app_id]
|
|
23
|
+
raise ArgumentError, "app_secret is required" if config[:app_secret].nil?
|
|
24
|
+
@app_secret = config[:app_secret]
|
|
25
|
+
raise ArgumentError, "private_key is required" if config[:private_key].nil?
|
|
26
|
+
@private_key = config[:private_key]
|
|
27
|
+
|
|
28
|
+
env_value = (config[:environment] || DEFAULT_ENVIRONMENT).to_s.downcase
|
|
29
|
+
unless ENVIRONMENTS.include?(env_value)
|
|
30
|
+
raise ArgumentError, "#{env_value} is not a valid environment. Options are [#{ENVIRONMENTS.join(', ')}]"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
@environment = env_value
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Getters
|
|
37
|
+
attr_reader :app_id, :app_secret, :private_key, :environment, :token, :token_expires_in
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def require_token!
|
|
42
|
+
raise VirementMaitrise::ValidationException, 'Token is required. Please call generate_token first.' if @token.nil?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
public
|
|
46
|
+
|
|
47
|
+
# Methodes
|
|
48
|
+
def generate_token
|
|
49
|
+
res = VirementMaitrise::Authentication.get_access_token self
|
|
50
|
+
body = VirementMaitrise::Utils::JsonParser.safe_parse(res.body, context: 'access token')
|
|
51
|
+
|
|
52
|
+
@token = body['access_token']
|
|
53
|
+
@token_expires_in = body['expires_in']
|
|
54
|
+
|
|
55
|
+
body
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def connect(payload, state, redirect_uri = nil, origin_uri = nil, with_virtualbeneficiary: false)
|
|
59
|
+
require_token!
|
|
60
|
+
res = VirementMaitrise::Pis::Connect.generate self, payload.dup, state, redirect_uri, origin_uri, with_virtualbeneficiary: with_virtualbeneficiary
|
|
61
|
+
|
|
62
|
+
VirementMaitrise::Utils::JsonParser.safe_parse(res.body, context: 'connect')
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def request_to_pay(payload, x_language, redirect_uri = nil)
|
|
66
|
+
require_token!
|
|
67
|
+
res = VirementMaitrise::Pis::RequestToPay.generate self, payload, x_language, redirect_uri
|
|
68
|
+
|
|
69
|
+
VirementMaitrise::Utils::JsonParser.safe_parse(res.body, context: 'request to pay')
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def initiate(payload, provider_id, redirect_uri, state = nil)
|
|
73
|
+
require_token!
|
|
74
|
+
res = VirementMaitrise::Pis::Initiate.generate self, payload, provider_id, redirect_uri, state
|
|
75
|
+
|
|
76
|
+
VirementMaitrise::Utils::JsonParser.safe_parse(res.body, context: 'initiate')
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def payments(session_id = nil, with_virtualbeneficiary: false)
|
|
80
|
+
require_token!
|
|
81
|
+
res = VirementMaitrise::Pis::Payments.get self, session_id, with_virtualbeneficiary: with_virtualbeneficiary
|
|
82
|
+
|
|
83
|
+
VirementMaitrise::Utils::JsonParser.safe_parse(res.body, context: 'payments')
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def refund(session_id, amount = nil, user_id = nil)
|
|
87
|
+
require_token!
|
|
88
|
+
res = VirementMaitrise::Pis::Refund.generate self, session_id, amount, user_id
|
|
89
|
+
|
|
90
|
+
VirementMaitrise::Utils::JsonParser.safe_parse(res.body, context: 'refund')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def settlements(settlement_id = nil, include_payments = false)
|
|
94
|
+
require_token!
|
|
95
|
+
res = VirementMaitrise::Pis::Settlements.get self, settlement_id, include_payments
|
|
96
|
+
|
|
97
|
+
VirementMaitrise::Utils::JsonParser.safe_parse(res.body, context: 'settlements')
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def providers(provider_id: nil, paramsProviders: nil)
|
|
101
|
+
res = VirementMaitrise::Ressources::Providers.get self, provider_id, paramsProviders
|
|
102
|
+
|
|
103
|
+
VirementMaitrise::Utils::JsonParser.safe_parse(res.body, context: 'providers')
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def applications
|
|
107
|
+
res = VirementMaitrise::Ressources::Applications.get self
|
|
108
|
+
|
|
109
|
+
VirementMaitrise::Utils::JsonParser.safe_parse(res.body, context: 'applications')
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def test_accounts(provider_id = nil)
|
|
113
|
+
res = VirementMaitrise::Ressources::TestAccounts.get self, provider_id
|
|
114
|
+
|
|
115
|
+
VirementMaitrise::Utils::JsonParser.safe_parse(res.body, context: 'test accounts')
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module VirementMaitrise
|
|
4
|
+
module Utils
|
|
5
|
+
module Constants
|
|
6
|
+
SIGNEDHEADERPARAMETERLIST = %w[(request-target) Date Digest X-Request-ID].freeze
|
|
7
|
+
PSU_TYPES = %w[retail corporate all].freeze
|
|
8
|
+
SCOPES = %w[pis ais].freeze
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
require 'openssl'
|
|
5
|
+
require 'base64'
|
|
6
|
+
require 'json'
|
|
7
|
+
require 'virementmaitrise/exceptions'
|
|
8
|
+
require 'virementmaitrise/utils/constants'
|
|
9
|
+
require 'cgi'
|
|
10
|
+
|
|
11
|
+
module VirementMaitrise
|
|
12
|
+
module Utils
|
|
13
|
+
class Crypto
|
|
14
|
+
class << self
|
|
15
|
+
def generate_uuid
|
|
16
|
+
SecureRandom.uuid
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def generate_uuid_only_chars
|
|
20
|
+
generate_uuid.gsub('-', '')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def sign_payload(payload)
|
|
24
|
+
payload = payload.to_json.to_s if payload.is_a? Hash
|
|
25
|
+
digest = OpenSSL::Digest.new('SHA256')
|
|
26
|
+
private_key = OpenSSL::PKey::RSA.new(@client.private_key)
|
|
27
|
+
|
|
28
|
+
begin
|
|
29
|
+
signature = private_key.sign(digest, payload)
|
|
30
|
+
Base64.strict_encode64(signature)
|
|
31
|
+
rescue StandardError
|
|
32
|
+
raise VirementMaitrise::CryptoException, 'error during signature'
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def decrypt_private(digest)
|
|
37
|
+
digest = CGI.unescape(digest)
|
|
38
|
+
encrypted_string = Base64.decode64(digest)
|
|
39
|
+
private_key = OpenSSL::PKey::RSA.new(@client.private_key)
|
|
40
|
+
|
|
41
|
+
begin
|
|
42
|
+
private_key.private_decrypt(encrypted_string, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
|
|
43
|
+
rescue OpenSSL::PKey::RSAError => e
|
|
44
|
+
raise VirementMaitrise::CryptoException, "error while decrypt, #{e.message}"
|
|
45
|
+
rescue StandardError
|
|
46
|
+
raise VirementMaitrise::CryptoException, 'error during decryption'
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def hash_base64(plain_text)
|
|
51
|
+
digest = Digest::SHA256.digest plain_text
|
|
52
|
+
Base64.strict_encode64(digest)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def create_signature_header(headers, client)
|
|
56
|
+
@client = client
|
|
57
|
+
signing = []
|
|
58
|
+
header = []
|
|
59
|
+
|
|
60
|
+
VirementMaitrise::Utils::Constants::SIGNEDHEADERPARAMETERLIST.each do |param|
|
|
61
|
+
next unless headers[param]
|
|
62
|
+
|
|
63
|
+
param_low = param.downcase
|
|
64
|
+
signing << "#{param_low}: #{headers[param]}"
|
|
65
|
+
header << param_low
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Double quote in join needed. If not we will get two slashes \\n
|
|
69
|
+
signature = sign_payload signing.join("\n")
|
|
70
|
+
"keyId=\"#{@client.app_id}\",algorithm=\"rsa-sha256\",headers=\"#{header.join(' ')}\",signature=\"#{signature}\""
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module VirementMaitrise
|
|
6
|
+
module Utils
|
|
7
|
+
module JsonParser
|
|
8
|
+
class JsonParseException < StandardError
|
|
9
|
+
attr_reader :context, :raw_body, :parse_error
|
|
10
|
+
|
|
11
|
+
def initialize(message, context:, raw_body:, parse_error:)
|
|
12
|
+
super(message)
|
|
13
|
+
@context = context
|
|
14
|
+
@raw_body = raw_body
|
|
15
|
+
@parse_error = parse_error
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def safe_parse(json_string, context: 'API response')
|
|
20
|
+
return {} if json_string.nil? || json_string.strip.empty?
|
|
21
|
+
|
|
22
|
+
JSON.parse(json_string)
|
|
23
|
+
rescue JSON::ParserError => e
|
|
24
|
+
truncated_body = json_string.length > 200 ? "#{json_string[0..200]}..." : json_string
|
|
25
|
+
|
|
26
|
+
raise JsonParseException.new(
|
|
27
|
+
"Failed to parse #{context}. JSON parse error: #{e.message}. Response body: #{truncated_body}",
|
|
28
|
+
context: context,
|
|
29
|
+
raw_body: json_string,
|
|
30
|
+
parse_error: e
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
module_function :safe_parse
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
module VirementMaitrise
|
|
6
|
+
module Utils
|
|
7
|
+
module QueryBuilder
|
|
8
|
+
|
|
9
|
+
def build_query_string(params)
|
|
10
|
+
return '' if params.nil? || params.empty?
|
|
11
|
+
|
|
12
|
+
"?#{params.map { |key, value| "#{key}=#{value}" }.join('&')}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module_function :build_query_string
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'virementmaitrise/exceptions'
|
|
4
|
+
|
|
5
|
+
module VirementMaitrise
|
|
6
|
+
module Utils
|
|
7
|
+
class Validation
|
|
8
|
+
class << self
|
|
9
|
+
def raise_if_klass_mismatch(target, klass, param_name = nil)
|
|
10
|
+
return if target.is_a? klass
|
|
11
|
+
|
|
12
|
+
raise VirementMaitrise::ValidationException,
|
|
13
|
+
"invalid #{param_name || 'parameter'} format, the parameter should be a #{klass} instead a #{target.class.name}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def raise_if_invalid_date_format(date)
|
|
17
|
+
return unless date
|
|
18
|
+
|
|
19
|
+
valid_format = date.match(/\d{4}-\d{2}-\d{2}/)
|
|
20
|
+
valid_date = begin
|
|
21
|
+
::Date.strptime(date, '%Y-%m-%d')
|
|
22
|
+
rescue StandardError
|
|
23
|
+
false
|
|
24
|
+
end
|
|
25
|
+
return if valid_format && valid_date
|
|
26
|
+
|
|
27
|
+
raise VirementMaitrise::ValidationException, "invalids #{date} date, the format should be YYYY-MM-DD"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'faraday'
|
|
6
|
+
|
|
7
|
+
require 'openssl'
|
|
8
|
+
require 'cgi'
|
|
9
|
+
|
|
10
|
+
# Version
|
|
11
|
+
require 'virementmaitrise/version'
|
|
12
|
+
|
|
13
|
+
# Modules
|
|
14
|
+
require 'virementmaitrise/api/auth/authentication'
|
|
15
|
+
|
|
16
|
+
# Clients
|
|
17
|
+
require 'virementmaitrise/pis_client'
|
|
18
|
+
require 'virementmaitrise/ais_client'
|
|
19
|
+
|
|
20
|
+
# Utilities
|
|
21
|
+
require 'virementmaitrise/utils/crypto'
|
|
22
|
+
require 'virementmaitrise/utils/json_parser'
|
|
23
|
+
require 'virementmaitrise/utils/query_builder'
|
|
24
|
+
require 'virementmaitrise/utils/validation'
|
|
25
|
+
|
|
26
|
+
# Endpoints
|
|
27
|
+
require 'virementmaitrise/base_url'
|
|
28
|
+
require 'virementmaitrise/endpoints/authentication'
|
|
29
|
+
require 'virementmaitrise/endpoints/ais'
|
|
30
|
+
require 'virementmaitrise/endpoints/pis'
|
|
31
|
+
require 'virementmaitrise/endpoints/ressources'
|
|
32
|
+
|
|
33
|
+
# Connections
|
|
34
|
+
require 'virementmaitrise/faraday/authentication/connection'
|
|
35
|
+
|
|
36
|
+
module VirementMaitrise
|
|
37
|
+
@log_level = nil
|
|
38
|
+
@logger = nil
|
|
39
|
+
|
|
40
|
+
class << self
|
|
41
|
+
attr_accessor :logger
|
|
42
|
+
attr_reader :log_level
|
|
43
|
+
|
|
44
|
+
# Logging
|
|
45
|
+
LEVEL_DEBUG = Logger::DEBUG
|
|
46
|
+
LEVEL_ERROR = Logger::ERROR
|
|
47
|
+
LEVEL_INFO = Logger::INFO
|
|
48
|
+
|
|
49
|
+
def log_level=(val)
|
|
50
|
+
case val
|
|
51
|
+
when 'debug'
|
|
52
|
+
val = LEVEL_DEBUG
|
|
53
|
+
when 'info'
|
|
54
|
+
val = LEVEL_INFO
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if !val.nil? && ![LEVEL_DEBUG, LEVEL_ERROR, LEVEL_INFO].include?(val)
|
|
58
|
+
raise ArgumentError, 'log_level should only be set to `nil`, `debug` or `info`'
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
@log_level = val
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
VirementMaitrise.log_level = ENV['FINTECTURE_LOG'] unless ENV['FINTECTURE_LOG'].nil?
|
|
@@ -0,0 +1,46 @@
|
|
|
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 'virementmaitrise/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.name = 'virementmaitrise'
|
|
9
|
+
spec.version = VirementMaitrise::VERSION
|
|
10
|
+
spec.authors = ['Fintecture']
|
|
11
|
+
spec.email = ['support@fintecture.com']
|
|
12
|
+
|
|
13
|
+
spec.required_ruby_version = '>= 2.7.0'
|
|
14
|
+
|
|
15
|
+
spec.summary = 'SDK to allow easy and secure access to bank account data and payment initiation with Virement Maitrisé.'
|
|
16
|
+
spec.description = 'Our APIs allow easy and secure access to bank account data and payment initiation. The account data accessible are account holder\'s personal information, account balances, transaction history and much more. The available payment methods depend on the banks implementation but typically are domestic transfers, SEPA credit transfer, instant SEPA credit transfer, fast payment scheme, and SWIFT international payments.'
|
|
17
|
+
spec.homepage = 'https://www.virementmaitrise.societegenerale.eu'
|
|
18
|
+
spec.license = 'GPL-3.0'
|
|
19
|
+
|
|
20
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
|
21
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
|
22
|
+
if spec.respond_to?(:metadata)
|
|
23
|
+
# spec.metadata["allowed_push_host"] = 'http://mygemserver.com'
|
|
24
|
+
|
|
25
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
26
|
+
spec.metadata['source_code_uri'] = 'https://github.com/Virementmaitrise/virementmaitrise-sdk-ruby'
|
|
27
|
+
# spec.metadata["changelog_uri"] = spec.homepage
|
|
28
|
+
else
|
|
29
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
|
30
|
+
'public gem pushes.'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Specify which files should be added to the gem when it is released.
|
|
34
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
35
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
36
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
37
|
+
end
|
|
38
|
+
spec.bindir = 'exe'
|
|
39
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
40
|
+
spec.require_paths = ['lib']
|
|
41
|
+
|
|
42
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
|
43
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
|
44
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
45
|
+
spec.add_dependency 'faraday', '> 1.0'
|
|
46
|
+
end
|