Files
2026-08-05 01:29:39 +02:00

219 lines
9.7 KiB
Lua

local accountLocks = {}
local function money(value, allowZero)
local amount = tonumber(value)
if not amount then return nil end
amount = math.floor(amount)
if amount < (allowZero and 0 or 1) or amount > DevXConfig.MaxMoneyOperation then return nil end
return amount
end
local function withLocks(accountIds, callback)
local unique = {}
for _, id in ipairs(accountIds) do
id = tonumber(id)
if id and not unique[id] then unique[id] = true end
end
for id in pairs(unique) do
if accountLocks[id] then return false, 'Ein beteiligtes Konto verarbeitet gerade eine andere Buchung.' end
end
for id in pairs(unique) do accountLocks[id] = true end
local ok, resultA, resultB = pcall(callback)
for id in pairs(unique) do accountLocks[id] = nil end
if not ok then
print(('[devx_banking] Buchungsfehler: %s'):format(tostring(resultA)))
return false, 'Interner Buchungsfehler.'
end
return resultA, resultB
end
local function getOverview(characterId)
local account = MySQL.single.await([[
SELECT id, account_number AS accountNumber, account_name AS accountName,
account_type AS accountType, balance
FROM devx_bank_accounts
WHERE character_id = ? AND account_type = 'personal'
LIMIT 1
]], { characterId })
if not account then return nil end
account.id = tonumber(account.id)
account.balance = tonumber(account.balance) or 0
local transactions = MySQL.query.await([[
SELECT id, direction, amount, balance_after AS balanceAfter,
counterparty, reference, created_at AS createdAt
FROM devx_bank_transactions
WHERE account_id = ?
ORDER BY id DESC
LIMIT 25
]], { account.id }) or {}
for _, transaction in ipairs(transactions) do
transaction.id = tonumber(transaction.id)
transaction.amount = tonumber(transaction.amount) or 0
transaction.balanceAfter = tonumber(transaction.balanceAfter) or 0
end
return { account = account, transactions = transactions }
end
local function addTransaction(accountId, direction, amount, balanceAfter, counterparty, reference)
return MySQL.insert.await([[
INSERT INTO devx_bank_transactions
(account_id, direction, amount, balance_after, counterparty, reference)
VALUES (?, ?, ?, ?, ?, ?)
]], { accountId, direction, amount, balanceAfter, counterparty, reference })
end
local function chargeCharacter(characterId, amount, reference, counterparty)
amount = money(amount)
if not amount then return false, 'Ungültiger Betrag.' end
local account = MySQL.single.await([[
SELECT id, balance FROM devx_bank_accounts
WHERE character_id = ? AND account_type = 'personal' LIMIT 1
]], { characterId })
if not account then return false, 'Kein Bankkonto gefunden.' end
return withLocks({ account.id }, function()
account = MySQL.single.await('SELECT id, balance FROM devx_bank_accounts WHERE id = ? LIMIT 1', { account.id })
if not account or tonumber(account.balance) < amount then return false, 'Das Kontoguthaben reicht nicht aus.' end
local newBalance = tonumber(account.balance) - amount
local changed = MySQL.update.await('UPDATE devx_bank_accounts SET balance = ? WHERE id = ?', { newBalance, account.id })
if not changed or changed < 1 then return false, 'Belastung fehlgeschlagen.' end
addTransaction(account.id, 'debit', amount, newBalance, counterparty or 'Unbekannt', reference or 'Zahlung')
return true, newBalance
end)
end
local function creditCharacter(characterId, amount, reference, counterparty)
amount = money(amount)
if not amount then return false, 'Ungültiger Betrag.' end
local account = MySQL.single.await([[
SELECT id, balance FROM devx_bank_accounts
WHERE character_id = ? AND account_type = 'personal' LIMIT 1
]], { characterId })
if not account then return false, 'Kein Bankkonto gefunden.' end
return withLocks({ account.id }, function()
account = MySQL.single.await('SELECT id, balance FROM devx_bank_accounts WHERE id = ? LIMIT 1', { account.id })
if not account then return false, 'Kein Bankkonto gefunden.' end
local newBalance = tonumber(account.balance) + amount
local changed = MySQL.update.await('UPDATE devx_bank_accounts SET balance = ? WHERE id = ?', { newBalance, account.id })
if not changed or changed < 1 then return false, 'Gutschrift fehlgeschlagen.' end
addTransaction(account.id, 'credit', amount, newBalance, counterparty or 'Unbekannt', reference or 'Gutschrift')
return true, newBalance
end)
end
local function setPlayerBalance(source, amount, reason)
local player = exports.devx_core:GetPlayer(source)
amount = money(amount, true)
if not player or not player.accountId or amount == nil then return false, 'Ungültige Anfrage.' end
return withLocks({ player.accountId }, function()
local changed = MySQL.update.await('UPDATE devx_bank_accounts SET balance = ? WHERE id = ?', { amount, player.accountId })
if not changed or changed < 1 then return false, 'Kontostand konnte nicht geändert werden.' end
addTransaction(player.accountId, 'credit', 0, amount, 'Administration', reason or 'Kontostand gesetzt')
exports.devx_core:SetBankCache(source, amount)
return true, amount
end)
end
local function transferForSource(source, payload)
local player = exports.devx_core:GetPlayer(source)
local amount = money(payload.amount)
local destinationNumber = type(payload.destination) == 'string' and payload.destination:gsub('%s+', ''):upper() or nil
local reference = type(payload.reference) == 'string' and payload.reference:sub(1, 140) or 'Überweisung'
if not player or not player.accountId then return false, 'Kein Charakter aktiv.' end
if not amount or not destinationNumber or #destinationNumber < 4 then return false, 'Betrag oder Empfängerkonto ist ungültig.' end
if destinationNumber == player.accountNumber then return false, 'Du kannst nicht auf dasselbe Konto überweisen.' end
local destination = MySQL.single.await([[
SELECT id, account_number, account_name, balance
FROM devx_bank_accounts WHERE account_number = ? LIMIT 1
]], { destinationNumber })
if not destination then return false, 'Empfängerkonto wurde nicht gefunden.' end
return withLocks({ player.accountId, destination.id }, function()
local sourceAccount = MySQL.single.await([[
SELECT id, account_number, account_name, balance
FROM devx_bank_accounts WHERE id = ? AND character_id = ? LIMIT 1
]], { player.accountId, player.characterId })
destination = MySQL.single.await([[
SELECT id, account_number, account_name, balance
FROM devx_bank_accounts WHERE id = ? LIMIT 1
]], { destination.id })
if not sourceAccount or not destination then return false, 'Ein Konto wurde nicht gefunden.' end
if tonumber(sourceAccount.balance) < amount then return false, 'Das Kontoguthaben reicht nicht aus.' end
local sourceBalance = tonumber(sourceAccount.balance) - amount
local destinationBalance = tonumber(destination.balance) + amount
local transactionOk = MySQL.transaction.await({
{
query = 'UPDATE devx_bank_accounts SET balance = ? WHERE id = ?',
values = { sourceBalance, sourceAccount.id }
},
{
query = 'UPDATE devx_bank_accounts SET balance = ? WHERE id = ?',
values = { destinationBalance, destination.id }
},
{
query = [[INSERT INTO devx_bank_transactions
(account_id, direction, amount, balance_after, counterparty, reference)
VALUES (?, 'debit', ?, ?, ?, ?)]],
values = { sourceAccount.id, amount, sourceBalance, destination.account_name, reference }
},
{
query = [[INSERT INTO devx_bank_transactions
(account_id, direction, amount, balance_after, counterparty, reference)
VALUES (?, 'credit', ?, ?, ?, ?)]],
values = { destination.id, amount, destinationBalance, sourceAccount.account_name, reference }
}
})
if not transactionOk then return false, 'Überweisung fehlgeschlagen.' end
exports.devx_core:SetBankCache(source, sourceBalance)
return true, sourceBalance
end)
end
exports('GetOverview', getOverview)
exports('ChargeCharacter', chargeCharacter)
exports('CreditCharacter', creditCharacter)
exports('SetPlayerBalance', setPlayerBalance)
exports('TransferForSource', transferForSource)
DevXRegisterCallback('banking:overview', function(source)
local player = exports.devx_core:GetPlayer(source)
if not player then return { ok = false, error = 'Kein Charakter aktiv.' } end
local overview = getOverview(player.characterId)
if not overview then return { ok = false, error = 'Kein Bankkonto gefunden.' } end
exports.devx_core:SetBankCache(source, overview.account.balance)
return { ok = true, overview = overview }
end)
DevXRegisterCallback('banking:transfer', function(source, payload)
local success, result = transferForSource(source, payload)
return success and { ok = true, balance = result } or { ok = false, error = result }
end)
RegisterCommand('setbank', function(source, args)
if source ~= 0 and not IsPlayerAceAllowed(source, 'devx.admin') then return end
local target = tonumber(args[1])
local amount = money(args[2], true)
if not target or amount == nil then return end
setPlayerBalance(target, amount, ('Admin %s'):format(source))
end, false)