Files
sky_phone-sky-systems/sky_phone/source/server/banking.lua
T

165 lines
5.6 KiB
Lua

Bridge.Database.AfterMigration("sky_phone", function()
local function valid_amount(value)
local amount = tonumber(value)
if not amount or amount ~= math.floor(amount) then
return nil
end
if amount < Config.Banking.MinimumAmount or amount > Config.Banking.MaximumAmount then
return nil
end
return amount
end
local function player_name(source)
local firstname = Bridge.Framework.GetFirstname(source)
local lastname = Bridge.Framework.GetLastname(source)
local name = ((firstname or "") .. " " .. (lastname or "")):match("^%s*(.-)%s*$")
if name == "" then
name = GetPlayerName(source) or ("Player %s"):format(source)
end
return name
end
local function require_banking_session(source)
local session, error_response = SkyPhone.RequireSession(source)
if not session then
return nil, error_response
end
local identifier = Bridge.Framework.GetIdentifier(source)
if type(identifier) ~= "string" or identifier == "" then
return nil, { success = false, error = "banking_unavailable" }
end
return identifier
end
local function transaction_dto(row)
return {
id = tonumber(row.id),
kind = row.kind,
amount = tonumber(row.amount) or 0,
label = row.label or "",
reference = row.reference or "",
createdAt = (tonumber(row.created_at_unix) or 0) * 1000,
}
end
local function transactions(identifier)
local rows = Bridge.Database.Query([[
SELECT `id`, `kind`, `amount`, `label`, `reference`,
UNIX_TIMESTAMP(`created_at`) AS `created_at_unix`
FROM `sky_phone_bank_transactions`
WHERE `owner_identifier` = ?
ORDER BY `id` DESC
LIMIT ?
]], { identifier, Config.Banking.HistoryLimit })
local result = {}
for _, row in ipairs(rows) do
result[#result + 1] = transaction_dto(row)
end
return result
end
local function overview(source, identifier)
return {
bank = math.max(0, tonumber(Bridge.Framework.GetMoney(source, "bank")) or 0),
cash = math.max(0, tonumber(Bridge.Framework.GetMoney(source, "cash")) or 0),
currency = Config.Banking.Currency,
playerId = source,
playerName = player_name(source),
transactions = transactions(identifier),
}
end
local function record_transaction(identifier, kind, amount, label, reference)
Bridge.Database.Query([[
INSERT INTO `sky_phone_bank_transactions`
(`owner_identifier`, `kind`, `amount`, `label`, `reference`)
VALUES (?, ?, ?, ?, ?)
]], { identifier, kind, amount, label or "", reference or "" })
end
local function notify_changed(source, data)
TriggerClientEvent("sky_phone:banking:changed", source, data)
end
local function online_source_for_phone(number)
local rows = Bridge.Database.Query([[
SELECT d.`imei`
FROM `sky_phone_sims` s
INNER JOIN `sky_phone_devices` d ON d.`sim_id` = s.`id`
WHERE s.`phone_number` = ?
LIMIT 1
]], { number })
local device = rows[1]
if not device or type(device.imei) ~= "string" then
return nil
end
for _, player_source in ipairs(Bridge.Framework.GetPlayers()) do
local target = tonumber(player_source) or player_source
if SkyPhone.FindDeviceSlots(target, device.imei)[1] then
return target
end
end
return nil
end
Bridge.Callbacks.Register("sky_phone:banking:overview", function(source)
local identifier, error_response = require_banking_session(source)
if not identifier then
return error_response
end
return { success = true, data = overview(source, identifier) }
end)
Bridge.Callbacks.Register("sky_phone:banking:transfer", function(source, data)
if not SkyPhone.AllowOperation(source, "banking_transaction", Config.Banking.ActionsPerMinute, 60) then
return { success = false, error = "rate_limited" }
end
local identifier, error_response = require_banking_session(source)
if not identifier then
return error_response
end
local amount = valid_amount(data and data.amount)
local number = SkyPhoneSimNumber.Normalize(
data and data.phoneNumber,
Config.Sim.NumberLength,
Config.Sim.NumberPrefix
)
if not amount or not number then
return { success = false, error = "invalid_request" }
end
local target = online_source_for_phone(number)
if not target then
return { success = false, error = "target_not_found" }
end
if target == source then
return { success = false, error = "self_transfer" }
end
local target_identifier = Bridge.Framework.GetIdentifier(target)
if type(target_identifier) ~= "string" or target_identifier == "" then
return { success = false, error = "target_not_found" }
end
if not Bridge.Framework.RemoveMoney(source, "bank", amount) then
return { success = false, error = "insufficient_funds" }
end
if not Bridge.Framework.AddMoney(target, "bank", amount) then
Bridge.Framework.AddMoney(source, "bank", amount)
return { success = false, error = "transfer_failed" }
end
local reference = ("phone-transfer-%s-%s"):format(os.time(), source)
local sender_name = player_name(source)
record_transaction(identifier, "transfer_out", amount, player_name(target), reference)
record_transaction(target_identifier, "transfer_in", amount, sender_name, reference)
notify_changed(target, {
kind = "transfer_in",
amount = amount,
currency = Config.Banking.Currency,
sender = sender_name,
})
return { success = true, data = overview(source, identifier) }
end)
end)