first commit
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user