adyen-ruby-api-library 6.1.0 → 6.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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -1
  3. data/.github/workflows/codeql.yml +41 -0
  4. data/.github/workflows/ruby.yml +1 -1
  5. data/.github/workflows/rubygems_release.yml +19 -0
  6. data/lib/adyen/client.rb +20 -7
  7. data/lib/adyen/errors.rb +2 -2
  8. data/lib/adyen/services/checkout.rb +80 -6
  9. data/lib/adyen/services/service.rb +5 -0
  10. data/lib/adyen/utils/hmac_validator.rb +2 -3
  11. data/lib/adyen/version.rb +2 -2
  12. data/spec/checkout_spec.rb +242 -0
  13. data/spec/client_spec.rb +27 -0
  14. data/spec/errors_spec.rb +1 -1
  15. data/spec/mocks/requests/Checkout/amount_updates.json +22 -0
  16. data/spec/mocks/requests/Checkout/capture.json +34 -0
  17. data/spec/mocks/requests/Checkout/generic_cancel.json +5 -0
  18. data/spec/mocks/requests/Checkout/modifications_request.json +0 -0
  19. data/spec/mocks/requests/Checkout/psp_cancel.json +4 -0
  20. data/spec/mocks/requests/Checkout/refund.json +34 -0
  21. data/spec/mocks/responses/Checkout/amount_updates.json +24 -0
  22. data/spec/mocks/responses/Checkout/capture.json +37 -0
  23. data/spec/mocks/responses/Checkout/generic_cancel.json +7 -0
  24. data/spec/mocks/responses/Checkout/modifications.json +0 -0
  25. data/spec/mocks/responses/Checkout/psp_cancel.json +7 -0
  26. data/spec/mocks/responses/Checkout/refund.json +37 -0
  27. data/spec/mocks/responses/Checkout/stored_payment_methods.json +1 -0
  28. data/spec/mocks/responses/Webhooks/backslash_notification.json +41 -0
  29. data/spec/mocks/responses/Webhooks/colon_notification.json +41 -0
  30. data/spec/mocks/responses/Webhooks/forwardslash_notification.json +41 -0
  31. data/spec/mocks/responses/Webhooks/mixed_notification.json +41 -0
  32. data/spec/utils/hmac_validator_spec.rb +20 -6
  33. metadata +21 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b2156b9db20964cb7f43be56c19a462ccc091e42fdcecd887c9da04dc880116
4
- data.tar.gz: 47cb2a027baa58061a6eb46e5c703ff7bc18b05002e5a7c0c5b9d9e9d68616fd
3
+ metadata.gz: 45627b45fc5df26d004c1a86d7640412d615383ccac35201f24f0e3ebed99394
4
+ data.tar.gz: 72a067ba4b95419ed87e526a371db2377d5b1af7056f9ac035a6e11327715da0
5
5
  SHA512:
6
- metadata.gz: a1e4c4a2fd0b91b3e375a66a59910548a9e272beb8940e2ecf6432593e2c14494006d7d6387020245fea48af0f24b50b7803d135db654294e9079c842f818390
7
- data.tar.gz: de54e9a6c6da6a49b922d26cb6197651f59f145fef43412bfdf50863097f287d8139dd1647e6efd585d1c08d8dbffaca845f16e0e46ec49334e323f73ade74d1
6
+ metadata.gz: a893d921b3236805bb89b6ca5fc1c553888d010c84c1927689f6bc47e1a78b23302c081edbc979d596ac70c0b6f23cad61b9a83cc11c6f8b22e1d73520951bdb
7
+ data.tar.gz: 3cc14ff3d1550ffb11f6ec08c37ae7858b5e39e1b14023e6323e3a132441b59ee383e39ca6db6dcb83eff24e4b9ae937bff416bc9684e1ba6cb8ba7b20e37405
data/.github/CODEOWNERS CHANGED
@@ -1 +1 @@
1
- * @crrood @wboereboom @AlexandrosMor @michaelpaul
1
+ * @Adyen/api-libraries-reviewers
@@ -0,0 +1,41 @@
1
+ name: "CodeQL"
2
+
3
+ on:
4
+ push:
5
+ branches: [ "develop", "main" ]
6
+ pull_request:
7
+ branches: [ "develop" ]
8
+ schedule:
9
+ - cron: "40 12 * * 0"
10
+
11
+ jobs:
12
+ analyze:
13
+ name: Analyze
14
+ runs-on: ubuntu-latest
15
+ permissions:
16
+ actions: read
17
+ contents: read
18
+ security-events: write
19
+
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ language: [ javascript ]
24
+
25
+ steps:
26
+ - name: Checkout
27
+ uses: actions/checkout@v3
28
+
29
+ - name: Initialize CodeQL
30
+ uses: github/codeql-action/init@v2
31
+ with:
32
+ languages: ${{ matrix.language }}
33
+ queries: +security-and-quality
34
+
35
+ - name: Autobuild
36
+ uses: github/codeql-action/autobuild@v2
37
+
38
+ - name: Perform CodeQL Analysis
39
+ uses: github/codeql-action/analyze@v2
40
+ with:
41
+ category: "/language:${{ matrix.language }}"
@@ -12,7 +12,7 @@ jobs:
12
12
  ruby: [2.5, 2.6, 2.7, '3.0', head]
13
13
  runs-on: ${{ matrix.os }}
14
14
  steps:
15
- - uses: actions/checkout@v2
15
+ - uses: actions/checkout@v3
16
16
  - uses: ruby/setup-ruby@v1
17
17
  with:
18
18
  ruby-version: ${{ matrix.ruby }}
@@ -0,0 +1,19 @@
1
+ name: Publish Gem
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+
11
+ steps:
12
+ - uses: actions/checkout@v3
13
+
14
+ - name: Release Gem on RubyGems
15
+ if: contains(github.ref, 'refs/tags/v')
16
+ uses: cadwallion/publish-rubygems-action@master
17
+ env:
18
+ GITHUB_TOKEN: ${{secrets.TOKEN_RUBYGEMS_RELEASES_WITH_EXPIRATION}}
19
+ RUBYGEMS_API_KEY: ${{secrets.RUBYGEMS_API_KEY}}
data/lib/adyen/client.rb CHANGED
@@ -6,9 +6,9 @@ require_relative "./result"
6
6
  module Adyen
7
7
  class Client
8
8
  attr_accessor :ws_user, :ws_password, :api_key, :client, :adapter, :live_url_prefix
9
- attr_reader :env
9
+ attr_reader :env, :connection_options
10
10
 
11
- def initialize(ws_user: nil, ws_password: nil, api_key: nil, env: :live, adapter: nil, mock_port: 3001, live_url_prefix: nil, mock_service_url_base: nil)
11
+ def initialize(ws_user: nil, ws_password: nil, api_key: nil, env: :live, adapter: nil, mock_port: 3001, live_url_prefix: nil, mock_service_url_base: nil, connection_options: nil)
12
12
  @ws_user = ws_user
13
13
  @ws_password = ws_password
14
14
  @api_key = api_key
@@ -16,6 +16,7 @@ module Adyen
16
16
  @adapter = adapter || Faraday.default_adapter
17
17
  @mock_service_url_base = mock_service_url_base || "http://localhost:#{mock_port}"
18
18
  @live_url_prefix = live_url_prefix
19
+ @connection_options = connection_options || Faraday::ConnectionOptions.new
19
20
  end
20
21
 
21
22
  # make sure that env can only be :live, :test, or :mock
@@ -96,7 +97,7 @@ module Adyen
96
97
  end
97
98
 
98
99
  # initialize Faraday connection object
99
- conn = Faraday.new(url: url) do |faraday|
100
+ conn = Faraday.new(url, @connection_options) do |faraday|
100
101
  faraday.adapter @adapter
101
102
  faraday.headers["Content-Type"] = "application/json"
102
103
  faraday.headers["User-Agent"] = Adyen::NAME + "/" + Adyen::VERSION
@@ -143,6 +144,13 @@ module Adyen
143
144
  raise connection_error, "Connection to #{url} failed"
144
145
  end
145
146
  end
147
+ if action.fetch(:method) == "delete"
148
+ begin
149
+ response = conn.delete
150
+ rescue Faraday::ConnectionFailed => connection_error
151
+ raise connection_error, "Connection to #{url} failed"
152
+ end
153
+ end
146
154
  if action.fetch(:method) == "patch"
147
155
  begin
148
156
  response = conn.patch do |req|
@@ -168,11 +176,16 @@ module Adyen
168
176
  when 401
169
177
  raise Adyen::AuthenticationError.new("Invalid API authentication; https://docs.adyen.com/user-management/how-to-get-the-api-key", request_data)
170
178
  when 403
171
- raise Adyen::PermissionError.new("Missing user permissions; https://docs.adyen.com/user-management/user-roles", request_data)
179
+ raise Adyen::PermissionError.new("Missing user permissions; https://docs.adyen.com/user-management/user-roles", request_data, response.body)
172
180
  end
173
-
174
- formatted_response = AdyenResult.new(response.body, response.headers, response.status)
175
-
181
+
182
+ # delete has no response.body (unless it throws an error)
183
+ if response.body == nil
184
+ formatted_response = AdyenResult.new("{}", response.headers, response.status)
185
+ else
186
+ formatted_response = AdyenResult.new(response.body, response.headers, response.status)
187
+ end
188
+
176
189
  formatted_response
177
190
  end
178
191
 
data/lib/adyen/errors.rb CHANGED
@@ -70,8 +70,8 @@ module Adyen
70
70
  end
71
71
 
72
72
  class PermissionError < AdyenError
73
- def initialize(msg, request)
74
- super(request, nil, msg, 403)
73
+ def initialize(msg, request, response)
74
+ super(request, response, msg, 403)
75
75
  end
76
76
  end
77
77
 
@@ -2,7 +2,7 @@ require_relative "service"
2
2
 
3
3
  module Adyen
4
4
  class Checkout < Service
5
- DEFAULT_VERSION = 68
5
+ DEFAULT_VERSION = 70
6
6
 
7
7
  def initialize(client, version = DEFAULT_VERSION)
8
8
  service = "Checkout"
@@ -13,7 +13,7 @@ module Adyen
13
13
  ]
14
14
 
15
15
  with_application_info = [
16
- :payment_session,
16
+ :payment_session
17
17
  ]
18
18
 
19
19
  super(client, version, service, method_names, with_application_info)
@@ -42,7 +42,7 @@ module Adyen
42
42
  else
43
43
  action = "paymentLinks"
44
44
  args[1] ||= {} # optional headers arg
45
- @client.call_adyen_api(@service, action, args[0], args[1], @version, true)
45
+ @client.call_adyen_api(@service, action, args[0], args[1], @version)
46
46
  end
47
47
  end
48
48
 
@@ -71,6 +71,14 @@ module Adyen
71
71
  def apple_pay
72
72
  @apple_pay ||= Adyen::CheckoutApplePay.new(@client, @version)
73
73
  end
74
+
75
+ def modifications
76
+ @modifications ||= Adyen::Modifications.new(@client, @version)
77
+ end
78
+
79
+ def stored_payment_methods
80
+ @stored_payment_methods ||= Adyen::StoredPaymentMethods.new(@client, @version)
81
+ end
74
82
  end
75
83
 
76
84
  class CheckoutDetail < Service
@@ -89,6 +97,16 @@ module Adyen
89
97
  action = "payments/result"
90
98
  @client.call_adyen_api(@service, action, request, headers, @version)
91
99
  end
100
+
101
+ def donations(request, headers = {})
102
+ action = "donations"
103
+ @client.call_adyen_api(@service, action, request, headers, @version)
104
+ end
105
+
106
+ def card_details(request, headers = {})
107
+ action = "cardDetails"
108
+ @client.call_adyen_api(@service, action, request, headers, @version)
109
+ end
92
110
  end
93
111
 
94
112
  class CheckoutLink < Service
@@ -100,12 +118,12 @@ module Adyen
100
118
 
101
119
  def get(linkId, headers = {})
102
120
  action = { method: 'get', url: "paymentLinks/" + linkId }
103
- @client.call_adyen_api(@service, action, {}, headers, @version, true)
121
+ @client.call_adyen_api(@service, action, {}, headers, @version)
104
122
  end
105
123
 
106
124
  def update(linkId, request, headers = {})
107
125
  action = { method: 'patch', url: "paymentLinks/" + linkId }
108
- @client.call_adyen_api(@service, action, request, headers, @version, false)
126
+ @client.call_adyen_api(@service, action, request, headers, @version)
109
127
  end
110
128
  end
111
129
 
@@ -147,4 +165,60 @@ module Adyen
147
165
  @client.call_adyen_api(@service, action, request, headers, @version)
148
166
  end
149
167
  end
150
- end
168
+
169
+ class Modifications < Service
170
+ def initialize(client, version = DEFAULT_VERSION)
171
+ @service = "Checkout"
172
+ @client = client
173
+ @version = version
174
+ end
175
+
176
+ def capture(linkId, request, headers = {})
177
+ action = "payments/" + linkId + "/captures"
178
+ @client.call_adyen_api(@service, action, request, headers, @version)
179
+ end
180
+
181
+ def cancel(linkId, request, headers = {})
182
+ action = "payments/" + linkId + "/cancels"
183
+ @client.call_adyen_api(@service, action, request, headers, @version)
184
+ end
185
+
186
+ def genericCancel(request, headers = {})
187
+ action = "cancels"
188
+ @client.call_adyen_api(@service, action, request, headers, @version)
189
+ end
190
+
191
+ def refund(linkId, request, headers = {})
192
+ action = "payments/" + linkId + "/refunds"
193
+ @client.call_adyen_api(@service, action, request, headers, @version)
194
+ end
195
+
196
+ def reversal(linkId, request, headers = {})
197
+ action = "payments/" + linkId + "/reversals"
198
+ @client.call_adyen_api(@service, action, request, headers, @version)
199
+ end
200
+
201
+ def amountUpdate(linkId, request, headers = {})
202
+ action = "payments/" + linkId + "/amountUpdates"
203
+ @client.call_adyen_api(@service, action, request, headers, @version)
204
+ end
205
+ end
206
+
207
+ class StoredPaymentMethods < Service
208
+ def initialize(client, version = DEFAULT_VERSION)
209
+ @service = "Checkout"
210
+ @client = client
211
+ @version = version
212
+ end
213
+
214
+ def get(query_array={}, headers = {})
215
+ action = { method: 'get', url: "storedPaymentMethods" + create_query_string(query_array)}
216
+ @client.call_adyen_api(@service, action, {}, headers, @version)
217
+ end
218
+
219
+ def delete(recurringId, query_array={}, headers = {})
220
+ action = { method: 'delete', url: "storedPaymentMethods/%s" % recurringId + create_query_string(query_array)}
221
+ @client.call_adyen_api(@service, action, {}, headers, @version)
222
+ end
223
+ end
224
+ end
@@ -24,5 +24,10 @@ module Adyen
24
24
  end
25
25
  end
26
26
  end
27
+
28
+ # create query parameter from an array
29
+ def create_query_string(arr)
30
+ "?" + URI.encode_www_form(arr)
31
+ end
27
32
  end
28
33
  end
@@ -22,16 +22,15 @@ module Adyen
22
22
  end
23
23
 
24
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(':', '\\:') }
25
+ data = NOTIFICATION_VALIDATION_KEYS.map { |key| fetch(notification_request_item, key).to_s }
27
26
  .join(DATA_SEPARATOR)
27
+ return data
28
28
  end
29
29
 
30
30
  private
31
31
 
32
32
  def fetch(hash, keys)
33
33
  value = hash
34
-
35
34
  keys.to_s.split('.').each do |key|
36
35
  value = if key.to_i.to_s == key
37
36
  value[key.to_i]
data/lib/adyen/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Adyen
2
2
  NAME = "adyen-ruby-api-library"
3
- VERSION = "6.1.0".freeze
4
- end
3
+ VERSION = "6.3.0".freeze
4
+ end
@@ -405,6 +405,248 @@ RSpec.describe Adyen::Checkout, service: "checkout" do
405
405
  to be_a_kind_of Hash
406
406
  end
407
407
 
408
+ it "makes a capture call" do
409
+ request_body = JSON.parse(json_from_file("mocks/requests/Checkout/capture.json"))
410
+
411
+ response_body = json_from_file("mocks/responses/Checkout/capture.json")
412
+
413
+ url = @shared_values[:client].service_url(@shared_values[:service], "payments/12345/captures", @shared_values[:client].checkout.version)
414
+ WebMock.stub_request(:post, url).
415
+ with(
416
+ body: request_body,
417
+ headers: {
418
+ "x-api-key" => @shared_values[:client].api_key
419
+ }
420
+ )
421
+ .to_return(body: response_body, status: 201)
422
+
423
+ result = @shared_values[:client].checkout.modifications.capture("12345", request_body)
424
+ response_hash = result.response
425
+
426
+ expect(result.status).
427
+ to eq(201)
428
+ expect(response_hash).
429
+ to eq(JSON.parse(response_body))
430
+ expect(response_hash).
431
+ to be_a Adyen::HashWithAccessors
432
+ expect(response_hash).
433
+ to be_a_kind_of Hash
434
+ expect(response_hash.reference).
435
+ to eq("123456789")
436
+ expect(response_hash.pspReference).
437
+ to eq("12345")
438
+ end
439
+
440
+ it "makes a psp specific cancel call" do
441
+ request_body = JSON.parse(json_from_file("mocks/requests/Checkout/psp_cancel.json"))
442
+
443
+ response_body = json_from_file("mocks/responses/Checkout/psp_cancel.json")
444
+
445
+ url = @shared_values[:client].service_url(@shared_values[:service], "payments/12345/cancels", @shared_values[:client].checkout.version)
446
+ WebMock.stub_request(:post, url).
447
+ with(
448
+ body: request_body,
449
+ headers: {
450
+ "x-api-key" => @shared_values[:client].api_key
451
+ }
452
+ )
453
+ .to_return(body: response_body, status: 201)
454
+
455
+ result = @shared_values[:client].checkout.modifications.cancel("12345", request_body)
456
+ response_hash = result.response
457
+
458
+ expect(result.status).
459
+ to eq(201)
460
+ expect(response_hash).
461
+ to eq(JSON.parse(response_body))
462
+ expect(response_hash).
463
+ to be_a Adyen::HashWithAccessors
464
+ expect(response_hash).
465
+ to be_a_kind_of Hash
466
+ expect(response_hash.reference).
467
+ to eq("123456789")
468
+ expect(response_hash.pspReference).
469
+ to eq("12345")
470
+ end
471
+
472
+ it "makes a psp specific refunds call" do
473
+ request_body = JSON.parse(json_from_file("mocks/requests/Checkout/refund.json"))
474
+
475
+ response_body = json_from_file("mocks/responses/Checkout/refund.json")
476
+
477
+ url = @shared_values[:client].service_url(@shared_values[:service], "payments/12345/refunds", @shared_values[:client].checkout.version)
478
+ WebMock.stub_request(:post, url).
479
+ with(
480
+ body: request_body,
481
+ headers: {
482
+ "x-api-key" => @shared_values[:client].api_key
483
+ }
484
+ )
485
+ .to_return(body: response_body, status: 201)
486
+
487
+ result = @shared_values[:client].checkout.modifications.refund("12345", request_body)
488
+ response_hash = result.response
489
+
490
+ expect(result.status).
491
+ to eq(201)
492
+ expect(response_hash).
493
+ to eq(JSON.parse(response_body))
494
+ expect(response_hash).
495
+ to be_a Adyen::HashWithAccessors
496
+ expect(response_hash).
497
+ to be_a_kind_of Hash
498
+ expect(response_hash.reference).
499
+ to eq("123456789")
500
+ expect(response_hash.pspReference).
501
+ to eq("12345")
502
+ end
503
+
504
+ it "makes a psp specific reversals call" do
505
+ request_body = JSON.parse(json_from_file("mocks/requests/Checkout/psp_cancel.json"))
506
+
507
+ response_body = json_from_file("mocks/responses/Checkout/psp_cancel.json")
508
+
509
+ url = @shared_values[:client].service_url(@shared_values[:service], "payments/12345/reversals", @shared_values[:client].checkout.version)
510
+ WebMock.stub_request(:post, url).
511
+ with(
512
+ body: request_body,
513
+ headers: {
514
+ "x-api-key" => @shared_values[:client].api_key
515
+ }
516
+ )
517
+ .to_return(body: response_body, status: 201)
518
+
519
+ result = @shared_values[:client].checkout.modifications.reversal("12345", request_body)
520
+ response_hash = result.response
521
+
522
+ expect(result.status).
523
+ to eq(201)
524
+ expect(response_hash).
525
+ to eq(JSON.parse(response_body))
526
+ expect(response_hash).
527
+ to be_a Adyen::HashWithAccessors
528
+ expect(response_hash).
529
+ to be_a_kind_of Hash
530
+ expect(response_hash.reference).
531
+ to eq("123456789")
532
+ expect(response_hash.pspReference).
533
+ to eq("12345")
534
+ end
535
+
536
+ it "makes a psp specific amountUpdates call" do
537
+ request_body = JSON.parse(json_from_file("mocks/requests/Checkout/amount_updates.json"))
538
+
539
+ response_body = json_from_file("mocks/responses/Checkout/amount_updates.json")
540
+
541
+ url = @shared_values[:client].service_url(@shared_values[:service], "payments/12345/amountUpdates", @shared_values[:client].checkout.version)
542
+ WebMock.stub_request(:post, url).
543
+ with(
544
+ body: request_body,
545
+ headers: {
546
+ "x-api-key" => @shared_values[:client].api_key
547
+ }
548
+ )
549
+ .to_return(body: response_body, status: 201)
550
+
551
+ result = @shared_values[:client].checkout.modifications.amountUpdate("12345", request_body)
552
+ response_hash = result.response
553
+
554
+ expect(result.status).
555
+ to eq(201)
556
+ expect(response_hash).
557
+ to eq(JSON.parse(response_body))
558
+ expect(response_hash).
559
+ to be_a Adyen::HashWithAccessors
560
+ expect(response_hash).
561
+ to be_a_kind_of Hash
562
+ expect(response_hash.reference).
563
+ to eq("123456789")
564
+ expect(response_hash.pspReference).
565
+ to eq("12345")
566
+ end
567
+
568
+ it "makes a generic cancel call" do
569
+ request_body = JSON.parse(json_from_file("mocks/requests/Checkout/generic_cancel.json"))
570
+
571
+ response_body = json_from_file("mocks/responses/Checkout/generic_cancel.json")
572
+
573
+ url = @shared_values[:client].service_url(@shared_values[:service], "cancels", @shared_values[:client].checkout.version)
574
+ WebMock.stub_request(:post, url).
575
+ with(
576
+ body: request_body,
577
+ headers: {
578
+ "x-api-key" => @shared_values[:client].api_key
579
+ }
580
+ )
581
+ .to_return(body: response_body, status: 201)
582
+
583
+ result = @shared_values[:client].checkout.modifications.genericCancel(request_body)
584
+ response_hash = result.response
585
+
586
+ expect(result.status).
587
+ to eq(201)
588
+ expect(response_hash).
589
+ to eq(JSON.parse(response_body))
590
+ expect(response_hash).
591
+ to be_a Adyen::HashWithAccessors
592
+ expect(response_hash).
593
+ to be_a_kind_of Hash
594
+ expect(response_hash.reference).
595
+ to eq("123456789")
596
+ expect(response_hash.pspReference).
597
+ to eq("12345")
598
+ end
599
+
600
+ it "makes a get storedPaymentMethods call" do
601
+ response_body = json_from_file("mocks/responses/Checkout/stored_payment_methods.json")
602
+
603
+ url = @shared_values[:client].service_url(@shared_values[:service], "storedPaymentMethods?merchantAccount=TestMerchantAccount&shopperReference=test-1234", @shared_values[:client].checkout.version)
604
+ WebMock.stub_request(:get, url).
605
+ with(
606
+ headers: {
607
+ "x-api-key" => @shared_values[:client].api_key
608
+ }
609
+ ).
610
+ to_return(
611
+ body: response_body
612
+ )
613
+
614
+ result = @shared_values[:client].checkout.stored_payment_methods.get({"merchantAccount" => "TestMerchantAccount", "shopperReference" => "test-1234"})
615
+ response_hash = result.response
616
+
617
+ expect(result.status).
618
+ to eq(200)
619
+ expect(response_hash).
620
+ to eq(JSON.parse(response_body))
621
+ expect(response_hash).
622
+ to be_a Adyen::HashWithAccessors
623
+ expect(response_hash).
624
+ to be_a_kind_of Hash
625
+ expect(response_hash["shopperReference"]).
626
+ to eq("test-1234")
627
+ end
628
+
629
+ it "makes a delete storedPaymentMethods call" do
630
+ response_body = json_from_file("mocks/responses/Checkout/stored_payment_methods.json")
631
+
632
+ url = @shared_values[:client].service_url(@shared_values[:service], "storedPaymentMethods/RL8FW7WZM6KXWD82?merchantAccount=TestMerchantAccount&shopperReference=test-1234", @shared_values[:client].checkout.version)
633
+ WebMock.stub_request(:delete, url).
634
+ with(
635
+ headers: {
636
+ "x-api-key" => @shared_values[:client].api_key
637
+ }
638
+ ).
639
+ to_return(
640
+ body: response_body
641
+ )
642
+
643
+ result = @shared_values[:client].checkout.stored_payment_methods.delete("RL8FW7WZM6KXWD82", {"merchantAccount" => "TestMerchantAccount", "shopperReference" => "test-1234"})
644
+ response_hash = result.response
645
+
646
+ expect(result.status).
647
+ to eq(200)
648
+ end
649
+
408
650
  # create client for automated tests
409
651
  client = create_client(:api_key)
410
652
 
data/spec/client_spec.rb CHANGED
@@ -83,4 +83,31 @@ RSpec.describe Adyen do
83
83
  expect(client.service_url_base("Payment")).
84
84
  to eq("https://abcdef1234567890-TestCompany-pal-live.adyenpayments.com/pal/servlet")
85
85
  end
86
+
87
+ it "generates a new set of ConnectionOptions when none are provided" do
88
+ expect(Faraday::ConnectionOptions).to receive(:new).and_call_original
89
+ client = Adyen::Client.new(env: :test)
90
+ end
91
+
92
+ it "uses the ConnectionOptions provided" do
93
+ connection_options = Faraday::ConnectionOptions.new
94
+ expect(Faraday::ConnectionOptions).not_to receive(:new)
95
+ client = Adyen::Client.new(env: :test, connection_options: connection_options)
96
+ end
97
+
98
+ it "initiates a Faraday connection with the provided options" do
99
+ connection_options = Faraday::ConnectionOptions.new
100
+ expect(Faraday::ConnectionOptions).not_to receive(:new)
101
+ client = Adyen::Client.new(api_key: "api_key", env: :mock, connection_options: connection_options)
102
+
103
+ mock_faraday_connection = double(Faraday::Connection)
104
+ url = client.service_url(@shared_values[:service], "payments/details", client.checkout.version)
105
+ request_body = JSON.parse(json_from_file("mocks/requests/Checkout/payment-details.json"))
106
+ mock_response = Faraday::Response.new(status: 200)
107
+
108
+ expect(Adyen::AdyenResult).to receive(:new)
109
+ expect(Faraday).to receive(:new).with("http://localhost:3001/v70/payments/details", connection_options).and_return(mock_faraday_connection)
110
+ expect(mock_faraday_connection).to receive(:post).and_return(mock_response)
111
+ client.checkout.payments.details(request_body)
112
+ end
86
113
  end
data/spec/errors_spec.rb CHANGED
@@ -33,7 +33,7 @@ RSpec.describe Adyen::AdyenError do
33
33
  expect(Adyen::AdyenError.new(@shared_values[:request], nil, nil, 'code').to_s).to eq("Adyen::AdyenError code:code, request:#{@shared_values[:request]}")
34
34
  end
35
35
  it 'uses the proper error class name' do
36
- expect(Adyen::PermissionError.new('message', @shared_values[:request]).to_s).to eq("Adyen::PermissionError code:403, msg:message, request:#{@shared_values[:request]}")
36
+ expect(Adyen::PermissionError.new('message', @shared_values[:request], 'response').to_s).to eq("Adyen::PermissionError code:403, msg:message, request:#{@shared_values[:request]}, response:response")
37
37
  end
38
38
  end
39
39
  describe '#masking' do
@@ -0,0 +1,22 @@
1
+ {
2
+ "amount": {
3
+ "currency": "str",
4
+ "value": 0
5
+ },
6
+ "merchantAccount": "TestMerchant",
7
+ "reason": "delayedCharge",
8
+ "reference": "123456789",
9
+ "splits": [
10
+ {
11
+ "account": "string",
12
+ "amount": {
13
+ "currency": "str",
14
+ "value": 0
15
+ },
16
+ "description": "string",
17
+ "reference": "string",
18
+ "type": "BalanceAccount"
19
+ }
20
+ ]
21
+ }
22
+
@@ -0,0 +1,34 @@
1
+ {
2
+ "amount": {
3
+ "currency": "str",
4
+ "value": 0
5
+ },
6
+ "lineItems": [
7
+ {
8
+ "amountExcludingTax": 0,
9
+ "amountIncludingTax": 0,
10
+ "description": "string",
11
+ "id": "string",
12
+ "imageUrl": "string",
13
+ "itemCategory": "string",
14
+ "productUrl": "string",
15
+ "quantity": 0,
16
+ "taxAmount": 0,
17
+ "taxPercentage": 0
18
+ }
19
+ ],
20
+ "merchantAccount": "string",
21
+ "reference": "string",
22
+ "splits": [
23
+ {
24
+ "account": "string",
25
+ "amount": {
26
+ "currency": "str",
27
+ "value": 0
28
+ },
29
+ "description": "string",
30
+ "reference": "string",
31
+ "type": "BalanceAccount"
32
+ }
33
+ ]
34
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "merchantAccount": "TestMerchant",
3
+ "paymentReference": "12345",
4
+ "reference": "123456789"
5
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "merchantAccount": "TestMerchant",
3
+ "reference": "123456789"
4
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "amount": {
3
+ "currency": "str",
4
+ "value": 0
5
+ },
6
+ "lineItems": [
7
+ {
8
+ "amountExcludingTax": 0,
9
+ "amountIncludingTax": 0,
10
+ "description": "string",
11
+ "id": "string",
12
+ "imageUrl": "string",
13
+ "itemCategory": "string",
14
+ "productUrl": "string",
15
+ "quantity": 0,
16
+ "taxAmount": 0,
17
+ "taxPercentage": 0
18
+ }
19
+ ],
20
+ "merchantAccount": "TestMerchant",
21
+ "reference": "123456789",
22
+ "splits": [
23
+ {
24
+ "account": "string",
25
+ "amount": {
26
+ "currency": "str",
27
+ "value": 0
28
+ },
29
+ "description": "string",
30
+ "reference": "string",
31
+ "type": "BalanceAccount"
32
+ }
33
+ ]
34
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "amount": {
3
+ "currency": "str",
4
+ "value": 0
5
+ },
6
+ "merchantAccount": "TestMerchant",
7
+ "paymentPspReference": "123456789",
8
+ "pspReference": "12345",
9
+ "reason": "delayedCharge",
10
+ "reference": "123456789",
11
+ "splits": [
12
+ {
13
+ "account": "string",
14
+ "amount": {
15
+ "currency": "str",
16
+ "value": 0
17
+ },
18
+ "description": "string",
19
+ "reference": "string",
20
+ "type": "BalanceAccount"
21
+ }
22
+ ],
23
+ "status": "received"
24
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "amount": {
3
+ "currency": "str",
4
+ "value": 0
5
+ },
6
+ "lineItems": [
7
+ {
8
+ "amountExcludingTax": 0,
9
+ "amountIncludingTax": 0,
10
+ "description": "string",
11
+ "id": "string",
12
+ "imageUrl": "string",
13
+ "itemCategory": "string",
14
+ "productUrl": "string",
15
+ "quantity": 0,
16
+ "taxAmount": 0,
17
+ "taxPercentage": 0
18
+ }
19
+ ],
20
+ "merchantAccount": "string",
21
+ "paymentPspReference": "string",
22
+ "pspReference": "12345",
23
+ "reference": "123456789",
24
+ "splits": [
25
+ {
26
+ "account": "string",
27
+ "amount": {
28
+ "currency": "str",
29
+ "value": 0
30
+ },
31
+ "description": "string",
32
+ "reference": "string",
33
+ "type": "BalanceAccount"
34
+ }
35
+ ],
36
+ "status": "received"
37
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "merchantAccount": "string",
3
+ "paymentReference": "123456789",
4
+ "pspReference": "12345",
5
+ "reference": "123456789",
6
+ "status": "received"
7
+ }
File without changes
@@ -0,0 +1,7 @@
1
+ {
2
+ "merchantAccount": "TestMerchant",
3
+ "paymentPspReference": "string",
4
+ "pspReference": "12345",
5
+ "reference": "123456789",
6
+ "status": "received"
7
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "amount": {
3
+ "currency": "str",
4
+ "value": 0
5
+ },
6
+ "lineItems": [
7
+ {
8
+ "amountExcludingTax": 0,
9
+ "amountIncludingTax": 0,
10
+ "description": "string",
11
+ "id": "string",
12
+ "imageUrl": "string",
13
+ "itemCategory": "string",
14
+ "productUrl": "string",
15
+ "quantity": 0,
16
+ "taxAmount": 0,
17
+ "taxPercentage": 0
18
+ }
19
+ ],
20
+ "merchantAccount": "TestMerchant",
21
+ "paymentPspReference": "123456789",
22
+ "pspReference": "12345",
23
+ "reference": "123456789",
24
+ "splits": [
25
+ {
26
+ "account": "string",
27
+ "amount": {
28
+ "currency": "str",
29
+ "value": 0
30
+ },
31
+ "description": "string",
32
+ "reference": "string",
33
+ "type": "BalanceAccount"
34
+ }
35
+ ],
36
+ "status": "received"
37
+ }
@@ -0,0 +1 @@
1
+ {"merchantAccount":"TestMerchantAccount", "shopperReference":"test-1234"}
@@ -0,0 +1,41 @@
1
+ {
2
+ "additionalData": {
3
+ "acquirerCode": "TestPmmAcquirer",
4
+ "acquirerReference": "DZMKWLXW6N6",
5
+ "authCode": "076181",
6
+ "avsResult": "5 No AVS data provided",
7
+ "avsResultRaw": "5",
8
+ "cardSummary": "1111",
9
+ "checkout.cardAddedBrand": "visa",
10
+ "cvcResult": "1 Matches",
11
+ "cvcResultRaw": "M",
12
+ "expiryDate": "03/2030",
13
+ "hmacSignature": "nIgT81gaB5oJpn2jPXupDq68iRo2wUlBsuYjtYfwKqo=",
14
+ "paymentMethod": "visa",
15
+ "refusalReasonRaw": "AUTHORISED",
16
+ "retry.attempt1.acquirer": "TestPmmAcquirer",
17
+ "retry.attempt1.acquirerAccount": "TestPmmAcquirerAccount",
18
+ "retry.attempt1.avsResultRaw": "5",
19
+ "retry.attempt1.rawResponse": "AUTHORISED",
20
+ "retry.attempt1.responseCode": "Approved",
21
+ "retry.attempt1.scaExemptionRequested": "lowValue",
22
+ "scaExemptionRequested": "lowValue"
23
+ },
24
+ "amount": {
25
+ "currency": "EUR",
26
+ "value": 1000
27
+ },
28
+ "eventCode": "AUTHORISATION",
29
+ "eventDate": "2023-01-09T16:27:29+01:00",
30
+ "merchantAccountCode": "AntoniStroinski",
31
+ "merchantReference": "\\\\slashes are fun",
32
+ "operations": [
33
+ "CANCEL",
34
+ "CAPTURE",
35
+ "REFUND"
36
+ ],
37
+ "paymentMethod": "visa",
38
+ "pspReference": "T7FD4VM4D3RZNN82",
39
+ "reason": "076181:1111:03/2030",
40
+ "success": "true"
41
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "additionalData": {
3
+ "acquirerCode": "TestPmmAcquirer",
4
+ "acquirerReference": "8NQH5BNF58M",
5
+ "authCode": "039404",
6
+ "avsResult": "5 No AVS data provided",
7
+ "avsResultRaw": "5",
8
+ "cardSummary": "1111",
9
+ "checkout.cardAddedBrand": "visa",
10
+ "cvcResult": "1 Matches",
11
+ "cvcResultRaw": "M",
12
+ "expiryDate": "03/2030",
13
+ "hmacSignature": "2EQYm7YJpKO4EtHSPu55SQTyWf8dkW5u2nD1tJFpViA=",
14
+ "paymentMethod": "visa",
15
+ "refusalReasonRaw": "AUTHORISED",
16
+ "retry.attempt1.acquirer": "TestPmmAcquirer",
17
+ "retry.attempt1.acquirerAccount": "TestPmmAcquirerAccount",
18
+ "retry.attempt1.avsResultRaw": "5",
19
+ "retry.attempt1.rawResponse": "AUTHORISED",
20
+ "retry.attempt1.responseCode": "Approved",
21
+ "retry.attempt1.scaExemptionRequested": "lowValue",
22
+ "scaExemptionRequested": "lowValue"
23
+ },
24
+ "amount": {
25
+ "currency": "EUR",
26
+ "value": 1000
27
+ },
28
+ "eventCode": "AUTHORISATION",
29
+ "eventDate": "2023-01-10T13:40:54+01:00",
30
+ "merchantAccountCode": "AntoniStroinski",
31
+ "merchantReference": ":slashes are fun",
32
+ "operations": [
33
+ "CANCEL",
34
+ "CAPTURE",
35
+ "REFUND"
36
+ ],
37
+ "paymentMethod": "visa",
38
+ "pspReference": "M8NB66SBZSGLNK82",
39
+ "reason": "039404:1111:03/2030",
40
+ "success": "true"
41
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "amount": {
3
+ "value": 1000,
4
+ "currency": "EUR"
5
+ },
6
+ "reason": "087330:1111:03/2030",
7
+ "success": "true",
8
+ "eventCode": "AUTHORISATION",
9
+ "eventDate": "2023-01-10T13:37:30+01:00",
10
+ "operations": [
11
+ "CANCEL",
12
+ "CAPTURE",
13
+ "REFUND"
14
+ ],
15
+ "pspReference": "X3GWNS6KJ8NKGK82",
16
+ "paymentMethod": "visa",
17
+ "additionalData": {
18
+ "authCode": "087330",
19
+ "avsResult": "5 No AVS data provided",
20
+ "cvcResult": "1 Matches",
21
+ "expiryDate": "03/2030",
22
+ "cardSummary": "1111",
23
+ "acquirerCode": "TestPmmAcquirer",
24
+ "avsResultRaw": "5",
25
+ "cvcResultRaw": "M",
26
+ "hmacSignature": "9Z0xdpG9Xi3zcmXv14t/BvMBut77O/Xq9D4CQXSDUi4=",
27
+ "paymentMethod": "visa",
28
+ "refusalReasonRaw": "AUTHORISED",
29
+ "acquirerReference": "HHCCC326PH6",
30
+ "scaExemptionRequested": "lowValue",
31
+ "checkout.cardAddedBrand": "visa",
32
+ "retry.attempt1.acquirer": "TestPmmAcquirer",
33
+ "retry.attempt1.rawResponse": "AUTHORISED",
34
+ "retry.attempt1.avsResultRaw": "5",
35
+ "retry.attempt1.responseCode": "Approved",
36
+ "retry.attempt1.acquirerAccount": "TestPmmAcquirerAccount",
37
+ "retry.attempt1.scaExemptionRequested": "lowValue"
38
+ },
39
+ "merchantReference": "//slashes are fun",
40
+ "merchantAccountCode": "AntoniStroinski"
41
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "additionalData": {
3
+ "acquirerCode": "TestPmmAcquirer",
4
+ "acquirerReference": "J8DXDJ2PV6P",
5
+ "authCode": "052095",
6
+ "avsResult": "5 No AVS data provided",
7
+ "avsResultRaw": "5",
8
+ "cardSummary": "1111",
9
+ "checkout.cardAddedBrand": "visa",
10
+ "cvcResult": "1 Matches",
11
+ "cvcResultRaw": "M",
12
+ "expiryDate": "03/2030",
13
+ "hmacSignature": "CZErGCNQaSsxbaQfZaJlakqo7KPP+mIa8a+wx3yNs9A=",
14
+ "paymentMethod": "visa",
15
+ "refusalReasonRaw": "AUTHORISED",
16
+ "retry.attempt1.acquirer": "TestPmmAcquirer",
17
+ "retry.attempt1.acquirerAccount": "TestPmmAcquirerAccount",
18
+ "retry.attempt1.avsResultRaw": "5",
19
+ "retry.attempt1.rawResponse": "AUTHORISED",
20
+ "retry.attempt1.responseCode": "Approved",
21
+ "retry.attempt1.scaExemptionRequested": "lowValue",
22
+ "scaExemptionRequested": "lowValue"
23
+ },
24
+ "amount": {
25
+ "currency": "EUR",
26
+ "value": 1000
27
+ },
28
+ "eventCode": "AUTHORISATION",
29
+ "eventDate": "2023-01-10T13:42:29+01:00",
30
+ "merchantAccountCode": "AntoniStroinski",
31
+ "merchantReference": "\\:/\\/slashes are fun",
32
+ "operations": [
33
+ "CANCEL",
34
+ "CAPTURE",
35
+ "REFUND"
36
+ ],
37
+ "paymentMethod": "visa",
38
+ "pspReference": "ZVWN7D3WSMK2WN82",
39
+ "reason": "052095:1111:03/2030",
40
+ "success": "true"
41
+ }
@@ -28,12 +28,6 @@ RSpec.describe Adyen::Utils::HmacValidator do
28
28
  expect(data_to_sign).to eq '7914073381342284::TestMerchant:TestPayment-1407325143704:1130:EUR:AUTHORISATION:true'
29
29
  end
30
30
 
31
- it 'should get correct data with escaped characters' do
32
- notification_request_item['merchantAccountCode'] = 'Test:\\Merchant'
33
- data_to_sign = validator.data_to_sign(notification_request_item)
34
- expect(data_to_sign).to eq '7914073381342284::Test\\:\\Merchant:TestPayment-1407325143704:1130:EUR:AUTHORISATION:true'
35
- end
36
-
37
31
  it 'should encrypt properly' do
38
32
  encrypted = validator.calculate_notification_hmac(notification_request_item, key)
39
33
  expect(encrypted).to eq expected_sign
@@ -48,5 +42,25 @@ RSpec.describe Adyen::Utils::HmacValidator do
48
42
 
49
43
  expect(validator.valid_notification_hmac?(notification_request_item, key)).to be false
50
44
  end
45
+
46
+ it 'should validate backslashes correctly' do
47
+ webhook = JSON.parse(json_from_file("mocks/responses/Webhooks/backslash_notification.json"))
48
+ expect(validator.valid_notification_hmac?(webhook, '74F490DD33F7327BAECC88B2947C011FC02D014A473AAA33A8EC93E4DC069174')).to be true
49
+ end
50
+
51
+ it 'should validate colons correctly' do
52
+ webhook = JSON.parse(json_from_file("mocks/responses/Webhooks/colon_notification.json"))
53
+ expect(validator.valid_notification_hmac?(webhook, '74F490DD33F7327BAECC88B2947C011FC02D014A473AAA33A8EC93E4DC069174')).to be true
54
+ end
55
+
56
+ it 'should validate forward slashes correctly' do
57
+ webhook = JSON.parse(json_from_file("mocks/responses/Webhooks/forwardslash_notification.json"))
58
+ expect(validator.valid_notification_hmac?(webhook, '74F490DD33F7327BAECC88B2947C011FC02D014A473AAA33A8EC93E4DC069174')).to be true
59
+ end
60
+
61
+ it 'should validate mix of slashes and colon correctly' do
62
+ webhook = JSON.parse(json_from_file("mocks/responses/Webhooks/mixed_notification.json"))
63
+ expect(validator.valid_notification_hmac?(webhook, '74F490DD33F7327BAECC88B2947C011FC02D014A473AAA33A8EC93E4DC069174')).to be true
64
+ end
51
65
  end
52
66
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: adyen-ruby-api-library
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.1.0
4
+ version: 6.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adyen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-12 00:00:00.000000000 Z
11
+ date: 2023-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -81,7 +81,9 @@ files:
81
81
  - ".github/ISSUE_TEMPLATE/feature_request.md"
82
82
  - ".github/PULL_REQUEST_TEMPLATE.md"
83
83
  - ".github/dependabot.yml"
84
+ - ".github/workflows/codeql.yml"
84
85
  - ".github/workflows/ruby.yml"
86
+ - ".github/workflows/rubygems_release.yml"
85
87
  - ".gitignore"
86
88
  - CODE_OF_CONDUCT.md
87
89
  - CONTRIBUTING.md
@@ -153,7 +155,11 @@ files:
153
155
  - spec/mocks/requests/Account/upload_document.json
154
156
  - spec/mocks/requests/BinLookup/get_3ds_availability.json
155
157
  - spec/mocks/requests/BinLookup/get_cost_estimate.json
158
+ - spec/mocks/requests/Checkout/amount_updates.json
156
159
  - spec/mocks/requests/Checkout/apple_pay_sessions.json
160
+ - spec/mocks/requests/Checkout/capture.json
161
+ - spec/mocks/requests/Checkout/generic_cancel.json
162
+ - spec/mocks/requests/Checkout/modifications_request.json
157
163
  - spec/mocks/requests/Checkout/orders.json
158
164
  - spec/mocks/requests/Checkout/orders_cancel.json
159
165
  - spec/mocks/requests/Checkout/origin_keys.json
@@ -164,6 +170,8 @@ files:
164
170
  - spec/mocks/requests/Checkout/payment_methods_balance.json
165
171
  - spec/mocks/requests/Checkout/payment_session.json
166
172
  - spec/mocks/requests/Checkout/payments.json
173
+ - spec/mocks/requests/Checkout/psp_cancel.json
174
+ - spec/mocks/requests/Checkout/refund.json
167
175
  - spec/mocks/requests/Checkout/sessions.json
168
176
  - spec/mocks/requests/Checkout/verify.json
169
177
  - spec/mocks/requests/DataProtectionService/request_subject_erasure.json
@@ -230,8 +238,12 @@ files:
230
238
  - spec/mocks/responses/Account/upload_document.json
231
239
  - spec/mocks/responses/BinLookup/get_3ds_availability.json
232
240
  - spec/mocks/responses/BinLookup/get_cost_estimate.json
241
+ - spec/mocks/responses/Checkout/amount_updates.json
233
242
  - spec/mocks/responses/Checkout/apple_pay_sessions.json
243
+ - spec/mocks/responses/Checkout/capture.json
244
+ - spec/mocks/responses/Checkout/generic_cancel.json
234
245
  - spec/mocks/responses/Checkout/get-payment-link.json
246
+ - spec/mocks/responses/Checkout/modifications.json
235
247
  - spec/mocks/responses/Checkout/orders.json
236
248
  - spec/mocks/responses/Checkout/orders_cancel.json
237
249
  - spec/mocks/responses/Checkout/origin_keys.json
@@ -242,7 +254,10 @@ files:
242
254
  - spec/mocks/responses/Checkout/payment_methods_balance.json
243
255
  - spec/mocks/responses/Checkout/payment_session.json
244
256
  - spec/mocks/responses/Checkout/payments.json
257
+ - spec/mocks/responses/Checkout/psp_cancel.json
258
+ - spec/mocks/responses/Checkout/refund.json
245
259
  - spec/mocks/responses/Checkout/sessions-success.json
260
+ - spec/mocks/responses/Checkout/stored_payment_methods.json
246
261
  - spec/mocks/responses/Checkout/update-payment-link.json
247
262
  - spec/mocks/responses/Checkout/verify.json
248
263
  - spec/mocks/responses/DataProtectionService/request_subject_erasure.json
@@ -290,6 +305,10 @@ files:
290
305
  - spec/mocks/responses/Terminal/assign_terminals.json
291
306
  - spec/mocks/responses/Terminal/find_terminal.json
292
307
  - spec/mocks/responses/Terminal/get_terminals_under_account.json
308
+ - spec/mocks/responses/Webhooks/backslash_notification.json
309
+ - spec/mocks/responses/Webhooks/colon_notification.json
310
+ - spec/mocks/responses/Webhooks/forwardslash_notification.json
311
+ - spec/mocks/responses/Webhooks/mixed_notification.json
293
312
  - spec/notification_spec.rb
294
313
  - spec/payments_spec.rb
295
314
  - spec/payouts_spec.rb