wajub 1.1.1 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 065a6a595ab1ec1a3b899d9bf6412b39ef6e11ba8e08474cc24b2c13543f07a4
4
- data.tar.gz: a9264472d89dc373cbc34d5b7dbf2e885e4f9fb5ad72508eac2e9f4db79f6888
3
+ metadata.gz: 3a5524dcd4b29eab3e66cc9c5fb1890324ee8fa4b7b090f1117d4feb4492dee7
4
+ data.tar.gz: a863c218296f0ed42c0b89198b612519ad3ec85589829b189aaad40564f3ef98
5
5
  SHA512:
6
- metadata.gz: eb98449ce3edf764a6715886e32065db1723baafc9c2ce7e719eccd6c405830ad9662dc3054a8c7ec92b7c77a750915c8fdd51bdec176791b542c5622079e6cf
7
- data.tar.gz: c49b5d28a9331dc063e8a6bcac9d04bb2ba0a02a71d0e88af29031b41840745e8a4575af0eecf89543c7e8c9d4001e61ca5201f7836ad17049523d414fceb327
6
+ metadata.gz: 92e45ed29b1f73f55eb0d43bd4bf2933ae744a622605f028fc017efc7af842f2462bd9e0d3aafa8c84bc54f6ce03da1d8dc1c15c5c205e6052effe1a9ed9603b
7
+ data.tar.gz: 4decf56f623ce500d86109d640608620c654c862f3bfdf948f2587e7c6aa7bb4dcb1ddf2dddbf80a9fbc49300a4ab0a383fb8866d800894dc76edbc60ccd1d60
data/CHANGELOG.md ADDED
@@ -0,0 +1,51 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `wajub` gem are documented in this file.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+ `wajub` uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ---
9
+
10
+ ## [1.3.0] — 2026-10-12
11
+
12
+ 1.2.1 was never published to RubyGems: its changes ship in this release.
13
+
14
+ ### Added
15
+ - The tax and Shield methods the other server SDKs already had, with the
16
+ same paths and the same results as the Python SDK:
17
+ - `tax.reports`, `tax.list_codes`, `tax.retrieve_code`, `tax.jurisdictions`,
18
+ `tax.thresholds` and `tax.threshold_alerts`;
19
+ - `tax.list_registrations` (a page), `tax.create_registration`,
20
+ `tax.retrieve_registration`, `tax.update_registration` and
21
+ `tax.delete_registration`;
22
+ - `shield.list_blocklist`, `shield.add_to_blocklist` and
23
+ `shield.remove_from_blocklist`.
24
+
25
+ The README listed them, and calling one raised `NoMethodError`.
26
+
27
+ ### Fixed
28
+ - `customers.delete_tax_id` called `DELETE /customers/%2Fcustomers%2F…` and
29
+ answered `404`: the nested path went through the resource's own `delete`,
30
+ which takes an id and encodes it as one path segment. It now calls
31
+ `DELETE /customers/{id}/tax_ids/{tax_id}`.
32
+ - `webhooks.construct_event` with a missing `X-Wajub-Signature` or
33
+ `X-Wajub-Timestamp` header (`nil`) raised `NoMethodError`, which a handler
34
+ rescuing `WebhookSignatureVerificationError` let through as a 500. It now
35
+ raises `WebhookSignatureVerificationError`, as does a timestamp that is not a
36
+ number.
37
+
38
+ ### Changed
39
+ - **The `409` for an `Idempotency-Key` still being processed is retried.** A
40
+ call sent again after a timeout can reach the API while the first one is still
41
+ running; the API answers `409` with `errors.idempotency_key`. The client now
42
+ waits 2 then 4 seconds and asks again with the same key, within its usual 2
43
+ retries, and returns the first request's result. Every other `409` is still
44
+ raised at once.
45
+
46
+ ## [1.2.0] — 2026-10-01
47
+
48
+ Tagged, never published to RubyGems. Its one change ships with 1.3.0.
49
+
50
+ ### Removed
51
+ - `payments.initialize_payment`, an alias of `payments.create`. Call `create`.
data/README.md CHANGED
@@ -30,7 +30,7 @@ Use **[Wajub.js](https://docs.wajub.com/libraries/components/js)** for embedded
30
30
  Add to your Gemfile:
31
31
 
32
32
  ```ruby
33
- gem 'wajub', '~> 1.1'
33
+ gem 'wajub', '~> 1.3'
34
34
  ```
35
35
 
36
36
  Or install directly:
@@ -99,7 +99,7 @@ class WebhooksController < ApplicationController
99
99
  request.headers['X-Wajub-Timestamp']
100
100
  )
101
101
 
102
- case event['type']
102
+ case event['event']
103
103
  when 'payment.succeeded'
104
104
  # fulfill order
105
105
  end
@@ -125,7 +125,7 @@ Test mode is selected by your API key prefix (`sk_test.…`), not by the API URL
125
125
  | Property | Methods |
126
126
  |----------|---------|
127
127
  | `client.global` | `ping`, `channels`, `countries`, `currencies` |
128
- | `client.payments` | `create`, `initialize_payment`, `retrieve`, `list`, `cancel`, `process`, `process_split`, `list_refunds` |
128
+ | `client.payments` | `create`, `retrieve`, `list`, `cancel`, `process`, `process_split`, `list_refunds` |
129
129
  | `client.customers` | `create`, `retrieve`, `update`, `delete`, `list`, `block`, `unblock`, `activate`, `deactivate`, `list_tax_ids`, `create_tax_id`, `delete_tax_id` |
130
130
  | `client.refunds` | `create`, `retrieve`, `list` |
131
131
  | `client.transfers` | `create`, `retrieve`, `list` |
@@ -162,7 +162,7 @@ rescue Wajub::WebhookSignatureVerificationError
162
162
  halt 400
163
163
  end
164
164
 
165
- case event['type']
165
+ case event['event']
166
166
  when 'payment.succeeded'
167
167
  # fulfill order
168
168
  end
@@ -89,6 +89,14 @@ module Wajub
89
89
  next
90
90
  end
91
91
 
92
+ # The same key still being processed: the first request's answer is
93
+ # coming. Asked again with the same key, the API returns it.
94
+ if idempotency_key && RetryPolicy.idempotency_key_in_progress?(status, data) && attempt < RetryPolicy.max_retries
95
+ attempt += 1
96
+ sleep RetryPolicy.idempotency_conflict_delay_seconds(attempt)
97
+ next
98
+ end
99
+
92
100
  retry_after = response.dig(:headers, 'retry-after')&.to_i
93
101
  raise Errors.from_response(status, data, retry_after: retry_after)
94
102
  end
@@ -15,6 +15,20 @@ module Wajub
15
15
  MAX_RETRIES
16
16
  end
17
17
 
18
+ # The 409 the API answers while a request with the same Idempotency-Key is
19
+ # still running (errors.idempotency_key). A call sent again after a timeout
20
+ # can arrive before the first has finished: its answer is on the way, so the
21
+ # call is worth repeating with the same key. Every other 409 is a refusal.
22
+ def idempotency_key_in_progress?(status, body)
23
+ status == 409 && body.is_a?(Hash) && body['errors'].is_a?(Hash) && body['errors'].key?('idempotency_key')
24
+ end
25
+
26
+ # The pause before asking again about a key still being processed:
27
+ # 2, 4, then 8 seconds.
28
+ def idempotency_conflict_delay_seconds(attempt)
29
+ 2**[attempt, 1].max
30
+ end
31
+
18
32
  def delay_seconds(attempt, response: nil)
19
33
  header = response&.[]('retry-after')&.strip
20
34
  if header && !header.empty?
@@ -41,10 +41,6 @@ module Wajub
41
41
  to_payment_object(post('/payments', params, options))
42
42
  end
43
43
 
44
- def initialize_payment(params, options = nil)
45
- create(params, options)
46
- end
47
-
48
44
  def retrieve(id)
49
45
  res = get("/payments/#{URI.encode_www_form_component(id)}")
50
46
  to_payment_object('transaction' => HttpUtils.pick_resource(res, 'transaction'))
@@ -159,7 +155,9 @@ module Wajub
159
155
  end
160
156
 
161
157
  def delete_tax_id(customer_id, tax_id, options = nil)
162
- delete("/customers/#{URI.encode_www_form_component(customer_id)}/tax_ids/#{URI.encode_www_form_component(tax_id)}", options)
158
+ # Not `delete`: on a CrudResource that one takes an id and encodes it as a path segment.
159
+ path = "/customers/#{URI.encode_www_form_component(customer_id)}/tax_ids/#{URI.encode_www_form_component(tax_id)}"
160
+ request('DELETE', path, options: options)
163
161
  end
164
162
  end
165
163
 
@@ -254,6 +252,55 @@ module Wajub
254
252
  def calculate(params, options = nil)
255
253
  HttpUtils.pick_resource(post('/tax/calculate', params, options), 'tax', 'calculation')
256
254
  end
255
+
256
+ def reports(params = nil)
257
+ list = HttpUtils.pick_list(get('/tax/reports', params), 'reports', 'tax_reports')
258
+ [list[:data], list[:meta]]
259
+ end
260
+
261
+ def list_codes(params = nil)
262
+ list = HttpUtils.pick_list(get('/tax/codes', params), 'tax_codes', 'codes')
263
+ [list[:data], list[:meta]]
264
+ end
265
+
266
+ def retrieve_code(code)
267
+ HttpUtils.pick_resource(get("/tax/codes/#{URI.encode_www_form_component(code)}"), 'tax_code', 'code')
268
+ end
269
+
270
+ def list_registrations(params = nil)
271
+ PagedResult.create(self, '/tax/registrations', 'registrations', params)
272
+ end
273
+
274
+ def create_registration(params, options = nil)
275
+ HttpUtils.pick_resource(post('/tax/registrations', params, options), 'registration', 'tax_registration')
276
+ end
277
+
278
+ def retrieve_registration(id)
279
+ HttpUtils.pick_resource(get("/tax/registrations/#{URI.encode_www_form_component(id)}"), 'registration', 'tax_registration')
280
+ end
281
+
282
+ def update_registration(id, params, options = nil)
283
+ HttpUtils.pick_resource(put("/tax/registrations/#{URI.encode_www_form_component(id)}", params, options), 'registration', 'tax_registration')
284
+ end
285
+
286
+ def delete_registration(id, options = nil)
287
+ request('DELETE', "/tax/registrations/#{URI.encode_www_form_component(id)}", options: options)
288
+ end
289
+
290
+ def jurisdictions(params = nil)
291
+ list = HttpUtils.pick_list(get('/tax/jurisdictions', params), 'jurisdictions', 'tax_jurisdictions')
292
+ [list[:data], list[:meta]]
293
+ end
294
+
295
+ def thresholds(params = nil)
296
+ list = HttpUtils.pick_list(get('/tax/thresholds', params), 'thresholds', 'tax_thresholds')
297
+ [list[:data], list[:meta]]
298
+ end
299
+
300
+ def threshold_alerts(params = nil)
301
+ list = HttpUtils.pick_list(get('/tax/thresholds/alerts', params), 'alerts', 'threshold_alerts')
302
+ [list[:data], list[:meta]]
303
+ end
257
304
  end
258
305
 
259
306
  class ShieldResource < BaseClient
@@ -268,6 +315,19 @@ module Wajub
268
315
  def stats(params = nil)
269
316
  HttpUtils.pick_resource(get('/shield/stats', params), 'shield', 'stats')
270
317
  end
318
+
319
+ def list_blocklist(params = nil)
320
+ list = HttpUtils.pick_list(get('/shield/blocklist', params), 'blocklist', 'entries')
321
+ [list[:data], list[:meta]]
322
+ end
323
+
324
+ def add_to_blocklist(params, options = nil)
325
+ HttpUtils.pick_resource(post('/shield/blocklist', params, options), 'blocklist', 'entry')
326
+ end
327
+
328
+ def remove_from_blocklist(id, options = nil)
329
+ request('DELETE', "/shield/blocklist/#{URI.encode_www_form_component(id)}", options: options)
330
+ end
271
331
  end
272
332
 
273
333
  class ListenResource < BaseClient
data/lib/wajub/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wajub
4
- VERSION = '1.1.1'
4
+ VERSION = '1.3.0'
5
5
  API_URL = 'https://api.wajub.com'
6
6
  end
@@ -15,8 +15,13 @@ module Wajub
15
15
  @secret = secret.to_s
16
16
  end
17
17
 
18
+ # A missing signature or timestamp header (nil, as request.headers returns
19
+ # it) raises WebhookSignatureVerificationError, like a wrong one.
18
20
  def construct_event(payload, signature, timestamp, tolerance = DEFAULT_TOLERANCE)
19
21
  raise ArgumentError, 'Wajub: webhook_secret is required for construct_event' if @secret.empty?
22
+ if signature.to_s.empty? || timestamp.to_s.empty?
23
+ raise WebhookSignatureVerificationError, 'Missing X-Wajub-Signature or X-Wajub-Timestamp header.'
24
+ end
20
25
  raise WebhookSignatureVerificationError, 'Invalid webhook signature format. Expected v1={hash}.' unless signature.start_with?('v1=')
21
26
  raise ArgumentError, 'Wajub: payload must be the raw request body as a String, not a parsed object' unless payload.is_a?(String)
22
27
 
@@ -26,8 +31,8 @@ module Wajub
26
31
 
27
32
  raise WebhookSignatureVerificationError, 'Webhook signature verification failed.' unless Crypto.timing_safe_equal(expected, received)
28
33
 
29
- ts = Float(timestamp)
30
- raise WebhookSignatureVerificationError, 'Invalid webhook timestamp.' unless ts.finite?
34
+ ts = Float(timestamp, exception: false)
35
+ raise WebhookSignatureVerificationError, 'Invalid webhook timestamp.' unless ts&.finite?
31
36
 
32
37
  drift = (Time.now.to_i - ts).abs
33
38
  if tolerance.positive? && drift > tolerance
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wajub
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wajub
@@ -16,6 +16,7 @@ executables: []
16
16
  extensions: []
17
17
  extra_rdoc_files: []
18
18
  files:
19
+ - CHANGELOG.md
19
20
  - LICENSE
20
21
  - README.md
21
22
  - lib/wajub.rb
@@ -37,6 +38,7 @@ licenses:
37
38
  metadata:
38
39
  homepage_uri: https://docs.wajub.com/libraries/sdks/ruby
39
40
  source_code_uri: https://github.com/wajubhq/wajub-ruby
41
+ changelog_uri: https://github.com/wajubhq/wajub-ruby/blob/main/CHANGELOG.md
40
42
  rdoc_options: []
41
43
  require_paths:
42
44
  - lib
@@ -51,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
51
53
  - !ruby/object:Gem::Version
52
54
  version: '0'
53
55
  requirements: []
54
- rubygems_version: 4.0.17
56
+ rubygems_version: 4.0.21
55
57
  specification_version: 4
56
58
  summary: Official Wajub Ruby SDK
57
59
  test_files: []