-- ============================================================================ -- READ SUBJECT BALANCES -- Functions to read shared SubjectBalance payloads from Redis hashes. -- Balance keys are built by the TS caller and passed in via -- params.balance_keys_by_feature_id to keep this script portable across -- Redis/Dragonfly cluster modes (no key construction inside Lua). -- ============================================================================ local function decode_subject_balance(raw_value) local decoded = safe_decode(raw_value) if type(decoded) ~= 'table' then return nil end return decoded end local function read_subject_balances(params) local balances_by_id = {} local missing_customer_entitlement_ids = {} local entries_by_balance_key = {} local seen_ids_by_balance_key = {} local balance_keys_by_feature_id = safe_table(params.balance_keys_by_feature_id) local function queue_balance_read(customer_entitlement_id, feature_id) if not (customer_entitlement_id and feature_id) then return false end local balance_key = balance_keys_by_feature_id[feature_id] if not balance_key then return false end if entries_by_balance_key[balance_key] == nil then entries_by_balance_key[balance_key] = { feature_id = feature_id, customer_entitlement_ids = {}, } seen_ids_by_balance_key[balance_key] = {} end if seen_ids_by_balance_key[balance_key][customer_entitlement_id] then return true end seen_ids_by_balance_key[balance_key][customer_entitlement_id] = true table.insert( entries_by_balance_key[balance_key].customer_entitlement_ids, customer_entitlement_id ) return true end for _, ent_obj in ipairs(params.customer_entitlement_deductions or {}) do local customer_entitlement_id = ent_obj.customer_entitlement_id if customer_entitlement_id then local queued = queue_balance_read(customer_entitlement_id, ent_obj.feature_id) if not queued then table.insert(missing_customer_entitlement_ids, customer_entitlement_id) end end end for balance_key, entry in pairs(entries_by_balance_key) do local hmget_args = { 'HMGET', balance_key } for _, customer_entitlement_id in ipairs(entry.customer_entitlement_ids) do table.insert(hmget_args, customer_entitlement_id) end local raw_values = redis.call(unpack(hmget_args)) for index, customer_entitlement_id in ipairs(entry.customer_entitlement_ids) do local raw_value = raw_values[index] local subject_balance = decode_subject_balance(raw_value) if subject_balance == nil then table.insert( missing_customer_entitlement_ids, customer_entitlement_id ) else balances_by_id[customer_entitlement_id] = { balance_key = balance_key, customer_entitlement_id = customer_entitlement_id, feature_id = entry.feature_id, subject_balance = subject_balance, } end end end return { balances_by_id = balances_by_id, missing_customer_entitlement_ids = missing_customer_entitlement_ids, } end local function read_rollover_data(rollover) if type(rollover) ~= 'table' then return nil end return { balance = safe_number(rollover.balance), usage = safe_number(rollover.usage), entities = safe_table(rollover.entities), } end local function read_current_balance(subject_balance) if type(subject_balance) ~= 'table' then return 0 end return safe_number(subject_balance.balance) end local function read_current_entity_balance(subject_balance, entity_id) if type(subject_balance) ~= 'table' or is_nil(entity_id) then return nil end local entities = safe_table(subject_balance.entities) local entity_balance = entities[entity_id] if type(entity_balance) ~= 'table' then return nil end return { balance = safe_number(entity_balance.balance), adjustment = safe_number(entity_balance.adjustment), } end local function read_current_entities(subject_balance) if type(subject_balance) ~= 'table' then return {} end return safe_table(subject_balance.entities) end