checkout_sdk 2.8.1 → 2.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9849ed54d294ed9bea5fdb04bb5378fb55b942a2adb0ffe0653cb5c628cc0882
4
- data.tar.gz: 7bd35374ce13e3feb72eb37a16930316ab6a01ce2cc10c49245f3e5a2daf6229
3
+ metadata.gz: ddcb702f44152460535a2f068b532e473f5076ab558231ab49087fa89b85bc5e
4
+ data.tar.gz: 44453c69fdf11cb1e35e18d22aade6e0fdfc7bed1eeab8db230179e9439fb3f7
5
5
  SHA512:
6
- metadata.gz: b6f8c31a1c66aecb8b8fad6345a2b693f133b8bee59376920445caa7779db02a3f18882170318870c5f86fb728d898a4baffc32a0b71159d8b1fa4e17087c144
7
- data.tar.gz: 6bdaf1a3c38c96bcafe0f52135ff77edda7a51d66946df1bc7d4b4d8e9e5729bf85f017e0a3a755567466e9132b4464428b7861ee58e56040fbf74294bc97598
6
+ metadata.gz: 5fc31731ba185059c64e43c7c54025fb7e61ec53df0317d6b8115cf10e131da37e187eaafa74ba0f93569ed3e8ef2ca4ac1b36a4ac5044d26f392ab8d9c4034f
7
+ data.tar.gz: 7318c1c93ad5979a27514c98c486985776648c19aa71544d07b026f8eb812ac1dbecd2c53531796b45f41c581785328fbda42b7c836443da4c0770d792455207
data/README.md CHANGED
@@ -151,6 +151,11 @@ All the API responses that do not fall in the 2** status codes will cause a `Che
151
151
  exception
152
152
  encapsulates the `http_metadata` and a map of `error_details`, if available.
153
153
 
154
+ Identifiers passed to client methods (for example a payment id) are sent as single, percent-encoded path
155
+ segments: pass them unencoded. A `CheckoutSdk::CheckoutArgumentException` is raised before any request is sent
156
+ when an identifier is `nil`, empty, `.` or `..`, cannot be converted to valid UTF-8, or contains `/`, `\` or an
157
+ ASCII control character (U+0000 to U+001F, U+007F).
158
+
154
159
  ## Custom Http Client
155
160
 
156
161
  Ruby SDK supports your own configuration for `http client` using `Faraday::Connection` object, you can pass
@@ -5,8 +5,9 @@ module CheckoutSdk
5
5
  # Bacs Direct Debit client.
6
6
  class BacsClient < Client
7
7
  APMS = 'apms'
8
- BACS_NOTIFICATIONS = 'bacs/notifications'
9
- private_constant :APMS, :BACS_NOTIFICATIONS
8
+ BACS = 'bacs'
9
+ NOTIFICATIONS = 'notifications'
10
+ private_constant :APMS, :BACS, :NOTIFICATIONS
10
11
 
11
12
  # @param [ApiClient] api_client
12
13
  # @param [CheckoutConfiguration] configuration
@@ -20,7 +21,7 @@ module CheckoutSdk
20
21
  # @param [Hash, BacsNotificationRequest] bacs_notification_request
21
22
  # @return [Hash] the BacsNotificationResponse, carrying event_id.
22
23
  def send_notification(bacs_notification_request)
23
- api_client.invoke_post(build_path(APMS, BACS_NOTIFICATIONS),
24
+ api_client.invoke_post(build_path(APMS, BACS, NOTIFICATIONS),
24
25
  sdk_authorization,
25
26
  bacs_notification_request)
26
27
  end
@@ -5,10 +5,11 @@ module CheckoutSdk
5
5
  module Apm
6
6
  class SepaClient < Client
7
7
  APMS = 'apms'
8
- SEPA_MANDATES = 'sepa/mandates'
8
+ SEPA = 'sepa'
9
+ MANDATES = 'mandates'
9
10
  PPRO = 'ppro'
10
11
  CANCEL = 'cancel'
11
- private_constant :APMS, :SEPA_MANDATES, :PPRO, :CANCEL
12
+ private_constant :APMS, :SEPA, :MANDATES, :PPRO, :CANCEL
12
13
 
13
14
  # @param [ApiClient] api_client
14
15
  # @param [CheckoutConfiguration] configuration
@@ -18,22 +19,22 @@ module CheckoutSdk
18
19
 
19
20
  # @param mandate_id [String]
20
21
  def get_mandate(mandate_id)
21
- api_client.invoke_get(build_path(SEPA_MANDATES, mandate_id), sdk_authorization)
22
+ api_client.invoke_get(build_path(SEPA, MANDATES, mandate_id), sdk_authorization)
22
23
  end
23
24
 
24
25
  # @param mandate_id [String]
25
26
  def cancel_mandate(mandate_id)
26
- api_client.invoke_post(build_path(SEPA_MANDATES, mandate_id, CANCEL), sdk_authorization)
27
+ api_client.invoke_post(build_path(SEPA, MANDATES, mandate_id, CANCEL), sdk_authorization)
27
28
  end
28
29
 
29
30
  # @param mandate_id [String]
30
31
  def get_mandate_via_ppro(mandate_id)
31
- api_client.invoke_get(build_path(APMS, PPRO, SEPA_MANDATES, mandate_id), sdk_authorization)
32
+ api_client.invoke_get(build_path(APMS, PPRO, SEPA, MANDATES, mandate_id), sdk_authorization)
32
33
  end
33
34
 
34
35
  # @param mandate_id [String]
35
36
  def cancel_mandate_via_ppro(mandate_id)
36
- api_client.invoke_post(build_path(APMS, PPRO, SEPA_MANDATES, mandate_id, CANCEL), sdk_authorization)
37
+ api_client.invoke_post(build_path(APMS, PPRO, SEPA, MANDATES, mandate_id, CANCEL), sdk_authorization)
37
38
  end
38
39
  end
39
40
  end
@@ -39,9 +39,8 @@ module CheckoutSdk
39
39
  # @param [String] currency_account_id the ID of the sub-account to retrieve top-up
40
40
  # instructions for
41
41
  # @return [Hash] the top-up instructions response
42
- # @raise [CheckoutArgumentException] if either path parameter is nil, empty or blank. Both
43
- # segments are interpolated straight into the request path, so a blank value would build a
44
- # malformed URL and be rejected by the API rather than by the SDK.
42
+ # @raise [CheckoutArgumentException] if either path parameter is nil, empty or blank, or is not
43
+ # a valid path segment (see Client#build_path).
45
44
  def retrieve_top_up_instructions(entity_id, currency_account_id)
46
45
  raise CheckoutArgumentException, 'entity_id cannot be blank' if entity_id.to_s.strip.empty?
47
46
  raise CheckoutArgumentException, 'currency_account_id cannot be blank' if currency_account_id.to_s.strip.empty?
@@ -52,6 +52,37 @@ module CheckoutSdk
52
52
  "#{prefix} Details: #{escape_control_characters(details)}."
53
53
  end
54
54
 
55
+ INVALID_PATH_SEGMENTS = ['', '.', '..'].freeze
56
+ FORBIDDEN_PATH_SEGMENT_BYTES = %r{[/\\\x00-\x1F\x7F]}n.freeze
57
+ INVALID_PATH_SEGMENT_MESSAGE = "Invalid path segment: it must be valid text, not nil, empty, '.' or '..', " \
58
+ "and must not contain '/', '\\' or ASCII control characters"
59
+ private_constant :INVALID_PATH_SEGMENTS, :FORBIDDEN_PATH_SEGMENT_BYTES, :INVALID_PATH_SEGMENT_MESSAGE
60
+
61
+ # Encodes a value as one RFC 3986 path segment. The value is converted to UTF-8 and every byte
62
+ # outside the unreserved set (A-Z, a-z, 0-9, "-", ".", "_", "~") is percent-encoded, so the value
63
+ # always stays a single segment of the request path. The checks run on the converted text.
64
+ #
65
+ # @param [String, Symbol, Integer] value
66
+ # @return [String]
67
+ # @raise [CheckoutArgumentException] when the value is nil, empty, "." or "..", cannot be converted
68
+ # to valid UTF-8, contains "/", "\" or an ASCII control character (U+0000 to U+001F, U+007F), or
69
+ # its to_s does not return a String
70
+ def self.encode_path_segment(value)
71
+ text = value.nil? ? '' : value.to_s
72
+ raise CheckoutArgumentException, INVALID_PATH_SEGMENT_MESSAGE unless text.is_a?(String)
73
+
74
+ bytes = to_valid_utf8(text)&.b
75
+ if bytes.nil? || FORBIDDEN_PATH_SEGMENT_BYTES.match?(bytes)
76
+ raise CheckoutArgumentException, INVALID_PATH_SEGMENT_MESSAGE
77
+ end
78
+
79
+ encoded = bytes.gsub(/[^A-Za-z0-9\-._~]/n) { |byte| format('%%%02X', byte.ord) }
80
+ .force_encoding(Encoding::UTF_8)
81
+ raise CheckoutArgumentException, INVALID_PATH_SEGMENT_MESSAGE if INVALID_PATH_SEGMENTS.include?(encoded)
82
+
83
+ encoded
84
+ end
85
+
55
86
  # Returns a copy of the Faraday error, of the same class and with the same backtrace and wrapped
56
87
  # exception, that keeps only the response status, headers and body and whose message names the
57
88
  # method and URL without the query string. Errors without a response are returned unchanged.
@@ -91,8 +122,21 @@ module CheckoutSdk
91
122
  end
92
123
  private_class_method :escape_control_characters
93
124
 
125
+ # Converts text to UTF-8, transcoding from its own encoding. Returns nil when the text cannot be
126
+ # converted or the result is not valid UTF-8.
127
+ def self.to_valid_utf8(text)
128
+ converted = text.encode(Encoding::UTF_8)
129
+ converted.valid_encoding? ? converted : nil
130
+ rescue EncodingError
131
+ nil
132
+ end
133
+ private_class_method :to_valid_utf8
134
+
135
+ # Lenient form of to_valid_utf8 for log and message text: bytes that cannot be converted are read
136
+ # as UTF-8 and invalid sequences are replaced.
94
137
  def self.utf8_text(text)
95
- text.to_s.dup.force_encoding(Encoding::UTF_8).scrub
138
+ text = text.to_s
139
+ (to_valid_utf8(text) || text.dup.force_encoding(Encoding::UTF_8)).scrub
96
140
  end
97
141
  private_class_method :utf8_text
98
142
 
@@ -23,9 +23,15 @@ module CheckoutSdk
23
23
  @configuration = configuration
24
24
  end
25
25
 
26
- # @param [Array] args
26
+ # Joins the arguments into a request path. Each argument is one path segment and is
27
+ # percent-encoded, so it never adds segments, a query or a fragment.
28
+ #
29
+ # @param [Array<String, Symbol, Integer>] args
30
+ # @return [String]
31
+ # @raise [CheckoutArgumentException] when an argument is not a valid path segment (see
32
+ # CheckoutUtils.encode_path_segment)
27
33
  def build_path(*args)
28
- args.join('/')
34
+ args.map { |segment| CheckoutUtils.encode_path_segment(segment) }.join('/')
29
35
  end
30
36
 
31
37
  # @return [TrueClass, FalseClass]
@@ -3,8 +3,9 @@
3
3
  module CheckoutSdk
4
4
  module Instruments
5
5
  class InstrumentsClient < BaseInstrumentsClient
6
- VALIDATION = 'validation/bank-accounts'
7
- private_constant :VALIDATION
6
+ VALIDATION = 'validation'
7
+ BANK_ACCOUNTS = 'bank-accounts'
8
+ private_constant :VALIDATION, :BANK_ACCOUNTS
8
9
 
9
10
  # @param [ApiClient] api_client
10
11
  # @param [CheckoutConfiguration] configuration
@@ -29,7 +30,7 @@ module CheckoutSdk
29
30
  # @param [String] currency {CheckoutSdk::Common::Currency}
30
31
  # @param [Hash, BankAccountFieldQuery] bank_account_field_query
31
32
  def get_bank_account_field_formatting(country, currency, bank_account_field_query)
32
- api_client.invoke_get(build_path(VALIDATION, country, currency),
33
+ api_client.invoke_get(build_path(VALIDATION, BANK_ACCOUNTS, country, currency),
33
34
  sdk_authorization(CheckoutSdk::AuthorizationType::OAUTH),
34
35
  bank_account_field_query)
35
36
  end
@@ -248,6 +248,8 @@ module CheckoutSdk
248
248
  # :schemes (supported card scheme names; Google Pay never lists cartes_bancaires) and card
249
249
  # details :last4, :bin, :scheme (Strings) and :expires_on (date-time); instrument returns the
250
250
  # read-only :type (String) and :action (:type, "set_instrument").
251
+ # @raise [CheckoutArgumentException] when id or payment_method_name is not a valid path segment
252
+ # (see Client#build_path)
251
253
  def confirm_payment_setup(id, payment_method_name)
252
254
  api_client.invoke_post(
253
255
  build_path(PAYMENTS_PATH, SETUPS_PATH, id, CONFIRM_PATH, payment_method_name),
@@ -5,9 +5,11 @@ module CheckoutSdk
5
5
  # @deprecated Risk endpoints are no longer supported officially, This module will be removed in a
6
6
  # future release.
7
7
  class RiskClient < Client
8
- PRE_AUTHENTICATION = 'risk/assessments/pre-authentication'
9
- PRE_CAPTURE = 'risk/assessments/pre-capture'
10
- private_constant :PRE_AUTHENTICATION, :PRE_CAPTURE
8
+ RISK = 'risk'
9
+ ASSESSMENTS = 'assessments'
10
+ PRE_AUTHENTICATION = 'pre-authentication'
11
+ PRE_CAPTURE = 'pre-capture'
12
+ private_constant :RISK, :ASSESSMENTS, :PRE_AUTHENTICATION, :PRE_CAPTURE
11
13
 
12
14
  # @param [ApiClient] api_client
13
15
  # @param [CheckoutConfiguration] configuration
@@ -17,12 +19,14 @@ module CheckoutSdk
17
19
 
18
20
  # @param [Hash, PreAuthenticationAssessment] pre_authentication_request
19
21
  def pre_authentication_scan(pre_authentication_request)
20
- api_client.invoke_post(PRE_AUTHENTICATION, sdk_authorization, pre_authentication_request)
22
+ api_client.invoke_post(build_path(RISK, ASSESSMENTS, PRE_AUTHENTICATION),
23
+ sdk_authorization,
24
+ pre_authentication_request)
21
25
  end
22
26
 
23
27
  # @param [Hash, PreCaptureAssessment] pre_capture_request
24
28
  def pre_capture_scan(pre_capture_request)
25
- api_client.invoke_post(PRE_CAPTURE, sdk_authorization, pre_capture_request)
29
+ api_client.invoke_post(build_path(RISK, ASSESSMENTS, PRE_CAPTURE), sdk_authorization, pre_capture_request)
26
30
  end
27
31
  end
28
32
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CheckoutSdk
4
- VERSION = '2.8.1'
4
+ VERSION = '2.8.2'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: checkout_sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.1
4
+ version: 2.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Checkout
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-11-04 00:00:00.000000000 Z
11
+ date: 2026-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake