Key: stripe402:txns:{clientId}Type: Sorted Set (score = timestamp in milliseconds, member = transaction ID)
This sorted set enables time-ordered lookups of all transactions for a client.
Atomic Balance Deduction (Lua Script)
The deductBalance method uses a Lua script to ensure atomic check-and-deduct:
How it works:
Reads the current balance from the hash
Checks if balance >= amount
If sufficient: sets the new balance and updates the timestamp, returns the new balance
If insufficient: returns -1 (mapped to null in TypeScript)
Because Redis executes Lua scripts atomically (single-threaded), this prevents race conditions where two concurrent requests could both read the same balance and both deduct.
Method Implementations
getClient(clientId)
Calls redis.hgetall(key). Returns null if the hash doesn't exist or has no clientId field. Parses balance as an integer and createdAt/updatedAt as Date objects.
createClient(record)
Calls redis.hset(key, { ... }) with all fields serialized as strings. balance is converted via .toString(), dates via .toISOString().
deductBalance(clientId, amount)
Executes the Lua script via redis.eval(). Maps the return value: -1 becomes null, any other value is the new balance.
addBalance(clientId, amount)
Calls redis.hincrby(key, 'balance', amount) for atomic increment. Also updates the updatedAt field via redis.hset(). Returns the new balance.
recordTransaction(transaction)
Two operations:
redis.set(txnKey, JSON.stringify(transaction)) — stores the full transaction as JSON
redis.zadd(txnsKey, timestamp, transactionId) — adds to the sorted set index