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)