Wróć do bloga
Tutorial10 min czytania

Skrypty bankowości i bankomatów FiveM: realistyczne systemy pieniędzy

Stwórz wiarygodną ekonomię za pomocą skryptów bankowości i bankomatów FiveM. Logika przelewów, pomysły na UI, najlepsze darmowe i płatne zasoby oraz integracja z frameworkiem.

Agency Scripts

Założyciel i główny deweloper w Agency Scripts

Architektura systemu bankowego

System bankowy to finansowy kręgosłup każdego serwera roleplay w FiveM, obsługujący wszystko: od prostych wpłat gotówki po skomplikowane przelewy między graczami i wspólne konta organizacji. Większość frameworków, takich jak QBCore i ESX, zawiera podstawowe zarządzanie pieniędzmi, ale dedykowany system bankowy rozszerza to o właściwe zarządzanie kontami, rejestrowanie transakcji oraz dopracowany interfejs bankomatu, który wciąga graczy w finansową stronę roleplay. Architektura dzieli się na trzy warstwy: baza danych przechowuje stany kont i historię transakcji, serwer weryfikuje każdą operację finansową i egzekwuje reguły biznesowe, a klient dostarcza interfejsy bankomatów i okienek bankowych za pośrednictwem NUI. Każda operacja finansowa musi przechodzić przez stronę serwera, ponieważ manipulacja pieniędzmi po stronie klienta to główny wektor exploitów na serwerach FiveM. Nawet wyświetlanie salda powinno pochodzić z callbacku serwera, nigdy ze zbuforowanych danych po stronie klienta, którymi można manipulować.

Schemat bazy danych dla bankowości

Twoja baza danych bankowości musi obsługiwać konta osobiste, konta współdzielone dla organizacji i firm oraz szczegółowy rejestr transakcji. Rejestr transakcji nie jest opcjonalny, ponieważ pełni podwójną rolę: funkcji dostępnej dla graczy oraz narzędzia administratorskiego do badania exploitów pieniężnych. Zaprojektuj swój schemat pod kątem operacji o wysokiej przepustowości, ponieważ obciążone serwery mogą przetwarzać setki transakcji na minutę w godzinach szczytu:

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)
);

Ten bank_account_access tabela umożliwia współdzielone konta, gdzie wielu graczy może mieć różne poziomy uprawnień. Lider gangu może mieć pełny dostęp do skarbca gangu, podczas gdy zwykli członkowie mogą jedynie przeglądać saldo. Użycie BIGINT dla pól salda zapobiega problemom z przepełnieniem na serwerach z napompowaną ekonomią, gdzie stany kont graczy mogą sięgać miliardów.

Logika transakcji po stronie serwera

Każda transakcja finansowa musi być atomowa i weryfikowana na serwerze. Używaj transakcji bazodanowych, aby upewnić się, że pieniądze nigdy nie są tworzone ani niszczone podczas przelewów. Gdy gracz A przesyła pieniądze do gracza B, zarówno pobranie środków od gracza A, jak i dodanie ich graczowi B muszą powieść się jednocześnie albo żadna z tych operacji nie powinna dojść do skutku. Wdróż kontrole weryfikujące wystarczające saldo, status zablokowanego konta, dzienne limity przelewów oraz minimalne kwoty transakcji. Oto bezpieczna implementacja przelewu:

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)

Zwróć uwagę na WHERE balance >= ? klauzula w zapytaniu o potrącenie, która działa jako ostateczne zabezpieczenie przed wyścigami (race conditions), gdzie dwa jednoczesne przelewy mogłyby doprowadzić do debetu na koncie. Ta weryfikacja na poziomie bazy danych jest niezbędna, nawet jeśli sprawdzasz już stan konta w Lua, ponieważ między twoim sprawdzeniem a faktyczną aktualizacją może nadejść wiele żądań.

Interfejs użytkownika bankomatu

Interfejs ATM to kompaktowy panel NUI zapewniający szybki dostęp do podstawowych funkcji bankowych: sprawdzania salda, wpłaty gotówki, wypłaty gotówki i przelewu pieniędzy. Zachowaj przejrzysty i intuicyjny wygląd, ponieważ gracze instynktownie oczekują, że bankomat będzie działał jak jego rzeczywisty odpowiednik. Wyświetlaj bieżące saldo w widocznym miejscu na górze, z przyciskami akcji poniżej dla każdej operacji. Widoki wpłat i wypłat powinny zawierać przyciski predefiniowanych kwot dla popularnych wartości, takich jak $100, $500, $1000 i $5000, obok pola wprowadzania własnej kwoty. W przypadku przelewów udostępnij pola na numer konta odbiorcy i kwotę wraz z opcjonalnym polem tytułu przelewu. Wyświetlaj krok potwierdzenia przed wykonaniem dowolnej transakcji, aby przypadkowe kliknięcia nie kosztowały graczy pieniędzy. Dołącz listę ostatnich transakcji wyświetlającą 10 ostatnich pozycji, aby gracze mogli sprawdzić swoją aktywność finansową bez konieczności odwiedzania pełnego oddziału banku. UI bankomatu powinno działać płynnie, dlatego pobieraj saldo i dane transakcji w jednym callbacku podczas otwierania menu, zamiast wysyłać osobne zapytania o każdą informację.

Konfiguracja interakcji z bankomatem

Umieść punkty interakcji z bankomatem w miejscach istniejących propów bankomatów na całej mapie GTA. FiveM udostępnia listę hashy modeli bankomatów, po których możesz iterować, aby znaleźć wszystkie propy bankomatów w świecie gry. Użyj systemu target, takiego jak ox_target, aby uzyskać przejrzystą interakcję, lub skorzystaj z alternatywy w postaci sprawdzania bliskości przy każdym propie bankomatu. Gdy gracz wejdzie w interakcję z bankomatem, odtwórz animację korzystania z bankomatu przez gracza, a następnie otwórz panel 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
        }
    })
end

Historia transakcji i wyciągi

Historia transakcji przekształca Twój system bankowy z prostej maszyny do wpłat i wypłat w pełnoprawne narzędzie do zarządzania finansami. Gracze powinni mieć możliwość przeglądania pełnej historii transakcji w oddziałach banku, filtrowanej według zakresu dat, typu transakcji lub kwoty. Każdy wpis transakcji powinien wyświetlać datę, typ, kwotę, saldo końcowe, opis oraz drugą stronę w przypadku przelewów. Zaimplementuj paginację po stronie serwera, ponieważ ładowanie tysięcy transakcji naraz zamrozi ramkę NUI i spowoduje gwałtowny wzrost zużycia pamięci serwera. Zwracaj 20-30 transakcji na stronę i pozwól graczowi ładować więcej w razie potrzeby. W placówkach bankowych zaoferuj dodatkowe funkcje wykraczające poza możliwości bankomatów, takie jak otwieranie nowych kont, zarządzanie uprawnieniami do wspólnych kont, generowanie wyciągów z konta za określony okres oraz wnioskowanie o pożyczki, jeśli Twój serwer obsługuje taką mechanikę. Zapisuj opisy transakcji jako czytelne dla człowieka ciągi znaków, aby automatyczne transakcje z prac, zakupów w sklepach i podatków rządowych wyświetlały czytelne wpisy, które gracze zrozumieją bez dodatkowego kontekstu.

Mechanika napadów na bank

Napady na bank to jedno z najbardziej ekscytujących wydarzeń na każdym serwerze roleplay, tworzące scenariusze o wysoką stawkę z udziałem przestępców, policji, negocjatorów i świadków. Dobrze zaprojektowany system napadów obejmuje wiele faz: rozpoznanie banku, rozpoczęcie skoku, hakowanie lub rozwiercanie systemów bezpieczeństwa za pomocą minigier, pakowanie łupu i ucieczkę w pościgu z policją. Zacznij od zdefiniowania, które banki można obrabować, ich poziomu trudności, czasów odnowienia i wymaganych przedmiotów. Napad powinien wymagać określonych narzędzi, takich jak termit do drzwi skarbca, urządzenia hakujące do paneli bezpieczeństwa i torby sportowe do przenoszenia łupu. Wdróż minigry o rosnącym poziomie trudności dla każdej warstwy zabezpieczeń, w których nieudane hakowanie wywołuje dodatkowe alarmy lub jeszcze bardziej blokuje skarbiec:

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 },
        },
    },
}

Używaj oznaczonych banknotów jako nagrody za napad zamiast czystej gotówki, zmuszając przestępców do prania pieniędzy w dodatkowych pętlach rozgrywki, takich jak pralnie pieniędzy lub punkty wymiany brudnej gotówki. Wydłuża to roleplay napadu poza sam skok i stwarza okazje do śledztw policyjnych. Powiadamiaj system dyspozytorski policji o rozpoczęciu napadu i śledź jego postęp po stronie serwera, aby funkcjonariusze mogli reagować taktycznie w zależności od postępów przestępców.

Środki bezpieczeństwa i anti-exploit

Systemy finansowe są głównym celem osób wykorzystujących błędy (exploiterów), ponieważ pieniądze bezpośrednio przekładają się na przewagę w grze. Oprócz omówionej już walidacji po stronie serwera wdróż kilka dodatkowych warstw zabezpieczeń. Dodaj rate limiting do wszystkich eventów bankowych, aby pojedynczy gracz nie mógł wysyłać setek żądań depozytu lub przelewu na sekundę. Rejestruj każdą transakcję finansową wraz z identyfikatorami gracza źródłowego oraz znacznikiem czasu, aby administratorzy mogli śledzić przepływ pieniędzy i wykrywać błędy duplikacji. Wdróż system cofania transakcji, za pomocą którego administratorzy mogą cofać fałszywe transakcje po wykryciu exploita. Ustaw maksymalne limity pojedynczych transakcji oraz dzienne limity łączne, które skalują się wraz z wiekiem konta gracza i całkowitym czasem gry, co sprawi, że nowo utworzone konta będą mniej przydatne do prania pieniędzy. Monitoruj podejrzane wzorce, takie jak szybkie transfery tam i z powrotem między dwoma kontami, depozyty dokładnie odpowiadające kwocie wypłaty innego gracza w ciągu kilku sekund lub wzrosty salda bez powiązanych wpisów transakcji. Wysyłaj powiadomienia webhook Discord po wykryciu podejrzanej aktywności, aby Twój zespół moderacji mógł badać sprawę w czasie rzeczywistym bez czekania na zgłoszenia graczy. Rozważ wdrożenie systemu zamrażania kont, w którym administrator może zablokować konta na czas dochodzenia, uniemożliwiając wydanie lub przesłanie zdobytych nielegalnie pieniędzy do czasu rozwiązania problemu.

Integracja z ekonomią serwera

Twój system bankowy powinien służyć jako centralny węzeł dla całego przepływu pieniędzy na serwerze. Kieruj wypłaty z prac przez system bankowy, aby gracze otrzymywali wynagrodzenie jako przelew na konto z przejrzystym wpisem transakcji pokazującym, która praca i ile im zapłaciła. Połącz zakupy w sklepach tak, aby wywoływały wypłatę z banku, gdy gracze płacą kartą zamiast gotówką, co tworzy ślad transakcyjny zwiększający realizm i dający graczom powód do korzystania z systemu bankowego poza zwykłym przechowywaniem środków. Wdróż automatyczne naliczanie opłat za cykliczne koszty, takie jak podatki od nieruchomości, ubezpieczenia pojazdów i wydatki operacyjne firm, pobierane z konta bankowego gracza w regularnych odstępach czasu. Jeśli na koncie gracza brakuje środków na automatyczną płatność, system powinien zarejestrować nieudaną płatność i wywołać konsekwencje, takie jak ostrzeżenia o zajęciu mienia lub wygaśnięcie ubezpieczenia. Połącz system bankowy ze swoim zasobem telefonu, aby gracze mogli sprawdzać stan konta, przeglądać ostatnie transakcje i wykonywać szybkie przelewy bez fizycznej wizyty przy bankomacie lub w oddziale banku. Ta integracja przekształca system bankowy z odizolowanej funkcji w finansowy układ nerwowy łączący każdą aktywność ekonomiczną na Twoim serwerze.

Pasujący skrypt

Gotowe na twój serwer, aktualizacje w zestawie.

Gotowy, aby zacząć?

Kup skrypty w naszym sklepie lub dołącz do Discorda, aby uzyskać wsparcie, aktualizacje i sprawdzić nadchodzące nowości.