Arquitetura do Sistema Bancário
Um sistema bancário é a espinha dorsal financeira de qualquer servidor de roleplay FiveM, lidando com tudo, desde simples depósitos em dinheiro até transferências complexas entre jogadores e contas de organização compartilhadas. A maioria das estruturas como QBCore e ESX incluem gerenciamento básico de dinheiro, mas um sistema bancário dedicado estende isso com gerenciamento de contas adequado, registro de transações e uma interface ATM refinada que mergulha os jogadores no lado financeiro do RPG. A arquitetura se divide em três camadas: o banco de dados armazena saldos de contas e registros de transações, o servidor valida todas as operações financeiras e aplica regras de negócios, e o cliente fornece interfaces de caixas eletrônicos e balcões bancários por meio de NUI. Cada operação de dinheiro deve fluir pelo lado do servidor porque a manipulação de dinheiro do lado do cliente é o vetor de exploração número um nos servidores FiveM. Até mesmo a exibição de um saldo deve vir de um retorno de chamada do servidor, nunca de dados armazenados em cache do lado do cliente que possam ser adulterados.
Esquema de banco de dados para bancos
Seu banco de dados bancário precisa oferecer suporte a contas pessoais, contas compartilhadas para organizações e empresas e um registro de transações abrangente. O log de transações não é opcional porque tem função dupla tanto como um recurso voltado para o jogador quanto como uma ferramenta administrativa para investigar explorações de dinheiro. Projete seu esquema para lidar com operações de alto rendimento, pois servidores ocupados podem processar centenas de transações por minuto durante horários de pico:
CREATE TABLE IF NOT EXISTS bank_accounts (
id INT AUTO_INCREMENT PRIMARY KEY,
account_number VARCHAR(20) UNIQUE NOT NULL,
owner_citizenid VARCHAR(50) NOT NULL,
account_type ENUM('personal', 'business', 'gang', 'shared') DEFAULT 'personal',
balance BIGINT DEFAULT 0,
account_name VARCHAR(100) DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_frozen BOOLEAN DEFAULT FALSE,
INDEX idx_owner (owner_citizenid),
INDEX idx_type (account_type)
);
CREATE TABLE IF NOT EXISTS bank_transactions (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
account_id INT NOT NULL,
type ENUM('deposit', 'withdraw', 'transfer_in', 'transfer_out', 'paycheck', 'purchase') NOT NULL,
amount BIGINT NOT NULL,
balance_after BIGINT NOT NULL,
description VARCHAR(255) DEFAULT NULL,
other_account VARCHAR(20) DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_account (account_id),
INDEX idx_created (created_at),
FOREIGN KEY (account_id) REFERENCES bank_accounts(id)
);
CREATE TABLE IF NOT EXISTS bank_account_access (
account_id INT NOT NULL,
citizenid VARCHAR(50) NOT NULL,
permission ENUM('view', 'withdraw', 'full') DEFAULT 'view',
PRIMARY KEY (account_id, citizenid),
FOREIGN KEY (account_id) REFERENCES bank_accounts(id)
);A tabela bank_account_access permite contas compartilhadas onde vários jogadores podem ter diferentes níveis de permissão. Um líder de gangue pode ter acesso total ao tesouro da gangue, enquanto os membros regulares só podem visualizar o saldo. Usar BIGINT para campos de saldo evita problemas de overflow em servidores com economias inflacionadas, onde os saldos dos jogadores podem chegar a bilhões.
Lógica de transação do lado do servidor
Toda transação financeira deve ser atômica e validada no servidor. Use transações de banco de dados para garantir que dinheiro nunca seja criado ou destruído durante as transferências. Quando o jogador A envia dinheiro para o jogador B, tanto a dedução de A como a adição a B devem ser bem sucedidas em conjunto, ou nenhuma delas deve ser aplicada. Implemente verificações de validação de saldo suficiente, status de conta congelada, limites diários de transferência e valores mínimos de transação. Aqui está uma implementação de transferência segura:
RegisterNetEvent('banking:server:transfer', function(targetAccount, amount, description)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
amount = math.floor(tonumber(amount) or 0)
if amount <= 0 then
TriggerClientEvent('QBCore:Notify', src, 'Invalid amount', 'error')
return
end
local citizenid = Player.PlayerData.citizenid
local senderAccount = GetPlayerPrimaryAccount(citizenid)
if not senderAccount or senderAccount.is_frozen then
TriggerClientEvent('QBCore:Notify', src, 'Account unavailable', 'error')
return
end
if senderAccount.balance < amount then
TriggerClientEvent('QBCore:Notify', src, 'Insufficient funds', 'error')
return
end
-- Check daily transfer limit
local todayTransfers = GetDailyTransferTotal(senderAccount.id)
if todayTransfers + amount > Config.DailyTransferLimit then
TriggerClientEvent('QBCore:Notify', src, 'Daily limit exceeded', 'error')
return
end
-- Atomic transfer using database transaction
local success = MySQL.transaction.await({
{
query = 'UPDATE bank_accounts SET balance = balance - ? WHERE id = ? AND balance >= ?',
values = {amount, senderAccount.id, amount}
},
{
query = 'UPDATE bank_accounts SET balance = balance + ? WHERE account_number = ?',
values = {amount, targetAccount}
},
{
query = 'INSERT INTO bank_transactions (account_id, type, amount, balance_after, description, other_account) VALUES (?, "transfer_out", ?, (SELECT balance FROM bank_accounts WHERE id = ?), ?, ?)',
values = {senderAccount.id, amount, senderAccount.id, description or 'Transfer', targetAccount}
},
})
if success then
TriggerClientEvent('QBCore:Notify', src, 'Transfer complete: $' .. amount, 'success')
TriggerClientEvent('banking:client:refreshBalance', src)
else
TriggerClientEvent('QBCore:Notify', src, 'Transfer failed', 'error')
end
end)Observe a cláusula WHERE balance >= ? na consulta de dedução, que atua como uma proteção final contra condições de corrida em que duas transferências simultâneas poderiam sacar a conta. Essa verificação no nível do banco de dados é essencial mesmo que você já tenha verificado o saldo em Lua, porque diversas solicitações podem chegar entre a sua verificação e a atualização real.
Interface de usuário ATM
A interface ATM é um painel NUI compacto que fornece acesso rápido às principais funções bancárias: verificar saldo, depositar dinheiro, sacar dinheiro e transferir dinheiro. Mantenha o design limpo e familiar porque os jogadores esperam instintivamente que um caixa eletrônico funcione como o equivalente do mundo real. Exiba o saldo atual em destaque na parte superior, com botões de ação abaixo para cada operação. As visualizações de depósito e retirada devem incluir botões de valores predefinidos para valores comuns como $ 100, $ 500, $ 1.000 e $ 5.000 junto com um campo de entrada de valor personalizado. Para transferências, forneça campos para o número e valor da conta do destinatário, juntamente com um campo de memorando opcional. Mostre uma etapa de confirmação antes de executar qualquer transação para evitar que cliques acidentais custem dinheiro aos jogadores. Inclui uma lista de transações recentes que exibe as últimas 10 entradas para que os jogadores possam verificar suas atividades financeiras sem precisar visitar uma agência bancária completa. A interface do ATM deve parecer ágil, portanto, busque o saldo e os dados da transação em um único retorno de chamada quando o menu for aberto, em vez de fazer solicitações separadas para cada informação.
Configuração de interação ATM
Coloque pontos de interação ATM nos locais de suporte ATM existentes em todo o mapa GTA. FiveM fornece uma lista de hashes de modelo de ATM que você pode iterar para encontrar todos os acessórios de ATM no mundo do jogo. Use um sistema de destino como ox_target para interação limpa ou volte para verificações de proximidade perto de cada suporte ATM. Quando o jogador interagir com um caixa eletrônico, reproduza uma animação do jogador usando o caixa eletrônico e abra o painel NUI:
local atmModels = {
'prop_atm_01', 'prop_atm_02', 'prop_atm_03',
'prop_fleeca_atm', 'v_5_b_atm1'
}
-- Using ox_target for ATM interaction
for _, model in ipairs(atmModels) do
exports.ox_target:addModel(GetHashKey(model), {
{
name = 'use_atm',
icon = 'fas fa-credit-card',
label = 'Use ATM',
onSelect = function(data)
local ped = PlayerPedId()
local atmCoords = GetEntityCoords(data.entity)
-- Face the ATM
TaskTurnPedToFaceCoord(ped, atmCoords.x, atmCoords.y, atmCoords.z, 1000)
Wait(1000)
-- Play ATM animation
RequestAnimDict('mini@atmenter')
while not HasAnimDictLoaded('mini@atmenter') do Wait(10) end
TaskPlayAnim(ped, 'mini@atmenter', 'enter', 8.0, -8.0, -1, 0, 0, false, false, false)
-- Open ATM UI
QBCore.Functions.TriggerCallback('banking:server:getAccountData', function(data)
SetNuiFocus(true, true)
SendNUIMessage({
action = 'openATM',
balance = data.balance,
transactions = data.recentTransactions,
accountNumber = data.accountNumber
})
end)
end
}
})
endHistórico de transações e extratos
O histórico de transações transforma seu sistema bancário de uma simples máquina de depósito e retirada em uma ferramenta completa de gerenciamento financeiro. Os jogadores devem ser capazes de visualizar seu histórico completo de transações nas agências bancárias, filtrado por intervalo de datas, tipo de transação ou valor. Cada entrada de transação deve exibir a data, tipo, valor, saldo resultante, descrição e a outra parte envolvida nas transferências. Implemente a paginação no lado do servidor porque carregar milhares de transações de uma vez irá congelar o quadro NUI e aumentar o uso de memória do servidor. Retorne de 20 a 30 transações por página e deixe o player carregar mais conforme necessário. Para agências bancárias, ofereça recursos adicionais além do que os caixas eletrônicos oferecem, como abertura de novas contas, gerenciamento de permissões de contas compartilhadas, geração de extratos de conta para um período específico e solicitação de empréstimos se o seu servidor suportar esse mecanismo. Armazene as descrições das transações como sequências legíveis para que as transações automatizadas de empregos, compras em lojas e impostos governamentais mostrem entradas claras que os jogadores possam entender sem contexto.
Mecânica de assalto a banco
Os assaltos a bancos são um dos eventos mais emocionantes em qualquer servidor de RPG, criando cenários de alto risco que envolvem criminosos, policiais, negociadores de reféns e transeuntes. Um sistema de roubo bem projetado inclui múltiplas fases: investigar o banco, iniciar o assalto, hackear ou perfurar sistemas de segurança por meio de minijogos, carregar o saque e escapar com a polícia em sua perseguição. Comece definindo quais bancos podem ser roubados, seu nível de dificuldade, tempos de espera e itens necessários. O roubo deve exigir ferramentas específicas, como termite para portas de cofres, dispositivos de hacking para painéis de segurança e mochilas para transportar o saque. Implemente minijogos de dificuldade progressiva para cada camada de segurança, onde falhar em um hack aciona alarmes adicionais ou bloqueia ainda mais o cofre:
Config.BankRobberies = {
['fleeca_1'] = {
label = 'Fleeca Bank - Legion Square',
coords = vector3(149.73, -1042.65, 29.37),
vault = vector3(144.87, -1044.16, 29.37),
tier = 1, -- 1=Fleeca, 2=Paleto, 3=Pacific Standard
cooldown = 7200, -- 2 hours
minPolice = 3,
requiredItems = {'electronickit', 'thermite'},
reward = { min = 40000, max = 80000, markedBills = true },
securityLayers = {
{ type = 'hack', difficulty = 'easy', time = 30 },
{ type = 'thermite', time = 10 },
{ type = 'drill', time = 45 },
},
},
['pacific_standard'] = {
label = 'Pacific Standard Bank',
coords = vector3(255.85, 225.60, 101.88),
vault = vector3(262.20, 222.10, 101.68),
tier = 3,
cooldown = 14400, -- 4 hours
minPolice = 6,
requiredItems = {'electronickit', 'thermite', 'advancedlaptop'},
reward = { min = 200000, max = 400000, markedBills = true },
securityLayers = {
{ type = 'hack', difficulty = 'hard', time = 20 },
{ type = 'hack', difficulty = 'hard', time = 20 },
{ type = 'thermite', time = 8 },
{ type = 'drill', time = 60 },
{ type = 'hack', difficulty = 'expert', time = 15 },
},
},
}Use notas marcadas como recompensa de roubo em vez de dinheiro limpo, forçando os criminosos a lavar o dinheiro por meio de ciclos de jogo adicionais, como locais de lavagem de dinheiro ou trocas de dinheiro sujo. Isso estende a dramatização do roubo além do roubo em si e cria oportunidades para investigações policiais. Notifique o sistema de despacho policial quando um roubo começar e acompanhe o progresso do roubo no servidor para que os policiais possam responder taticamente com base no progresso dos criminosos.
Medidas de segurança e anti-exploração
Os sistemas financeiros são o principal alvo dos exploradores porque o dinheiro se traduz diretamente em poder no jogo. Além da validação do lado do servidor já discutida, implemente diversas camadas de segurança adicionais. Adicione limitação de taxa a todos os eventos bancários para que um único jogador não possa disparar centenas de solicitações de depósito ou transferência por segundo. Registre todas as transações financeiras com os identificadores e carimbo de data/hora do jogador de origem para que os administradores possam rastrear o fluxo de dinheiro e identificar explorações de duplicação. Implemente um sistema de reversão de transações que os administradores possam usar para desfazer transações fraudulentas quando explorações forem descobertas. Defina limites máximos de transação única e limites cumulativos diários que variam de acordo com a idade da conta do jogador e o tempo total de jogo, tornando as contas recém-criadas menos úteis para lavagem de dinheiro. Monitore padrões suspeitos, como transferências rápidas de ida e volta entre duas contas, depósitos que correspondam exatamente ao valor da retirada de outro jogador em segundos ou aumentos de saldo sem registros de transação correspondentes. Envie alertas de webhook do Discord quando atividades suspeitas forem detectadas para que sua equipe de moderação possa investigar em tempo real sem esperar pelos relatórios dos jogadores. Considere implementar um sistema de contas congeladas onde o administrador possa bloquear contas durante as investigações, evitando que o dinheiro explorado seja gasto ou transferido enquanto o problema é resolvido.
Integração com a economia do servidor
Seu sistema bancário deve servir como hub central para todo o fluxo monetário no servidor. Encaminhe os contracheques de trabalho através do sistema bancário para que os jogadores recebam seu salário como um depósito bancário com um registro de transação claro mostrando qual trabalho lhes pagou e quanto. Conecte compras em lojas para acionar saques bancários quando os jogadores pagam com cartão em vez de dinheiro, criando um registro em papel que adiciona realismo e dá aos jogadores um motivo para usar o sistema bancário além do simples armazenamento. Implemente o faturamento automático para custos recorrentes, como impostos sobre a propriedade, seguro de veículos e despesas operacionais comerciais que são deduzidas da conta bancária do jogador em intervalos regulares. Se a conta de um jogador não tiver fundos suficientes para um pagamento automático, o sistema deverá registar uma falha no pagamento e desencadear consequências como avisos de apreensão de propriedade ou caducidade do seguro. Vincule o sistema bancário ao recurso do seu telefone para que os jogadores possam verificar seu saldo, visualizar transações recentes e fazer transferências rápidas sem visitar fisicamente um caixa eletrônico ou agência bancária. Essa integração transforma o sistema bancário de um recurso isolado no sistema nervoso financeiro que conecta todas as atividades econômicas em seu servidor.
