adyen-ruby-api-library 4.0.0 → 4.3.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 +5 -5
- data/.github/CODEOWNERS +1 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +7 -0
- data/.github/dependabot.yml +8 -0
- data/CODE_OF_CONDUCT.md +76 -0
- data/Gemfile +2 -2
- data/README.md +16 -2
- data/docs/checkout.html +17 -0
- data/lib/adyen-ruby-api-library.rb +4 -11
- data/lib/adyen/client.rb +16 -3
- data/lib/adyen/errors.rb +44 -2
- data/lib/adyen/hash_with_accessors.rb +38 -0
- data/lib/adyen/result.rb +2 -2
- data/lib/adyen/services/checkout.rb +2 -1
- data/lib/adyen/services/data_protection.rb +17 -0
- data/lib/adyen/services/marketpay.rb +18 -0
- data/lib/adyen/services/payments.rb +2 -1
- data/lib/adyen/services/postfmapi.rb +19 -0
- data/lib/adyen/services/service.rb +10 -1
- data/lib/adyen/utils/hmac_validator.rb +48 -0
- data/lib/adyen/version.rb +2 -2
- data/renovate.json +5 -0
- data/spec/checkout_spec.rb +13 -4
- data/spec/checkout_utility_spec.rb +4 -2
- data/spec/data_protection_spec.rb +14 -0
- data/spec/errors_spec.rb +33 -3
- data/spec/hash_with_accessors_spec.rb +127 -0
- data/spec/hop_spec.rb +14 -0
- data/spec/mocks/requests/Checkout/payment_links.json +9 -0
- data/spec/mocks/requests/DataProtectionService/request_subject_erasure.json +5 -0
- data/spec/mocks/requests/Hop/get_onboarding_url.json +4 -0
- data/spec/mocks/requests/Payment/donate.json +10 -0
- data/spec/mocks/requests/Terminal/assign_terminals.json +6 -0
- data/spec/mocks/requests/Terminal/find_terminal.json +3 -0
- data/spec/mocks/requests/Terminal/get_terminals_under_account.json +4 -0
- data/spec/mocks/responses/Checkout/payment_links.json +9 -0
- data/spec/mocks/responses/DataProtectionService/request_subject_erasure.json +3 -0
- data/spec/mocks/responses/Hop/get_onboarding_url.json +7 -0
- data/spec/mocks/responses/Payment/donate.json +4 -0
- data/spec/mocks/responses/Terminal/assign_terminals.json +5 -0
- data/spec/mocks/responses/Terminal/find_terminal.json +6 -0
- data/spec/mocks/responses/Terminal/get_terminals_under_account.json +11 -0
- data/spec/payments_spec.rb +2 -1
- data/spec/postfmapi_spec.rb +16 -0
- data/spec/service_spec.rb +45 -0
- data/spec/spec_helper.rb +7 -4
- data/spec/utils/hmac_validator_spec.rb +52 -0
- metadata +34 -5
- data/lib/adyen/util.rb +0 -21
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'service'
|
2
|
+
|
3
|
+
module Adyen
|
4
|
+
class DataProtection < Service
|
5
|
+
attr_accessor :version
|
6
|
+
DEFAULT_VERSION = 1
|
7
|
+
|
8
|
+
def initialize(client, version = DEFAULT_VERSION)
|
9
|
+
service = 'DataProtectionService'
|
10
|
+
method_names = [
|
11
|
+
:request_subject_erasure
|
12
|
+
]
|
13
|
+
|
14
|
+
super(client, version, service, method_names)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -21,6 +21,10 @@ module Adyen
|
|
21
21
|
def notification
|
22
22
|
@notification ||= Adyen::Marketpay::Notification.new(@client)
|
23
23
|
end
|
24
|
+
|
25
|
+
def hop
|
26
|
+
@hop ||= Adyen::Marketpay::Hop.new(@client)
|
27
|
+
end
|
24
28
|
end
|
25
29
|
|
26
30
|
class Account < Service
|
@@ -88,5 +92,19 @@ module Adyen
|
|
88
92
|
super(client, version, service, method_names)
|
89
93
|
end
|
90
94
|
end
|
95
|
+
|
96
|
+
class Hop < Service
|
97
|
+
attr_accessor :version
|
98
|
+
DEFAULT_VERSION = 1
|
99
|
+
|
100
|
+
def initialize(client, version = DEFAULT_VERSION)
|
101
|
+
service = 'Hop'
|
102
|
+
method_names = [
|
103
|
+
:get_onboarding_url
|
104
|
+
]
|
105
|
+
|
106
|
+
super(client, version, service, method_names)
|
107
|
+
end
|
108
|
+
end
|
91
109
|
end
|
92
110
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative 'service'
|
2
|
+
|
3
|
+
module Adyen
|
4
|
+
class PosTerminalManagement < Service
|
5
|
+
attr_accessor :version
|
6
|
+
DEFAULT_VERSION = 1
|
7
|
+
|
8
|
+
def initialize(client, version = DEFAULT_VERSION)
|
9
|
+
service = 'Terminal'
|
10
|
+
method_names = [
|
11
|
+
:assign_terminals,
|
12
|
+
:find_terminal,
|
13
|
+
:get_terminals_under_account
|
14
|
+
]
|
15
|
+
|
16
|
+
super(client, version, service, method_names)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -2,6 +2,15 @@ module Adyen
|
|
2
2
|
class Service
|
3
3
|
attr_accessor :service, :version
|
4
4
|
|
5
|
+
# add snake case to camel case converter to String
|
6
|
+
# to convert rubinic method names to Adyen API methods
|
7
|
+
#
|
8
|
+
# i.e. snake_case -> snakeCase
|
9
|
+
# note that the first letter is not capitalized as normal
|
10
|
+
def self.action_for_method_name(method_name)
|
11
|
+
method_name.to_s.gsub(/_./) { |x| x[1].upcase }
|
12
|
+
end
|
13
|
+
|
5
14
|
def initialize(client, version, service, method_names)
|
6
15
|
@client = client
|
7
16
|
@version = version
|
@@ -10,7 +19,7 @@ module Adyen
|
|
10
19
|
# dynamically create API methods
|
11
20
|
method_names.each do |method_name|
|
12
21
|
define_singleton_method method_name do |request, headers = {}|
|
13
|
-
action =
|
22
|
+
action = self.class.action_for_method_name(method_name)
|
14
23
|
@client.call_adyen_api(@service, action, request, headers, @version)
|
15
24
|
end
|
16
25
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Adyen
|
2
|
+
module Utils
|
3
|
+
class HmacValidator
|
4
|
+
HMAC_ALGORITHM = 'sha256'.freeze
|
5
|
+
DATA_SEPARATOR = ':'.freeze
|
6
|
+
NOTIFICATION_VALIDATION_KEYS = %w[
|
7
|
+
pspReference originalReference merchantAccountCode merchantReference
|
8
|
+
amount.value amount.currency eventCode success
|
9
|
+
].freeze
|
10
|
+
|
11
|
+
def valid_notification_hmac?(notification_request_item, hmac_key)
|
12
|
+
expected_sign = calculate_notification_hmac(notification_request_item, hmac_key)
|
13
|
+
merchant_sign = fetch(notification_request_item, 'additionalData.hmacSignature')
|
14
|
+
|
15
|
+
expected_sign == merchant_sign
|
16
|
+
end
|
17
|
+
|
18
|
+
def calculate_notification_hmac(notification_request_item, hmac_key)
|
19
|
+
data = data_to_sign(notification_request_item)
|
20
|
+
|
21
|
+
Base64.strict_encode64(OpenSSL::HMAC.digest(HMAC_ALGORITHM, [hmac_key].pack('H*'), data))
|
22
|
+
end
|
23
|
+
|
24
|
+
def data_to_sign(notification_request_item)
|
25
|
+
NOTIFICATION_VALIDATION_KEYS.map { |key| fetch(notification_request_item, key).to_s }
|
26
|
+
.map { |value| value.gsub('\\', '\\\\').gsub(':', '\\:') }
|
27
|
+
.join(DATA_SEPARATOR)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def fetch(hash, keys)
|
33
|
+
value = hash
|
34
|
+
|
35
|
+
keys.to_s.split('.').each do |key|
|
36
|
+
value = if key.to_i.to_s == key
|
37
|
+
value[key.to_i]
|
38
|
+
else
|
39
|
+
value[key].nil? ? value[key.to_sym] : value[key]
|
40
|
+
end
|
41
|
+
break if value.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/adyen/version.rb
CHANGED
data/spec/checkout_spec.rb
CHANGED
@@ -57,10 +57,14 @@ RSpec.describe Adyen::Checkout, service: "checkout" do
|
|
57
57
|
to eq(200)
|
58
58
|
expect(response_hash).
|
59
59
|
to eq(JSON.parse(response_body))
|
60
|
-
expect(response_hash
|
61
|
-
to
|
60
|
+
expect(response_hash).
|
61
|
+
to be_a Adyen::HashWithAccessors
|
62
|
+
expect(response_hash).
|
63
|
+
to be_a_kind_of Hash
|
62
64
|
expect(response_hash["resultCode"]).
|
63
65
|
to eq("RedirectShopper")
|
66
|
+
expect(response_hash.resultCode).
|
67
|
+
to eq("RedirectShopper")
|
64
68
|
end
|
65
69
|
|
66
70
|
# must be created manually due to payments/result format
|
@@ -89,10 +93,14 @@ RSpec.describe Adyen::Checkout, service: "checkout" do
|
|
89
93
|
to eq(200)
|
90
94
|
expect(response_hash).
|
91
95
|
to eq(JSON.parse(response_body))
|
92
|
-
expect(response_hash
|
93
|
-
to
|
96
|
+
expect(response_hash).
|
97
|
+
to be_a Adyen::HashWithAccessors
|
98
|
+
expect(response_hash).
|
99
|
+
to be_a_kind_of Hash
|
94
100
|
expect(response_hash["resultCode"]).
|
95
101
|
to eq("Authorised")
|
102
|
+
expect(response_hash.resultCode).
|
103
|
+
to eq("Authorised")
|
96
104
|
end
|
97
105
|
|
98
106
|
# create client for automated tests
|
@@ -102,6 +110,7 @@ RSpec.describe Adyen::Checkout, service: "checkout" do
|
|
102
110
|
# format is defined in spec_helper
|
103
111
|
test_sets = [
|
104
112
|
["payment_session", "publicKeyToken", "8115054323780109"],
|
113
|
+
["payment_links", "url", "https://checkoutshopper-test.adyen.com"],
|
105
114
|
["payments", "resultCode", "Authorised"]
|
106
115
|
]
|
107
116
|
|
@@ -19,8 +19,10 @@ RSpec.describe Adyen::CheckoutUtility, service: "checkout utility" do
|
|
19
19
|
# must be created manually because every field in the response is an array
|
20
20
|
it "makes an origin_keys call" do
|
21
21
|
parsed_body = create_test(@shared_values[:client], @shared_values[:service], "origin_keys", @shared_values[:client].checkout_utility)
|
22
|
-
expect(parsed_body["originKeys"]
|
23
|
-
to
|
22
|
+
expect(parsed_body["originKeys"]).
|
23
|
+
to be_a Adyen::HashWithAccessors
|
24
|
+
expect(parsed_body["originKeys"]).
|
25
|
+
to be_a_kind_of Hash
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Adyen::DataProtection, service: "Data Protection Service" do
|
4
|
+
# client instance to be used in dynamically generated tests
|
5
|
+
client = create_client(:basic)
|
6
|
+
|
7
|
+
# methods / values to test for
|
8
|
+
# format is defined in spec_helper
|
9
|
+
test_sets = [
|
10
|
+
["request_subject_erasure", "result", "SUCCESS"],
|
11
|
+
]
|
12
|
+
|
13
|
+
generate_tests(client, "DataProtectionService", test_sets, client.data_protection)
|
14
|
+
end
|
data/spec/errors_spec.rb
CHANGED
@@ -3,15 +3,45 @@
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
5
|
RSpec.describe Adyen::AdyenError do
|
6
|
+
before(:all) do
|
7
|
+
@shared_values = {
|
8
|
+
request: {
|
9
|
+
amount: {
|
10
|
+
currency: "USD",
|
11
|
+
value: 1000
|
12
|
+
},
|
13
|
+
reference: "Your order number",
|
14
|
+
paymentMethod: {
|
15
|
+
type: "scheme",
|
16
|
+
number: "4111111111111111",
|
17
|
+
expiryMonth: "10",
|
18
|
+
expiryYear: "2020",
|
19
|
+
holderName: "John Smith",
|
20
|
+
cvc: "737"
|
21
|
+
},
|
22
|
+
returnUrl: "https://your-company.com/",
|
23
|
+
merchantAccount: "YOUR_MERCHANT_ACCOUNT"
|
24
|
+
}
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
6
28
|
describe '#to_s' do
|
7
29
|
it 'describes using the error properties' do
|
8
|
-
expect(Adyen::AdyenError.new(
|
30
|
+
expect(Adyen::AdyenError.new(@shared_values[:request], 'response', 'message', 'code').to_s).to eq("Adyen::AdyenError code:code, msg:message, request:#{@shared_values[:request]}, response:response")
|
9
31
|
end
|
10
32
|
it 'skips the null properties' do
|
11
|
-
expect(Adyen::AdyenError.new(
|
33
|
+
expect(Adyen::AdyenError.new(@shared_values[:request], nil, nil, 'code').to_s).to eq("Adyen::AdyenError code:code, request:#{@shared_values[:request]}")
|
12
34
|
end
|
13
35
|
it 'uses the proper error class name' do
|
14
|
-
expect(Adyen::PermissionError.new('
|
36
|
+
expect(Adyen::PermissionError.new('message', @shared_values[:request]).to_s).to eq("Adyen::PermissionError code:403, msg:message, request:#{@shared_values[:request]}")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
describe '#masking' do
|
40
|
+
it 'masks card number when logging request in errors' do
|
41
|
+
expect(Adyen::AdyenError.new(@shared_values[:request], 'response', 'message', 'code').request[:paymentMethod][:number]).to eq('411111******1111')
|
42
|
+
end
|
43
|
+
it 'masks CVC when logging request in errors' do
|
44
|
+
expect(Adyen::AdyenError.new(@shared_values[:request], 'response', 'message', 'code').request[:paymentMethod][:cvc]).to eq('***')
|
15
45
|
end
|
16
46
|
end
|
17
47
|
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Adyen::HashWithAccessors do
|
4
|
+
shared_examples :hash_with_accessors do
|
5
|
+
subject do
|
6
|
+
h = described_class.new
|
7
|
+
h[key] = 1
|
8
|
+
h
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'returns values of a hashes' do
|
12
|
+
expect(subject.arbitrary_accessor).to be 1
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'can assign existing values' do
|
16
|
+
subject.arbitrary_accessor = 2
|
17
|
+
expect(subject.arbitrary_accessor).to be 2
|
18
|
+
expect(subject[key]).to be 2
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'complains if there are arguments for the accessor' do
|
22
|
+
expect { subject.arbitrary_accessor(1) }.to raise_error(ArgumentError, 'wrong number of arguments (given 1, expected 0)')
|
23
|
+
expect { subject.arbitrary_accessor(1, 2) }.to raise_error(ArgumentError, 'wrong number of arguments (given 2, expected 0)')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'complains if there are arguments for the accessor =' do
|
27
|
+
# using send because i'm not sure how to do this wrong with normal ruby setter calling.
|
28
|
+
# just here for completeness
|
29
|
+
expect { subject.send(:arbitrary_accessor=) }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1)')
|
30
|
+
expect { subject.send(:arbitrary_accessor=, 1, 2) }.to raise_error(ArgumentError, 'wrong number of arguments (given 2, expected 1)')
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'responds to the accessor' do
|
34
|
+
expect(subject).to respond_to(:arbitrary_accessor)
|
35
|
+
expect(subject).to respond_to(:arbitrary_accessor=)
|
36
|
+
expect(subject).to_not respond_to(:another_accessor)
|
37
|
+
expect(subject).to_not respond_to(:another_accessor=)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "raises when the key doesn't exist" do
|
41
|
+
expect { subject.another_accessor }.to raise_error(NoMethodError)
|
42
|
+
expect { subject.another_accessor = 1 }.to raise_error(NoMethodError)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'with a string key' do
|
47
|
+
let(:key) { 'arbitrary_accessor' }
|
48
|
+
|
49
|
+
it_behaves_like :hash_with_accessors
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'with a symbol key' do
|
53
|
+
let(:key) { :arbitrary_accessor }
|
54
|
+
|
55
|
+
it_behaves_like :hash_with_accessors
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'with a conflicting key' do
|
59
|
+
subject do
|
60
|
+
h = described_class.new
|
61
|
+
h['keys'] = 'not the keys'
|
62
|
+
h
|
63
|
+
end
|
64
|
+
|
65
|
+
it "does original thing if there'd be a conflict" do
|
66
|
+
expect(subject.keys).to eq ['keys'] # the default behaviour
|
67
|
+
expect(subject['keys']).to eq 'not the keys'
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'still does the writer thing even if the reader is defined' do
|
71
|
+
subject.keys = 'written keys'
|
72
|
+
expect(subject['keys']).to eq 'written keys'
|
73
|
+
expect(subject.keys).to eq ['keys']
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'with some other method missing defined' do
|
78
|
+
# this test setup is kind of janky,
|
79
|
+
# but we want to confirm super is set up correctly
|
80
|
+
# We could do a lot more house-keeping if we weren't sure Hash doesn't
|
81
|
+
# define its own method_missing and respond_to_missing? by default,
|
82
|
+
# and there was any particular reason to clean up properly and remove our
|
83
|
+
# called_super method from Hash.
|
84
|
+
|
85
|
+
before(:all) do
|
86
|
+
class Hash
|
87
|
+
def called_super(*args)
|
88
|
+
end
|
89
|
+
|
90
|
+
def method_missing(*args)
|
91
|
+
called_super(:method_missing, *args)
|
92
|
+
super
|
93
|
+
end
|
94
|
+
|
95
|
+
def respond_to_missing?(*args)
|
96
|
+
called_super(:respond_to_missing?, *args)
|
97
|
+
super
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
subject do
|
103
|
+
h = described_class.new
|
104
|
+
h[:my_accessor] = 1
|
105
|
+
h
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'can fall back to another respond_to_missing?' do
|
109
|
+
expect(subject).to_not receive(:called_super).with(:respond_to_missing?, :my_accessor, false)
|
110
|
+
expect(subject).to respond_to(:my_accessor)
|
111
|
+
expect(subject).to receive(:called_super).with(:respond_to_missing?, :literally_anything, false)
|
112
|
+
expect(subject.respond_to?(:literally_anything)).to be false
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'can fall back to another method_missing' do
|
116
|
+
expect(subject).to_not receive(:called_super).with(:method_missing, :my_accessor)
|
117
|
+
expect(subject.my_accessor).to be 1
|
118
|
+
expect(subject).to receive(:called_super).with(:method_missing, :something_else)
|
119
|
+
expect { subject.something_else }.to raise_error(NoMethodError)
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
it "doesn't modify all hashes" do
|
125
|
+
expect { {a: 1}.a }.to raise_error(NoMethodError)
|
126
|
+
end
|
127
|
+
end
|
data/spec/hop_spec.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Adyen::Payments, service: "marketpay hop service" do
|
4
|
+
# client instance to be used in dynamically generated tests
|
5
|
+
client = create_client(:basic)
|
6
|
+
|
7
|
+
# methods / values to test for
|
8
|
+
# format is defined in spec_helper
|
9
|
+
test_sets = [
|
10
|
+
["get_onboarding_url", "pspReference", "8815850625171183"]
|
11
|
+
]
|
12
|
+
|
13
|
+
generate_tests(client, "Hop", test_sets, client.marketpay.hop)
|
14
|
+
end
|