killbill-client 4.0.6 → 4.0.7

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
  SHA1:
3
- metadata.gz: 31adb602c314f8de1252ffe021141878f4fcac84
4
- data.tar.gz: 21c6b2aa1caed17c653f9b68bab2f4486f64fcc6
3
+ metadata.gz: 8e86e4cbc6c49a01a442aeb497caacd4a7893948
4
+ data.tar.gz: f02a33231d29d69f6be5b5f8987b87cc7f62514f
5
5
  SHA512:
6
- metadata.gz: 955feb2439672126f43a76b3163560e3cf15ff150ae832c5e80b3c7fc9369dbcdca0e4480a2f7bc3f82b2e5740a6c48254a00110a9e7343685a846a6d8b73cfc
7
- data.tar.gz: b45da24a25e77779133b17338f50ac2ff5d41de5c7276701c72cbe7ebe51865b4583abbb5532add4f1005f61f4bd2d5835be506225f8870eb3ee2878d057098a
6
+ metadata.gz: a5d298382c7555f66ce46ceae00be264f335a419682671ec1233c9c3c22aa706f318563a287b270c638e815c8d09f8e152f9c6226ce6b349623762706d673ec4
7
+ data.tar.gz: 8de6e6aea0a680b4301c1d8cb688a89b939ce68ee7b40b12f1acf34986b5e2d74dc79c5679046339c10a4d039bc7ed47eb027587070870658bfeec3c318cf178
@@ -1,8 +1,9 @@
1
1
  name: ci
2
2
 
3
3
  on:
4
- push:
5
- workflow_dispatch:
4
+ - push
5
+ - workflow_dispatch
6
+ - pull_request
6
7
 
7
8
  env:
8
9
  COMPOSE_DOCKER_CLI_BUILD: 1
@@ -15,11 +16,11 @@ env:
15
16
 
16
17
  jobs:
17
18
  test:
18
- runs-on: ubuntu-latest
19
+ runs-on: ubuntu-22.04
19
20
  strategy:
20
21
  matrix:
21
22
  include:
22
- - ruby-version: '2.4.2'
23
+ - ruby-version: '3.2.2'
23
24
  database-adapter: 'mysql2'
24
25
  database-user: 'root'
25
26
  database-password: 'root'
@@ -31,13 +32,13 @@ jobs:
31
32
  database-password: 'postgres'
32
33
  database-port: '5432'
33
34
  docker-compose-file: 'docker-compose.ci.postgresql.yml'
34
- - ruby-version: 'jruby-9.1.17.0'
35
+ - ruby-version: 'jruby-9.4.2.0'
35
36
  database-adapter: 'mysql2'
36
37
  database-user: 'root'
37
38
  database-password: 'root'
38
39
  database-port: '3306'
39
40
  docker-compose-file: 'docker-compose.ci.mysql.yml'
40
- - ruby-version: '2.4.2'
41
+ - ruby-version: '3.3.5'
41
42
  database-adapter: 'postgresql'
42
43
  database-user: 'postgres'
43
44
  database-password: 'postgres'
@@ -49,7 +50,7 @@ jobs:
49
50
  database-password: 'postgres'
50
51
  database-port: '5432'
51
52
  docker-compose-file: 'docker-compose.ci.postgresql.yml'
52
- - ruby-version: 'jruby-9.1.17.0'
53
+ - ruby-version: 'jruby-9.4.2.0'
53
54
  database-adapter: 'postgresql'
54
55
  database-user: 'postgres'
55
56
  database-password: 'postgres'
@@ -57,7 +58,7 @@ jobs:
57
58
  docker-compose-file: 'docker-compose.ci.postgresql.yml'
58
59
  steps:
59
60
  - name: Checkout code
60
- uses: actions/checkout@v2
61
+ uses: actions/checkout@v4
61
62
  - name: Set up Ruby
62
63
  uses: ruby/setup-ruby@v1
63
64
  with:
@@ -66,8 +67,8 @@ jobs:
66
67
  - name: Start stack
67
68
  run: |
68
69
  cd docker
69
- docker-compose -p it -f ${{ matrix.docker-compose-file }} up --no-start
70
- docker start it_db_1
70
+ docker compose -p it -f ${{ matrix.docker-compose-file }} up --no-start
71
+ docker start it-db-1
71
72
  - name: Wait for MySQL
72
73
  if: ${{ matrix.docker-compose-file == 'docker-compose.ci.mysql.yml' }}
73
74
  run: |
@@ -100,7 +101,7 @@ jobs:
100
101
  # Sometimes it gets stuck (if Kill Bill starts when the DB isn't ready?)
101
102
  timeout-minutes: 4
102
103
  run: |
103
- docker start it_killbill_1
104
+ docker start it-killbill-1
104
105
  count=0
105
106
  until $(curl --connect-timeout 10 --max-time 30 --output /dev/null --silent --fail http://${KB_ADDRESS}:${KB_PORT}/1.0/healthcheck); do
106
107
  if [[ "$count" == "180" ]]; then
@@ -148,10 +149,10 @@ jobs:
148
149
  echo "[DEBUG] docker ps -a"
149
150
  docker ps -a
150
151
  echo "[DEBUG] killbill env"
151
- docker exec it_killbill_1 env || true
152
+ docker exec it-killbill-1 env || true
152
153
  echo "[DEBUG] db env"
153
- docker exec it_db_1 env || true
154
+ docker exec it-db-1 env || true
154
155
  echo "[DEBUG] killbill logs"
155
- docker logs -t --details it_killbill_1 || true
156
+ docker logs -t --details it-killbill-1 || true
156
157
  echo "[DEBUG] db logs"
157
- docker logs -t --details it_db_1 || true
158
+ docker logs -t --details it-db-1 || true
@@ -3,7 +3,7 @@ version: '3.8'
3
3
  services:
4
4
  killbill:
5
5
  network_mode: host
6
- image: killbill/killbill:0.22.20
6
+ image: killbill/killbill:0.24.0
7
7
  environment:
8
8
  - KILLBILL_CATALOG_URI=SpyCarAdvanced.xml
9
9
  - KILLBILL_DAO_URL=jdbc:mysql://127.0.0.1:3306/killbill
@@ -16,6 +16,6 @@ services:
16
16
  - db
17
17
  db:
18
18
  network_mode: host
19
- image: killbill/mariadb:0.22
19
+ image: killbill/mariadb:0.24
20
20
  environment:
21
21
  - MYSQL_ROOT_PASSWORD=root
@@ -3,7 +3,7 @@ version: '3.8'
3
3
  services:
4
4
  killbill:
5
5
  network_mode: host
6
- image: killbill/killbill:0.22.20
6
+ image: killbill/killbill:0.24.0
7
7
  environment:
8
8
  - KILLBILL_CATALOG_URI=SpyCarAdvanced.xml
9
9
  - KILLBILL_DAO_URL=jdbc:postgresql://127.0.0.1:5432/killbill
@@ -16,6 +16,6 @@ services:
16
16
  - db
17
17
  db:
18
18
  network_mode: host
19
- image: killbill/postgresql:0.22
19
+ image: killbill/postgresql:0.24
20
20
  environment:
21
21
  - POSTGRES_PASSWORD=postgres
@@ -36,9 +36,79 @@ module KillBillClient
36
36
  }
37
37
 
38
38
  def build_uri(relative_uri, options)
39
- # Need to encode in case of spaces (e.g. /1.0/kb/security/users/Mad Max/roles)
40
- encoded_relative_uri = URI::DEFAULT_PARSER.regexp[:UNSAFE].match?(relative_uri) ? relative_uri : URI::DEFAULT_PARSER.escape(relative_uri)
41
- if URI(encoded_relative_uri).scheme.nil?
39
+ # Split the URI into path and query parts
40
+ uri_parts = relative_uri.split('?', 2)
41
+ path_part = uri_parts[0]
42
+ query_part = uri_parts[1]
43
+
44
+ # Check if this is an absolute URI (has scheme) by looking for protocol pattern
45
+ is_absolute_uri = path_part.match?(/\A[a-z][a-z0-9+.-]*:\/\//i)
46
+
47
+ if is_absolute_uri
48
+ # This is an absolute URI, parse it carefully
49
+ begin
50
+ # Parse the URI components manually to handle spaces properly
51
+ if path_part.match(/\A([a-z][a-z0-9+.-]*):\/\/([^\/]+)(\/.*)?/i)
52
+ scheme = $1
53
+ authority = $2 # host:port
54
+ path = $3 || '/'
55
+
56
+ # Encode only the path segments, not the scheme or authority
57
+ if path && path != '/'
58
+ path_segments = path.split('/')
59
+ encoded_segments = path_segments.map do |segment|
60
+ # Skip encoding if the segment is already encoded (contains %XX patterns)
61
+ if segment.match?(/%[0-9A-Fa-f]{2}/)
62
+ segment
63
+ else
64
+ unsafe_regex = /[^a-zA-Z0-9\-_.!~*'()]/
65
+ if unsafe_regex.match?(segment)
66
+ CGI.escape(segment).gsub('+', '%20')
67
+ else
68
+ segment
69
+ end
70
+ end
71
+ end
72
+ encoded_path = encoded_segments.join('/')
73
+ else
74
+ encoded_path = path
75
+ end
76
+
77
+ encoded_relative_uri = "#{scheme}://#{authority}#{encoded_path}"
78
+ encoded_relative_uri += "?#{query_part}" if query_part
79
+ else
80
+ # Fallback: treat as relative if parsing fails
81
+ is_absolute_uri = false
82
+ end
83
+ rescue
84
+ # Fallback: treat as relative if any error occurs
85
+ is_absolute_uri = false
86
+ end
87
+ end
88
+
89
+ unless is_absolute_uri
90
+ # This is a relative URI, encode path segments individually
91
+ path_segments = path_part.split('/')
92
+ encoded_segments = path_segments.map do |segment|
93
+ # Skip encoding if the segment is already encoded (contains %XX patterns)
94
+ if segment.match?(/%[0-9A-Fa-f]{2}/)
95
+ segment
96
+ else
97
+ # Only encode segments that contain unsafe characters
98
+ unsafe_regex = /[^a-zA-Z0-9\-_.!~*'()]/
99
+ if unsafe_regex.match?(segment)
100
+ # Use CGI.escape and replace + with %20 for URL path encoding
101
+ CGI.escape(segment).gsub('+', '%20')
102
+ else
103
+ segment
104
+ end
105
+ end
106
+ end
107
+ encoded_path = encoded_segments.join('/')
108
+ encoded_relative_uri = query_part ? "#{encoded_path}?#{query_part}" : encoded_path
109
+ end
110
+
111
+ if !is_absolute_uri
42
112
  uri = (options[:base_uri] || KillBillClient::API.base_uri)
43
113
  uri = URI.parse(uri) unless uri.is_a?(URI)
44
114
  # Note: make sure to keep the full path (if any) from URI::HTTP, for non-ROOT deployments
@@ -49,7 +119,19 @@ module KillBillClient
49
119
  uri = encoded_relative_uri
50
120
  uri = URI.parse(uri) unless uri.is_a?(URI)
51
121
  end
52
- uri += encode_params(options).to_s
122
+
123
+ query_params = encode_params(options)
124
+ if query_params && !query_params.empty?
125
+ # encode_params returns "?param=value", so remove the leading "?"
126
+ params_without_question = query_params[1..-1]
127
+ if uri.query && !uri.query.empty?
128
+ # If there's already a query string, append with &
129
+ uri.query = uri.query + '&' + params_without_question
130
+ else
131
+ # If no existing query string, set it directly
132
+ uri.query = params_without_question
133
+ end
134
+ end
53
135
 
54
136
  uri
55
137
  end
@@ -59,17 +141,25 @@ module KillBillClient
59
141
  # so remove with from global hash and insert them under :params
60
142
  plugin_properties = options.delete :pluginProperty
61
143
  if plugin_properties && plugin_properties.size > 0
144
+ options[:params] ||= {}
62
145
  options[:params][:pluginProperty] = plugin_properties.map { |p| "#{CGI.escape p.key.to_s}=#{CGI.escape p.value.to_s}" }
63
146
  end
64
147
 
65
148
  control_plugin_names = options.delete(:controlPluginNames)
66
- options[:params][:controlPluginName] = control_plugin_names if control_plugin_names
149
+ if control_plugin_names
150
+ options[:params] ||= {}
151
+ options[:params][:controlPluginName] = control_plugin_names
152
+ end
67
153
 
68
154
  return nil unless (options[:params] && !options[:params].empty?)
69
155
 
70
- options[:params][:withStackTrace] = true if (options[:return_full_stacktraces] || KillBillClient.return_full_stacktraces)
156
+ if (options[:return_full_stacktraces] || KillBillClient.return_full_stacktraces)
157
+ options[:params][:withStackTrace] = true
158
+ end
71
159
 
72
160
  pairs = options[:params].map { |key, value|
161
+ next if value.nil?
162
+
73
163
  # If the value is an array, we 'demultiplex' into several
74
164
  if value.is_a? Array
75
165
  internal_pairs = value.map do |simple_value|
@@ -79,8 +169,9 @@ module KillBillClient
79
169
  else
80
170
  "#{CGI.escape key.to_s}=#{CGI.escape value.to_s}"
81
171
  end
82
- }
172
+ }.compact
83
173
  pairs.flatten!
174
+ return nil if pairs.empty?
84
175
  "?#{pairs.join '&'}"
85
176
  end
86
177
 
@@ -212,7 +212,7 @@ module KillBillClient
212
212
  def get_invoice_template(is_manual_pay, locale = nil, options = {})
213
213
 
214
214
  require_multi_tenant_options!(options, "Retrieving an invoice template supported in multi-tenant mode")
215
-
215
+ locale ||= 'en'
216
216
 
217
217
  get "#{KILLBILL_API_INVOICES_PREFIX}/#{is_manual_pay ? "manualPayTemplate/#{locale}" : "template"}",
218
218
  {},
@@ -1,3 +1,3 @@
1
1
  module KillBillClient
2
- VERSION = '4.0.6'
2
+ VERSION = '4.0.7'
3
3
  end
@@ -106,6 +106,256 @@ describe KillBillClient::API do
106
106
  uri = http_adapter.send(:build_uri, KillBillClient::Model::Account::KILLBILL_API_ACCOUNTS_PREFIX, options)
107
107
  expect(uri).to eq(URI.parse("#{KillBillClient::API.base_uri.to_s}/1.0/kb/accounts"))
108
108
  end
109
+
110
+ describe '#build_uri' do
111
+ let(:http_adapter) { DummyForHTTPAdapter.new }
112
+
113
+ before do
114
+ KillBillClient.url = 'http://example.com:8080'
115
+ end
116
+
117
+ context 'with basic relative URI' do
118
+ it 'should combine base URI with relative URI' do
119
+ relative_uri = '/1.0/kb/accounts'
120
+ options = {}
121
+ uri = http_adapter.send(:build_uri, relative_uri, options)
122
+ expect(uri.to_s).to eq('http://example.com:8080/1.0/kb/accounts')
123
+ end
124
+
125
+ it 'should handle relative URI without leading slash' do
126
+ relative_uri = '1.0/kb/accounts'
127
+ options = {}
128
+ uri = http_adapter.send(:build_uri, relative_uri, options)
129
+ expect(uri.to_s).to eq('http://example.com:8080/1.0/kb/accounts')
130
+ end
131
+ end
132
+
133
+ context 'with custom base_uri in options' do
134
+ it 'should use custom base_uri from options' do
135
+ relative_uri = '/1.0/kb/accounts'
136
+ options = { base_uri: 'https://custom.example.com:9090' }
137
+ uri = http_adapter.send(:build_uri, relative_uri, options)
138
+ expect(uri.to_s).to eq('https://custom.example.com:9090/1.0/kb/accounts')
139
+ end
140
+ end
141
+
142
+ context 'with absolute URI' do
143
+ it 'should use absolute URI as-is' do
144
+ relative_uri = 'https://absolute.example.com/api/test'
145
+ options = {}
146
+ uri = http_adapter.send(:build_uri, relative_uri, options)
147
+ expect(uri.to_s).to eq('https://absolute.example.com/api/test')
148
+ end
149
+ end
150
+
151
+ context 'with path encoding' do
152
+ it 'should handle already encoded URIs without double encoding' do
153
+ relative_uri = '/1.0/kb/accounts/my%20account%20with%20spaces'
154
+ options = {}
155
+ uri = http_adapter.send(:build_uri, relative_uri, options)
156
+ expect(uri.to_s).to eq('http://example.com:8080/1.0/kb/accounts/my%20account%20with%20spaces')
157
+ end
158
+
159
+ it 'should not encode safe characters in path' do
160
+ relative_uri = '/1.0/kb/accounts/abc-1234'
161
+ options = {}
162
+ uri = http_adapter.send(:build_uri, relative_uri, options)
163
+ expect(uri.to_s).to eq('http://example.com:8080/1.0/kb/accounts/abc-1234')
164
+ end
165
+
166
+ it 'should handle already encoded URI with query string without double encoding' do
167
+ relative_uri = '/1.0/kb/accounts/my%20account?search=test%20value'
168
+ options = {}
169
+ uri = http_adapter.send(:build_uri, relative_uri, options)
170
+ expect(uri.to_s).to eq('http://example.com:8080/1.0/kb/accounts/my%20account?search=test%20value')
171
+ end
172
+ end
173
+
174
+ context 'with query parameters in options' do
175
+ it 'should add simple query parameters' do
176
+ relative_uri = '/1.0/kb/accounts'
177
+ options = { params: { accountId: '123', limit: 10 } }
178
+ uri = http_adapter.send(:build_uri, relative_uri, options)
179
+ expect(uri.query).to include('accountId=123')
180
+ expect(uri.query).to include('limit=10')
181
+ end
182
+
183
+ it 'should handle array parameters' do
184
+ relative_uri = '/1.0/kb/accounts'
185
+ options = { params: { tags: ['premium', 'business'] } }
186
+ uri = http_adapter.send(:build_uri, relative_uri, options)
187
+ expect(uri.query).to include('tags=premium')
188
+ expect(uri.query).to include('tags=business')
189
+ end
190
+
191
+ it 'should merge with existing query string in relative URI' do
192
+ relative_uri = '/1.0/kb/accounts?existing=value'
193
+ options = { params: { new: 'param' } }
194
+ uri = http_adapter.send(:build_uri, relative_uri, options)
195
+ expect(uri.query).to include('existing=value')
196
+ expect(uri.query).to include('new=param')
197
+ end
198
+
199
+ it 'should handle empty params hash' do
200
+ relative_uri = '/1.0/kb/accounts'
201
+ options = { params: {} }
202
+ uri = http_adapter.send(:build_uri, relative_uri, options)
203
+ expect(uri.query).to be_nil
204
+ end
205
+
206
+ it 'should encode special characters in query parameters' do
207
+ relative_uri = '/1.0/kb/accounts'
208
+ options = { params: { search: 'test & special chars' } }
209
+ uri = http_adapter.send(:build_uri, relative_uri, options)
210
+ expect(uri.query).to include('search=test+%26+special+chars')
211
+ end
212
+ end
213
+
214
+ context 'with plugin properties' do
215
+ it 'should handle pluginProperty option' do
216
+ contract_property = KillBillClient::Model::PluginPropertyAttributes.new
217
+ contract_property.key = :contractId
218
+ contract_property.value = 'test-123'
219
+ relative_uri = '/1.0/kb/accounts'
220
+ options = {
221
+ params: {},
222
+ pluginProperty: [contract_property]
223
+ }
224
+ uri = http_adapter.send(:build_uri, relative_uri, options)
225
+ expect(uri.query).to include('pluginProperty=contractId%3Dtest-123')
226
+ end
227
+ end
228
+
229
+ context 'with control plugin names' do
230
+ it 'should handle controlPluginNames option' do
231
+ relative_uri = '/1.0/kb/accounts'
232
+ options = {
233
+ params: {},
234
+ controlPluginNames: ['plugin1', 'plugin2']
235
+ }
236
+ uri = http_adapter.send(:build_uri, relative_uri, options)
237
+ expect(uri.query).to include('controlPluginName=plugin1')
238
+ expect(uri.query).to include('controlPluginName=plugin2')
239
+ end
240
+ end
241
+
242
+ context 'with spaces in path segments' do
243
+ it 'should encode spaces in relative URI path segments' do
244
+ relative_uri = '/1.0/kb/accounts/search/Kill Bill Client'
245
+ options = {}
246
+ uri = http_adapter.send(:build_uri, relative_uri, options)
247
+ expect(uri.to_s).to eq('http://example.com:8080/1.0/kb/accounts/search/Kill%20Bill%20Client')
248
+ end
249
+
250
+ it 'should handle absolute URI with spaces in path' do
251
+ relative_uri = 'http://127.0.0.1:8080/1.0/kb/accounts/search/Kill Bill Client'
252
+ options = {}
253
+ uri = http_adapter.send(:build_uri, relative_uri, options)
254
+ expect(uri.to_s).to eq('http://127.0.0.1:8080/1.0/kb/accounts/search/Kill%20Bill%20Client')
255
+ end
256
+
257
+ it 'should preserve scheme and host when encoding spaces in absolute URI' do
258
+ relative_uri = 'https://api.example.com:9090/search/My Test Account'
259
+ options = {}
260
+ uri = http_adapter.send(:build_uri, relative_uri, options)
261
+ expect(uri.to_s).to eq('https://api.example.com:9090/search/My%20Test%20Account')
262
+ expect(uri.scheme).to eq('https')
263
+ expect(uri.host).to eq('api.example.com')
264
+ expect(uri.port).to eq(9090)
265
+ end
266
+ end
267
+
268
+ context 'with nil values in query parameters' do
269
+ it 'should skip nil values in params' do
270
+ relative_uri = '/1.0/kb/accounts'
271
+ options = {
272
+ params: {
273
+ account: nil,
274
+ withStackTrace: true,
275
+ limit: 10
276
+ }
277
+ }
278
+ uri = http_adapter.send(:build_uri, relative_uri, options)
279
+ expect(uri.query).not_to include('account=')
280
+ expect(uri.query).to include('withStackTrace=true')
281
+ expect(uri.query).to include('limit=10')
282
+ end
283
+
284
+ it 'should handle all nil values in params' do
285
+ relative_uri = '/1.0/kb/accounts'
286
+ options = {
287
+ params: {
288
+ account: nil,
289
+ tenant: nil
290
+ }
291
+ }
292
+ uri = http_adapter.send(:build_uri, relative_uri, options)
293
+ expect(uri.query).to be_nil
294
+ end
295
+
296
+ it 'should handle mixed nil and valid values' do
297
+ relative_uri = '/1.0/kb/accounts'
298
+ options = {
299
+ params: {
300
+ account: nil,
301
+ withStackTrace: true,
302
+ offset: 0,
303
+ search: nil,
304
+ limit: 50
305
+ }
306
+ }
307
+ uri = http_adapter.send(:build_uri, relative_uri, options)
308
+ expect(uri.query).not_to include('account=')
309
+ expect(uri.query).not_to include('search=')
310
+ expect(uri.query).to include('withStackTrace=true')
311
+ expect(uri.query).to include('offset=0')
312
+ expect(uri.query).to include('limit=50')
313
+ end
314
+ end
315
+ end
316
+
317
+ describe '#encode_params' do
318
+ let(:http_adapter) { DummyForHTTPAdapter.new }
319
+
320
+ it 'should filter out nil values from params' do
321
+ options = {
322
+ params: {
323
+ account: nil,
324
+ withStackTrace: true,
325
+ limit: 10,
326
+ search: nil
327
+ }
328
+ }
329
+ query_string = http_adapter.send(:encode_params, options)
330
+ expect(query_string).to include('withStackTrace=true')
331
+ expect(query_string).to include('limit=10')
332
+ expect(query_string).not_to include('account=')
333
+ expect(query_string).not_to include('search=')
334
+ end
335
+
336
+ it 'should return nil when all params are nil' do
337
+ options = {
338
+ params: {
339
+ account: nil,
340
+ tenant: nil
341
+ }
342
+ }
343
+ query_string = http_adapter.send(:encode_params, options)
344
+ expect(query_string).to be_nil
345
+ end
346
+
347
+ it 'should return nil when params hash is empty' do
348
+ options = { params: {} }
349
+ query_string = http_adapter.send(:encode_params, options)
350
+ expect(query_string).to be_nil
351
+ end
352
+
353
+ it 'should handle options without params key' do
354
+ options = { account: nil, withStackTrace: true }
355
+ query_string = http_adapter.send(:encode_params, options)
356
+ expect(query_string).to be_nil
357
+ end
358
+ end
109
359
  end
110
360
 
111
361
  class DummyForHTTPAdapter
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: killbill-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.6
4
+ version: 4.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Killbill core team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-04-10 00:00:00.000000000 Z
11
+ date: 2025-07-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gem-release