Module: UnivapayClientSdk::AppJwt

Defined in:
lib/univapay_client_sdk/extensions.rb

Overview

── App token (JWT) claim decoding ────────────────────────────────────────

A UnivaPay app token JWT carries the context it was issued for. A store-level token has both merchant_id and store_id; a merchant-level token has only merchant_id.

Decoding only reads the payload segment -- it does NOT verify the signature, which is deliberate. The value is the caller's own credential, already trusted by virtue of being configured on the client; nothing here is an authorization decision. Never use these values to authenticate a third party's token.

Constant Summary collapse

UUID_PATTERN =

Matches the canonical 8-4-4-4-12 hexadecimal UUID form.

/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i.freeze

Class Method Summary collapse

Class Method Details

.decode_payload(jwt_token) ⇒ Hash?

Decodes the payload segment of a JWT without verifying its signature.

Parameters:

  • jwt_token (String, nil)

    The JWT to decode.

Returns:

  • (Hash, nil)

    The decoded claims, or nil unless the token is a well-formed three-segment JWT whose payload segment is base64url-encoded JSON describing an object.



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/univapay_client_sdk/extensions.rb', line 198

def self.decode_payload(jwt_token)
  return nil if jwt_token.nil? || !jwt_token.is_a?(String) || jwt_token.empty?

  segments = jwt_token.split('.', -1)
  return nil unless segments.length == 3

  begin
    # unpack1('m0') is strict base64 and needs no `base64` gem, which stopped
    # being a default gem in Ruby 3.4. It requires correct padding, so
    # translate base64url to base64 and pad first.
    base64 = segments[1].tr('-_', '+/')
    base64 += '=' * ((4 - (base64.length % 4)) % 4)
    payload = JSON.parse(base64.unpack1('m0'))
  rescue ArgumentError, JSON::ParserError
    return nil
  end
  payload.is_a?(Hash) ? payload : nil
end

.read_uuid_claim(jwt_token, claim) ⇒ String?

Reads a claim from a JWT payload and returns it only if it is a UUID.

Anything else -- claim absent, nil, not a string, or a string that is not a canonical UUID -- yields nil, so a caller never has to distinguish "not set" from "could not decode".

Parameters:

  • jwt_token (String, nil)

    The JWT to decode.

  • claim (String)

    Name of the claim to read.

Returns:

  • (String, nil)

    The claim value as a UUID string, or nil.



226
227
228
229
230
231
232
# File 'lib/univapay_client_sdk/extensions.rb', line 226

def self.read_uuid_claim(jwt_token, claim)
  payload = decode_payload(jwt_token)
  return nil if payload.nil?

  value = payload[claim]
  value.is_a?(String) && UUID_PATTERN.match?(value) ? value : nil
end

.require_store_id(store_id) ⇒ String

Asserts that a store id was resolvable from the configured app token.

Named and shaped to match AppJwt.requireStoreId in the Java and PHP SDKs, RequireStoreId in the C# one and requireStoreId in the TypeScript and Python ones, so the guard reads the same in every language. Returning the id (rather than fetching on the caller's behalf) keeps the check ahead of every side effect.

Parameters:

  • store_id (String, nil)

    The store id read from the token, or nil.

Returns:

  • (String)

    store_id, when it is present.

Raises:

  • (RuntimeError)

    When it is not -- before any request is built, so a missing id can never reach the request path.



246
247
248
249
250
251
252
253
254
255
256
# File 'lib/univapay_client_sdk/extensions.rb', line 246

def self.require_store_id(store_id)
  return store_id unless store_id.nil?

  # Says nothing about the token itself: the credential and its claims must
  # never reach an error message or a log. A merchant-level token reaching
  # here is not a broken token -- it is simply not scoped to a store.
  raise 'get_charge(charge_id) requires a store-level App Token: the ' \
        'configured token carries no usable "store_id" claim. Use a ' \
        'store-level App Token, or call get_charge(store_id, charge_id) ' \
        'on charges with an explicit store id.'
end