univapay-client-sdk 1.2.0 → 1.2.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: 79defc22034350900b06cf5d1c4a3c63cdee6bed5ea4e43595685f443da6c3ca
4
- data.tar.gz: 12730cd792e21693771ca200748b298096b28c659ccb49633c7e88c2ff9f15d2
3
+ metadata.gz: 3dad9df2de9a66d2b1dd4fba909ae6bda94459bef6d270803e1411b0a98c71ab
4
+ data.tar.gz: eaf97de06378b70bb1f6a9f1a1e2748688350222a5f7c8566ca064b451492b42
5
5
  SHA512:
6
- metadata.gz: 8411eb3d7b92fde1d317379086a0ec13f4312508ec8b644120e65710fb654783ba43a75b4e2186f9ab20fd95f2e37908c88bd5cc38415a71e259388ba6006f0f
7
- data.tar.gz: 5f23c4ac26fab020c1cf29f9f1557119460f91162b71fa50e61ed8811552d7da8f411f5bdc0c1af425e69d792bde07bc2f394b65e158f635fceaf14b63bae7d2
6
+ metadata.gz: 06062d75f1be86e35e936c5a9745809ecb50e215fb2beeb5bfa5973885a577e12f113545f07399a52276d006c53a53cf124794c0dde000444b2cec20822632af
7
+ data.tar.gz: 19d93fc64e65334d9917cb2449a96c26d0e8be08d8b132cb90a309cf5aa096ecb51a49dbf1f36b8456f61033cf0165235096125e3793d778d0447461f8b6cbb2
data/README.md CHANGED
@@ -36,16 +36,16 @@ We will assume that all requests are going to originate from a backend server th
36
36
  Install the gem from the command line:
37
37
 
38
38
  ```bash
39
- gem install univapay-client-sdk -v 1.2.0
39
+ gem install univapay-client-sdk -v 1.2.2
40
40
  ```
41
41
 
42
42
  Or add the gem to your Gemfile and run `bundle`:
43
43
 
44
44
  ```ruby
45
- gem 'univapay-client-sdk', '1.2.0'
45
+ gem 'univapay-client-sdk', '1.2.2'
46
46
  ```
47
47
 
48
- For additional gem details, see the [RubyGems page for the univapay-client-sdk gem](https://rubygems.org/gems/univapay-client-sdk/versions/1.2.0).
48
+ For additional gem details, see the [RubyGems page for the univapay-client-sdk gem](https://rubygems.org/gems/univapay-client-sdk/versions/1.2.2).
49
49
 
50
50
  ## IRB Console Usage
51
51
 
@@ -10,7 +10,7 @@ module UnivapayClientSdk
10
10
  attr_accessor :config, :http_call_back
11
11
 
12
12
  def self.user_agent
13
- 'Ruby-SDK/1.2.0 (OS: {os-info}, Engine: {engine}/{engine-version})'
13
+ 'Ruby-SDK/1.2.2 (OS: {os-info}, Engine: {engine}/{engine-version})'
14
14
  end
15
15
 
16
16
  def self.user_agent_parameters
@@ -391,73 +391,5 @@ module UnivapayClientSdk
391
391
  APIException))
392
392
  .execute
393
393
  end
394
-
395
- # Re-sends the webhook payload for a previously delivered (or failed) event.
396
- # Returns 202 Accepted immediately; delivery is asynchronous.
397
- # @param [UUID | String] store_id Required parameter: The unique identifier
398
- # of the store.
399
- # @param [UUID | String] id Required parameter: The unique identifier of the
400
- # resource.
401
- # @param [UUID | String] event_id Required parameter: The unique identifier
402
- # of the webhook event.
403
- # @param [String] idempotency_key Optional parameter: An optional
404
- # idempotency key to prevent double charges and duplicate operations. We
405
- # recommend a randomly generated UUID (v4).
406
- # @return [ApiResponse] Complete http response with raw body and status code.
407
- def redeliver_webhook_event(store_id,
408
- id,
409
- event_id,
410
- idempotency_key: nil)
411
- @api_call
412
- .request(new_request_builder(HttpMethodEnum::POST,
413
- '/stores/{storeId}/webhooks/{id}/events/{eventId}/redeliver',
414
- Server::DEFAULT)
415
- .template_param(new_parameter(store_id, key: 'storeId')
416
- .is_required(true)
417
- .should_encode(true))
418
- .template_param(new_parameter(id, key: 'id')
419
- .is_required(true)
420
- .should_encode(true))
421
- .template_param(new_parameter(event_id, key: 'eventId')
422
- .is_required(true)
423
- .should_encode(true))
424
- .header_param(new_parameter(idempotency_key, key: 'Idempotency-Key'))
425
- .header_param(new_parameter('application/json', key: 'accept'))
426
- .auth(Single.new('JWT_TOKEN')))
427
- .response(new_response_handler
428
- .deserializer(APIHelper.method(:json_deserialize))
429
- .is_api_response(true)
430
- .local_error_template('401',
431
- 'HTTP 401 Unauthorized: {$response.body#/code}',
432
- ApiErrorException)
433
- .local_error_template('403',
434
- 'HTTP 403 Forbidden: {$response.body#/code}',
435
- ApiErrorException)
436
- .local_error_template('404',
437
- 'HTTP 404 Not Found: {$response.body#/code}',
438
- ApiErrorException)
439
- .local_error_template('400',
440
- 'HTTP 400 Bad Request: {$response.body#/code}',
441
- APIException)
442
- .local_error_template('409',
443
- 'HTTP 409 Conflict: {$response.body#/code}',
444
- APIException)
445
- .local_error_template('429',
446
- 'HTTP 429 Rate Limited: {$response.body#/code}',
447
- APIException)
448
- .local_error_template('500',
449
- 'HTTP 500 Server Error: {$response.body#/code}',
450
- APIException)
451
- .local_error_template('503',
452
- 'HTTP 503 Unavailable: {$response.body#/code}',
453
- APIException)
454
- .local_error_template('504',
455
- 'HTTP 504 Timeout: {$response.body#/code}',
456
- APIException)
457
- .local_error_template('default',
458
- 'HTTP {$statusCode}: {$response.body#/code}',
459
- APIException))
460
- .execute
461
- end
462
394
  end
463
395
  end
@@ -230,6 +230,30 @@ module UnivapayClientSdk
230
230
  value = payload[claim]
231
231
  value.is_a?(String) && UUID_PATTERN.match?(value) ? value : nil
232
232
  end
233
+
234
+ # Asserts that a store id was resolvable from the configured app token.
235
+ #
236
+ # Named and shaped to match `AppJwt.requireStoreId` in the Java and PHP
237
+ # SDKs, `RequireStoreId` in the C# one and `requireStoreId` in the
238
+ # TypeScript and Python ones, so the guard reads the same in every language.
239
+ # Returning the id (rather than fetching on the caller's behalf) keeps the
240
+ # check ahead of every side effect.
241
+ #
242
+ # @param store_id [String, nil] The store id read from the token, or nil.
243
+ # @return [String] store_id, when it is present.
244
+ # @raise [RuntimeError] When it is not -- before any request is built, so a
245
+ # missing id can never reach the request path.
246
+ def self.require_store_id(store_id)
247
+ return store_id unless store_id.nil?
248
+
249
+ # Says nothing about the token itself: the credential and its claims must
250
+ # never reach an error message or a log. A merchant-level token reaching
251
+ # here is not a broken token -- it is simply not scoped to a store.
252
+ raise 'get_charge(charge_id) requires a store-level App Token: the ' \
253
+ 'configured token carries no usable "store_id" claim. Use a ' \
254
+ 'store-level App Token, or call get_charge(store_id, charge_id) ' \
255
+ 'on charges with an explicit store id.'
256
+ end
233
257
  end
234
258
 
235
259
  # Reopened onto the generated client class to expose the app token's context.
@@ -259,6 +283,30 @@ module UnivapayClientSdk
259
283
  AppJwt.read_uuid_claim(jwt_token_or_nil, 'store_id')
260
284
  end
261
285
 
286
+ # Retrieves a charge without being given a store id.
287
+ #
288
+ # `/stores/{storeId}/charges/{id}` needs a store, which callers would
289
+ # otherwise have to persist alongside every charge id -- but a store-level
290
+ # app token already carries one, so this reads it from the configured token
291
+ # and then behaves exactly like `charges.get_charge`.
292
+ #
293
+ # Raises before any request is built when the configured token carries no
294
+ # `store_id` claim -- a merchant-level token, or none at all. With a
295
+ # merchant-level token, resolve the store yourself (see `stores`) and call
296
+ # `charges.get_charge` with it.
297
+ #
298
+ # @param charge_id [String] The unique identifier of the charge.
299
+ # @param polling [Boolean, nil] If true, instructs the API to internally
300
+ # poll the charge status until it leaves 'pending'.
301
+ # @return The controller's response, untouched.
302
+ # @raise [RuntimeError] When the token carries no usable `store_id` claim.
303
+ def get_charge(charge_id, polling: nil)
304
+ # Guard first, controller second: nothing is built and no request is made
305
+ # until a store id is known to exist.
306
+ store_id = AppJwt.require_store_id(get_current_store_id)
307
+ charges.get_charge(store_id, charge_id, polling: polling)
308
+ end
309
+
262
310
  private
263
311
 
264
312
  # The configured JWT, or nil when no credentials are set.
@@ -39,7 +39,7 @@ module UnivapayClientSdk
39
39
  # @return [TrueClass | FalseClass]
40
40
  attr_accessor :verified
41
41
 
42
- # Merchant configuration snapshot serialized by gyron-payments-api.
42
+ # Merchant configuration snapshot as serialized by the backend.
43
43
  # @return [MerchantWebhookConfiguration]
44
44
  attr_accessor :configuration
45
45
 
@@ -4,7 +4,7 @@
4
4
  # by APIMATIC v3.0 ( https://www.apimatic.io ).
5
5
 
6
6
  module UnivapayClientSdk
7
- # Merchant configuration object serialized by gyron-payments-api.
7
+ # Merchant configuration object as serialized by the backend.
8
8
  class MerchantWebhookConfiguration < BaseModel
9
9
  SKIP = Object.new
10
10
  private_constant :SKIP
@@ -24,8 +24,8 @@ module UnivapayClientSdk
24
24
  # @return [DateTime]
25
25
  attr_accessor :created_on
26
26
 
27
- # Store-scoped configuration snapshot serialized by gyron-payments-api. It
28
- # uses the same flattened serializer as merchant configuration, but omits
27
+ # Store-scoped configuration snapshot as serialized by the backend. It uses
28
+ # the same flattened serializer as merchant configuration, but omits
29
29
  # `transfer_schedule`.
30
30
  # @return [MerchantWebhookConfiguration]
31
31
  attr_accessor :configuration
@@ -212,34 +212,4 @@ class WebhooksApiTest < ApiTestBase
212
212
  assert(ComparisonHelper.match_body(expected_body, received_body))
213
213
  end
214
214
 
215
- # Re-sends the webhook payload for a previously delivered (or failed) event. Returns 202 Accepted immediately; delivery is asynchronous.
216
- def test_redeliver_webhook_event
217
- # Parameters for the API call
218
- store_id = '0cab399b-5621-425b-993b-f8507eba1e78'
219
- id = 'c4e87129-cad4-47fb-8ded-b4c0a4ae0dd4'
220
- event_id = 'e1f2a3b4-c5d6-7890-efab-123456789cde'
221
- idempotency_key = 'f64be872-353d-4c3c-84cb-3dc617fe89f7'
222
-
223
- # Perform the API call through the SDK function
224
- result = @controller.redeliver_webhook_event(
225
- store_id, id, event_id, idempotency_key: idempotency_key
226
- )
227
-
228
- # Test response code
229
- assert_equal(202, @response_catcher.response.status_code)
230
- # Test headers
231
- expected_headers = {}
232
- expected_headers['content-type'] = 'application/json; charset=utf-8'
233
-
234
- assert(ComparisonHelper.match_headers(expected_headers, @response_catcher.response.headers))
235
-
236
- # Test whether the captured response is as we expected
237
- refute_nil(result)
238
- expected_body = JSON.parse(
239
- '{}'
240
- )
241
- received_body = JSON.parse(@response_catcher.response.raw_body)
242
- assert(ComparisonHelper.match_body(expected_body, received_body))
243
- end
244
-
245
215
  end
@@ -0,0 +1,154 @@
1
+ # Custom test (not auto-generated): pins get_charge(charge_id) -- the
2
+ # store-scoped convenience call that reads the store id from the configured App
3
+ # Token instead of making the caller pass (and persist) one.
4
+ #
5
+ # Two things are guarded here.
6
+ #
7
+ # First the *guard*: when the configured token carries no usable store_id, the
8
+ # call must fail before a request is built. Interpolating a missing id would
9
+ # send GET /stores//charges/{id} -- a confusing 4xx instead of a clear
10
+ # client-side failure -- so the failure cases assert not just the raise but that
11
+ # the controller was never reached.
12
+ #
13
+ # Second the *delegation*: on the happy path this must behave exactly like
14
+ # charges.get_charge(store_id, charge_id, polling:), forwarding every argument
15
+ # and returning the response untouched. Anything else and the shortcut would be
16
+ # a second, subtly different way to fetch a charge.
17
+ #
18
+ # Everything here is synthetic and offline -- no network, no environment, no
19
+ # real credential. Note the SDK test harness injects JWT_TOKEN=test-jwt, which
20
+ # is not a JWT at all, so a test resting on an env-built client would silently
21
+ # exercise only the failure path. Hence the locally built clients.
22
+ #
23
+ # Seven SDKs are expected to share this contract; keep the case table aligned
24
+ # when porting.
25
+
26
+ require 'base64'
27
+ require 'json'
28
+ require 'minitest/autorun'
29
+ require 'univapay_client_sdk'
30
+
31
+ class ClientGetChargeTest < Minitest::Test
32
+ include UnivapayClientSdk
33
+
34
+ MERCHANT_ID = '11ec8e24-0ecf-2c5a-923c-331b915dc311'.freeze
35
+ STORE_ID = '11ec8e24-b133-6c68-b54d-971717202e9b'.freeze
36
+ CHARGE_ID = '11ec8e24-c5f5-6f2e-b9b0-1f4d3c6a9e10'.freeze
37
+ RESPONSE = Object.new
38
+
39
+ # Builds a JWT carrying claims. Header and signature are inert -- nothing
40
+ # verifies them.
41
+ def jwt(claims)
42
+ segment = lambda do |payload|
43
+ Base64.urlsafe_encode64(JSON.generate(payload), padding: false)
44
+ end
45
+ "#{segment.call({ 'alg' => 'HS256', 'typ' => 'JWT' })}.#{segment.call(claims)}.c2ln"
46
+ end
47
+
48
+ def store_token
49
+ jwt('merchant_id' => MERCHANT_ID, 'store_id' => STORE_ID)
50
+ end
51
+
52
+ def merchant_token
53
+ jwt('merchant_id' => MERCHANT_ID)
54
+ end
55
+
56
+ def client_with(jwt_token)
57
+ credentials =
58
+ if jwt_token.nil?
59
+ nil
60
+ else
61
+ BearerAuthCredentials.new(secret_key: 'not-a-real-secret', jwt_token: jwt_token)
62
+ end
63
+ Client.new(bearer_auth_credentials: credentials)
64
+ end
65
+
66
+ # Replaces the controller call with a recorder, so no HTTP is issued.
67
+ def watching(client)
68
+ calls = []
69
+ controller = client.charges
70
+ controller.define_singleton_method(:get_charge) do |store_id, id, polling: nil|
71
+ calls << { store_id: store_id, id: id, polling: polling, receiver: self }
72
+ RESPONSE
73
+ end
74
+ calls
75
+ end
76
+
77
+ # ── The guard: no usable store_id ─────────────────────────────────────────
78
+
79
+ def assert_guard_trips(jwt_token)
80
+ client = client_with(jwt_token)
81
+ calls = watching(client)
82
+
83
+ assert_nil client.get_current_store_id, 'precondition: no store id in this token'
84
+
85
+ error = assert_raises(RuntimeError) { client.get_charge(CHARGE_ID) }
86
+ assert_includes error.message, 'store-level App Token'
87
+ assert_empty calls, 'no request may be built when the store id is missing'
88
+ end
89
+
90
+ def test_merchant_level_token_raises
91
+ assert_guard_trips(merchant_token)
92
+ end
93
+
94
+ def test_no_credentials_raises
95
+ assert_guard_trips(nil)
96
+ end
97
+
98
+ def test_malformed_token_raises
99
+ assert_guard_trips('not.a-jwt')
100
+ end
101
+
102
+ def test_non_uuid_store_id_raises
103
+ assert_guard_trips(jwt('merchant_id' => MERCHANT_ID, 'store_id' => 'store-1'))
104
+ end
105
+
106
+ def test_never_puts_the_credential_in_the_message
107
+ error = assert_raises(RuntimeError) { client_with(merchant_token).get_charge(CHARGE_ID) }
108
+
109
+ assert_includes error.message, 'get_charge(store_id, charge_id)'
110
+ refute_includes error.message, merchant_token
111
+ refute_includes error.message, MERCHANT_ID
112
+ refute_includes error.message, STORE_ID
113
+ end
114
+
115
+ # ── The delegation: store id taken from the token ─────────────────────────
116
+
117
+ def test_delegates_with_the_store_id_from_the_token
118
+ client = client_with(store_token)
119
+ calls = watching(client)
120
+
121
+ client.get_charge(CHARGE_ID)
122
+
123
+ assert_equal 1, calls.length
124
+ assert_equal STORE_ID, calls[0][:store_id]
125
+ assert_equal CHARGE_ID, calls[0][:id]
126
+ assert_nil calls[0][:polling]
127
+ end
128
+
129
+ def test_forwards_the_polling_flag
130
+ client = client_with(store_token)
131
+ calls = watching(client)
132
+
133
+ client.get_charge(CHARGE_ID, polling: true)
134
+
135
+ assert_equal true, calls[0][:polling]
136
+ end
137
+
138
+ def test_returns_the_controller_response_untouched
139
+ client = client_with(store_token)
140
+ watching(client)
141
+
142
+ assert_same RESPONSE, client.get_charge(CHARGE_ID)
143
+ end
144
+
145
+ def test_reuses_one_controller_across_calls
146
+ client = client_with(store_token)
147
+ calls = watching(client)
148
+
149
+ client.get_charge(CHARGE_ID)
150
+ client.get_charge(CHARGE_ID)
151
+
152
+ assert_same calls[0][:receiver], calls[1][:receiver]
153
+ end
154
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: univapay-client-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Univapay Developers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-19 00:00:00.000000000 Z
11
+ date: 2026-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: apimatic_core_interfaces
@@ -433,6 +433,7 @@ files:
433
433
  - test/framework_integrations/webhooks/test/test_sinatra_charge_app.rb
434
434
  - test/http_response_catcher.rb
435
435
  - test/test_app_jwt.rb
436
+ - test/test_client_get_charge.rb
436
437
  homepage: https://univapay.com
437
438
  licenses:
438
439
  - MIT