-- Initialize QBCore local QBCore = exports['qb-core']:GetCoreObject() local isUiOpen = false local cooldown = false local pendingBills = {} -- Command to open main billing menu RegisterCommand('bill', function() OpenMainBillingMenu() end, false) -- Keybind registration RegisterKeyMapping('bill', 'Open Billing Menu', 'keyboard', 'F7') -- Default key is F7, can be changed in settings -- Function to open the main billing menu function OpenMainBillingMenu() lib.registerContext({ id = 'main_billing_menu', title = 'Billing System', options = { { title = 'Rechnung erstellen', description = 'Sende eine Rechnung an einen Spieler in der Nähe', icon = 'file-invoice-dollar', onSelect = function() OpenCreateBillMenu() end }, { title = 'Meine Rechnungen', description = 'Sehe & bezahle deine erhaltenen Rechnungen', icon = 'money-check', onSelect = function() ViewBills() end } } }) lib.showContext('main_billing_menu') end -- Function to open the create bill menu function OpenCreateBillMenu() if cooldown then lib.notify({ title = 'Billing System', description = 'Bitte warte, bevor du das Abrechnungssystem erneut verwendest', type = 'error' }) return end -- Get nearby players local players = GetNearbyPlayers() if #players == 0 then lib.notify({ title = 'Billing System', description = 'Keine Spieler in der Nähe zum Abrechnen', type = 'error' }) return end -- Get player's accounts for receiving payment local accounts = lib.callback.await('ps-banking:server:getAccounts', false) or {} local accountOptions = {} -- Add default bank account table.insert(accountOptions, { value = 'personal', label = 'Persönliches Bankkonto' }) -- Add other accounts for _, account in ipairs(accounts) do table.insert(accountOptions, { value = account.id, label = account.holder .. ' - ' .. account.cardNumber }) end -- Show the list of nearby players local playerOptions = {} for _, player in ipairs(players) do table.insert(playerOptions, { title = player.name, description = 'ID: ' .. player.id, icon = 'user', onSelect = function() -- When a player is selected, show the billing form ShowBillingForm(player, accountOptions) end }) end -- Register and show the player selection menu lib.registerContext({ id = 'nearby_players_menu', title = 'Spieler zum Abrechnen auswählen', menu = 'main_billing_menu', -- Add back button to main menu options = playerOptions }) lib.showContext('nearby_players_menu') end -- Function to show the billing form after selecting a player function ShowBillingForm(selectedPlayer, accountOptions) local input = lib.inputDialog('Rechnung erstellen für ' .. selectedPlayer.name, { { type = 'number', label = 'Betrag ($)', description = 'Gib den Rechnungsbetrag ein', icon = 'dollar-sign', required = true, min = 1, max = 100000 }, { type = 'input', label = 'Grund', description = 'Gib den Grund für die Rechnung ein', placeholder = 'Erbrachte Dienstleistungen...', required = true }, { type = 'select', label = 'Empfangskonto', options = accountOptions, required = true } }) if not input then return end -- Play animation PlayBillingAnimation() -- Send the bill local billData = lib.callback.await('billing:server:sendBill', false, { playerId = selectedPlayer.id, amount = input[1], reason = input[2], account = input[3] }) if billData and billData.success then lib.notify({ title = 'Billing System', description = 'Rechnung erfolgreich an ' .. selectedPlayer.name .. ' gesendet', type = 'success' }) -- Set cooldown cooldown = true SetTimeout(5000, function() cooldown = false end) -- Add to pending bills pendingBills[billData.billId] = { player = selectedPlayer, amount = input[1], reason = input[2], account = input[3], timestamp = GetGameTimer() } -- Show waiting screen with all details ShowWaitingScreen(billData.billId, selectedPlayer.name, input[1], input[2]) else lib.notify({ title = 'Billing System', description = 'Fehler beim Senden der Rechnung', type = 'error' }) -- Return to main menu OpenMainBillingMenu() end end -- Function to show waiting screen function ShowWaitingScreen(billId, playerName, amount, reason) -- Create a more informative waiting screen lib.registerContext({ id = 'bill_waiting_screen', title = 'Warte auf Zahlung', options = { { title = 'Rechnung Details', description = 'Gesendet an: ' .. playerName, metadata = { {label = 'Betrag', value = '$' .. amount}, {label = 'Grund', value = reason}, {label = 'Status', value = 'Warte auf Antwort...'}, }, icon = 'file-invoice-dollar', disabled = true }, { title = 'Warte auf Antwort', progress = 100, -- This creates a loading bar description = playerName .. ' entscheidet über die Rechnung', icon = 'clock', disabled = true }, { title = 'Abbrechen', description = 'Zurück zum Hauptmenü', icon = 'times-circle', onSelect = function() pendingBills[billId] = nil OpenMainBillingMenu() end } } }) lib.showContext('bill_waiting_screen') -- Start a loading animation Citizen.CreateThread(function() local dots = 0 local loadingText = {'Warte', 'Warte.', 'Warte..', 'Warte...'} local progress = 100 while pendingBills[billId] do Citizen.Wait(500) dots = (dots + 1) % 4 -- Update the context menu with new loading text lib.registerContext({ id = 'bill_waiting_screen', title = 'Warte auf Zahlung', options = { { title = 'Rechnung Details', description = 'Gesendet an: ' .. playerName, metadata = { {label = 'Betrag', value = '$' .. amount}, {label = 'Grund', value = reason}, {label = 'Status', value = 'Warte auf Antwort...'}, }, icon = 'file-invoice-dollar', disabled = true }, { title = loadingText[dots + 1], progress = progress, description = playerName .. ' entscheidet über die Rechnung', icon = 'clock', disabled = true }, { title = 'Abbrechen', description = 'Zurück zum Hauptmenü', icon = 'times-circle', onSelect = function() pendingBills[billId] = nil OpenMainBillingMenu() end } } }) -- Only refresh the context if it's still showing if isMenuOpen('bill_waiting_screen') then lib.showContext('bill_waiting_screen') else break end -- Cycle the progress bar progress = progress - 5 if progress <= 0 then progress = 100 end end end) end -- Helper function to check if a menu is open function isMenuOpen(menuId) -- This is a placeholder - ox_lib doesn't provide a direct way to check -- You might need to track this yourself or use a different approach return true end -- Function to show payment account selection function ShowPaymentAccountSelection(billId, amount, description) -- Get player's accounts for payment local accounts = lib.callback.await('ps-banking:server:getAccounts', false) or {} local accountOptions = {} -- Add default bank account table.insert(accountOptions, { value = 'personal', label = 'Persönliches Bankkonto', description = 'Bezahlen mit deinem Hauptkonto' }) -- Add other accounts for _, account in ipairs(accounts) do table.insert(accountOptions, { value = account.id, label = account.holder .. ' - ' .. account.cardNumber, description = 'Kontostand: $' .. account.balance }) end -- Register and show the account selection menu lib.registerContext({ id = 'payment_account_menu', title = 'Wähle Zahlungskonto', menu = 'billing_menu', -- Add back button to bills menu options = accountOptions, onExit = function() ViewBills() -- Return to bills menu end, onSelect = function(selected) local accountId = selected.value local confirm = lib.alertDialog({ header = 'Rechnung bezahlen', content = ('Möchtest du $%s für %s von diesem Konto bezahlen?'):format(amount, description), centered = true, cancel = true }) if confirm == 'confirm' then local success = lib.callback.await('billing:server:payBillFromAccount', false, { billId = billId, accountId = accountId }) if success then lib.notify({ title = 'Billing System', description = 'Rechnung erfolgreich bezahlt', type = 'success' }) ViewBills() -- Refresh the list else lib.notify({ title = 'Billing System', description = 'Fehler beim Bezahlen der Rechnung. Unzureichendes Guthaben.', type = 'error' }) end end end }) lib.showContext('payment_account_menu') end -- Function to view received bills function ViewBills() -- Get all bills from ps-banking local bills = lib.callback.await('ps-banking:server:getBills', false) if not bills or #bills == 0 then lib.notify({ title = 'Billing System', description = 'Du hast keine unbezahlten Rechnungen', type = 'info' }) -- Return to main menu after a short delay SetTimeout(1000, function() OpenMainBillingMenu() end) return end -- Get bill statuses from our custom table local billStatuses = lib.callback.await('billing:server:getAllBillStatuses', false) local statusMap = {} -- Create a map of bill ID to status for quick lookup if billStatuses then for _, status in ipairs(billStatuses) do statusMap[status.bill_id] = status end end local billOptions = {} for _, bill in ipairs(bills) do local timestamp = os.date('%Y-%m-%d %H:%M', bill.date) -- Check if bill is declined in our custom table local billStatus = statusMap[bill.id] local isDeclined = billStatus and billStatus.declined == 1 local status = 'Unbezahlt' if bill.isPaid then status = 'Bezahlt' elseif isDeclined then status = 'Abgelehnt' end table.insert(billOptions, { title = bill.description, description = ('Betrag: $%s | Datum: %s'):format(bill.amount, timestamp), icon = 'file-invoice', metadata = { {label = 'Rechnungs-ID', value = bill.id}, {label = 'Typ', value = bill.type}, {label = 'Status', value = status} }, onSelect = function() if not bill.isPaid and not isDeclined then -- Show account selection for payment ShowPaymentAccountSelection(bill.id, bill.amount, bill.description) end end }) end lib.registerContext({ id = 'billing_menu', title = 'Deine Rechnungen', menu = 'main_billing_menu', -- Add back button to main menu options = billOptions }) lib.showContext('billing_menu') end -- Helper function to get nearby players function GetNearbyPlayers() local playerPed = PlayerPedId() local playerCoords = GetEntityCoords(playerPed) local players = {} -- Get all players local allPlayers = GetActivePlayers() for _, player in ipairs(allPlayers) do local targetPed = GetPlayerPed(player) local targetCoords = GetEntityCoords(targetPed) local distance = #(playerCoords - targetCoords) if distance <= 5.0 and targetPed ~= playerPed then local targetId = GetPlayerServerId(player) local targetName = GetPlayerName(player) table.insert(players, {id = targetId, name = targetName}) end end return players end -- Animation function function PlayBillingAnimation() lib.requestAnimDict("cellphone@") -- Request the prop model local propModel = 'bzzz_prop_payment_terminal' lib.requestModel(propModel) -- Create prop local playerPed = PlayerPedId() local coords = GetEntityCoords(playerPed) local prop = CreateObject(GetHashKey(propModel), coords.x, coords.y, coords.z, true, true, true) -- Attach prop to player AttachEntityToEntity(prop, playerPed, GetPedBoneIndex(playerPed, 57005), 0.18, 0.01, 0.0, -54.0, 220.0, 43.0, true, true, false, true, 1, true) -- Play animation TaskPlayAnim(playerPed, "cellphone@", "cellphone_text_read_base", 2.0, 3.0, -1, 49, 0, false, false, false) -- Wait for animation to complete Wait(5000) -- Clear animation and delete prop ClearPedTasks(playerPed) DeleteEntity(prop) end -- Event to show payment prompt when receiving a bill RegisterNetEvent('billing:client:showPaymentPrompt', function(data) -- Play a notification sound PlaySound(-1, "Event_Start_Text", "GTAO_FM_Events_Soundset", 0, 0, 1) -- Get player's accounts for payment local accounts = lib.callback.await('ps-banking:server:getAccounts', false) or {} local accountOptions = {} -- Add default bank account table.insert(accountOptions, { title = 'Persönliches Bankkonto', description = 'Bezahlen mit deinem Hauptkonto', icon = 'credit-card', onSelect = function() local success = lib.callback.await('billing:server:handleBillResponse', false, { action = 'pay', billId = data.billId, accountId = 'personal' }) if success then lib.notify({ title = 'Rechnung bezahlt', description = 'Du hast die Rechnung erfolgreich bezahlt', type = 'success' }) -- Notify the sender TriggerServerEvent('billing:server:notifyBillSender', data.billId, 'pay') else lib.notify({ title = 'Zahlung fehlgeschlagen', description = 'Du hast nicht genug Geld auf deinem Konto', type = 'error' }) end end }) -- Add other accounts for _, account in ipairs(accounts) do table.insert(accountOptions, { title = account.holder .. ' - ' .. account.cardNumber, description = 'Kontostand: $' .. account.balance, icon = 'university', onSelect = function() local success = lib.callback.await('billing:server:handleBillResponse', false, { action = 'pay', billId = data.billId, accountId = account.id }) if success then lib.notify({ title = 'Rechnung bezahlt', description = 'Du hast die Rechnung erfolgreich bezahlt', type = 'success' }) -- Notify the sender TriggerServerEvent('billing:server:notifyBillSender', data.billId, 'pay') else lib.notify({ title = 'Zahlung fehlgeschlagen', description = 'Nicht genug Geld auf diesem Konto', type = 'error' }) end end }) end -- Create the payment prompt lib.registerContext({ id = 'bill_payment_prompt', title = 'Neue Rechnung erhalten', options = { { title = 'Rechnung Details', description = 'Von: ' .. data.sender, metadata = { {label = 'Betrag', value = '$' .. data.amount}, {label = 'Grund', value = data.reason}, } }, { title = 'Bezahlen', description = 'Wähle ein Konto zum Bezahlen', icon = 'money-bill', menu = 'payment_account_selection' }, { title = 'Ablehnen', description = 'Rechnung ablehnen', icon = 'times-circle', onSelect = function() local confirm = lib.alertDialog({ header = 'Rechnung ablehnen', content = 'Bist du sicher, dass du diese Rechnung ablehnen möchtest?', centered = true, cancel = true }) if confirm == 'confirm' then local success = lib.callback.await('billing:server:handleBillResponse', false, { action = 'decline', billId = data.billId }) if success then lib.notify({ title = 'Rechnung abgelehnt', description = 'Du hast die Rechnung abgelehnt', type = 'info' }) -- Notify the sender TriggerServerEvent('billing:server:notifyBillSender', data.billId, 'decline') else lib.notify({ title = 'Fehler', description = 'Fehler beim Ablehnen der Rechnung', type = 'error' }) end end end } -- "Pay Later" option removed } }) -- Register the account selection submenu lib.registerContext({ id = 'payment_account_selection', title = 'Wähle Zahlungskonto', menu = 'bill_payment_prompt', options = accountOptions }) -- Show the payment prompt lib.showContext('bill_payment_prompt') end) -- Event to handle bill response RegisterNetEvent('billing:client:billResponse', function(billId, action, playerName) -- Check if this bill is in our pending bills if pendingBills[billId] then local bill = pendingBills[billId] if action == 'pay' then lib.notify({ title = 'Rechnung bezahlt', description = playerName .. ' hat deine Rechnung über $' .. bill.amount .. ' bezahlt', type = 'success', duration = 7000 }) -- Show a more detailed success message lib.alertDialog({ header = 'Zahlung erhalten', content = playerName .. ' hat deine Rechnung über $' .. bill.amount .. ' für "' .. bill.reason .. '" bezahlt.', centered = true, cancel = false }) elseif action == 'decline' then lib.notify({ title = 'Rechnung abgelehnt', description = playerName .. ' hat deine Rechnung über $' .. bill.amount .. ' abgelehnt', type = 'error', duration = 7000 }) -- Show a more detailed decline message lib.alertDialog({ header = 'Rechnung abgelehnt', content = playerName .. ' hat deine Rechnung über $' .. bill.amount .. ' für "' .. bill.reason .. '" abgelehnt.', centered = true, cancel = false }) end -- "later" case removed -- Remove from pending bills pendingBills[billId] = nil -- Return to main menu OpenMainBillingMenu() end end) -- Event handler for opening bills menu from command RegisterNetEvent('billing:client:openBillsMenu', function() ViewBills() end) -- Check for timed out bills every minute Citizen.CreateThread(function() while true do Citizen.Wait(60000) -- Check every minute local currentTime = GetGameTimer() local timeoutBills = {} for billId, bill in pairs(pendingBills) do -- If bill is older than 5 minutes, consider it timed out if (currentTime - bill.timestamp) > 300000 then table.insert(timeoutBills, billId) end end -- Clean up timed out bills for _, billId in ipairs(timeoutBills) do lib.notify({ title = 'Rechnung Timeout', description = 'Keine Antwort von ' .. pendingBills[billId].player.name .. ' erhalten', type = 'warning' }) pendingBills[billId] = nil end end end)