1
0
Fork 0
forked from Simnation/Main
Main/resources/[tools]/nordi_billing/billing_client.lua

751 lines
25 KiB
Lua
Raw Normal View History

2025-08-05 11:16:01 +02:00
-- Initialize QBCore
2025-08-05 11:15:04 +02:00
local QBCore = exports['qb-core']:GetCoreObject()
2025-08-05 11:11:48 +02:00
local isUiOpen = false
local cooldown = false
2025-08-05 15:29:57 +02:00
local pendingBills = {}
2025-08-05 11:11:48 +02:00
2025-08-05 11:19:51 +02:00
-- Command to open main billing menu
2025-08-05 11:11:48 +02:00
RegisterCommand('bill', function()
2025-08-05 11:19:51 +02:00
OpenMainBillingMenu()
2025-08-05 11:11:48 +02:00
end, false)
-- Keybind registration
RegisterKeyMapping('bill', 'Open Billing Menu', 'keyboard', 'F7') -- Default key is F7, can be changed in settings
2025-08-05 11:19:51 +02:00
-- Function to open the main billing menu
function OpenMainBillingMenu()
lib.registerContext({
id = 'main_billing_menu',
title = 'Billing System',
options = {
{
2025-08-05 11:23:12 +02:00
title = 'Rechnung erstellen',
description = 'Sende eine Rechnung an einen Spieler in der Nähe',
2025-08-05 11:19:51 +02:00
icon = 'file-invoice-dollar',
onSelect = function()
OpenCreateBillMenu()
end
},
{
2025-08-05 11:23:12 +02:00
title = 'Meine Rechnungen',
description = 'Sehe & bezahle deine erhaltenen Rechnungen',
2025-08-05 11:19:51 +02:00
icon = 'money-check',
onSelect = function()
ViewBills()
end
}
}
})
lib.showContext('main_billing_menu')
end
-- Function to open the create bill menu
function OpenCreateBillMenu()
2025-08-05 11:11:48 +02:00
if cooldown then
lib.notify({
title = 'Billing System',
2025-08-05 14:07:51 +02:00
description = 'Bitte warte, bevor du das Abrechnungssystem erneut verwendest',
2025-08-05 11:11:48 +02:00
type = 'error'
})
return
end
-- Get nearby players
local players = GetNearbyPlayers()
if #players == 0 then
lib.notify({
title = 'Billing System',
2025-08-05 14:07:51 +02:00
description = 'Keine Spieler in der Nähe zum Abrechnen',
2025-08-05 11:11:48 +02:00
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',
2025-08-05 14:07:51 +02:00
label = 'Persönliches Bankkonto'
2025-08-05 11:11:48 +02:00
})
-- Add other accounts
for _, account in ipairs(accounts) do
table.insert(accountOptions, {
value = account.id,
label = account.holder .. ' - ' .. account.cardNumber
})
end
2025-08-05 11:19:51 +02:00
-- Show the list of nearby players
2025-08-05 11:11:48 +02:00
local playerOptions = {}
for _, player in ipairs(players) do
table.insert(playerOptions, {
2025-08-05 11:16:01 +02:00
title = player.name,
description = 'ID: ' .. player.id,
2025-08-05 11:19:51 +02:00
icon = 'user',
2025-08-05 11:16:01 +02:00
onSelect = function()
-- When a player is selected, show the billing form
ShowBillingForm(player, accountOptions)
end
2025-08-05 11:11:48 +02:00
})
end
2025-08-05 11:16:01 +02:00
-- Register and show the player selection menu
lib.registerContext({
id = 'nearby_players_menu',
2025-08-05 14:07:51 +02:00
title = 'Spieler zum Abrechnen auswählen',
2025-08-05 11:19:51 +02:00
menu = 'main_billing_menu', -- Add back button to main menu
2025-08-05 11:16:01 +02:00
options = playerOptions
})
lib.showContext('nearby_players_menu')
end
-- Function to show the billing form after selecting a player
function ShowBillingForm(selectedPlayer, accountOptions)
2025-08-05 14:07:51 +02:00
local input = lib.inputDialog('Rechnung erstellen für ' .. selectedPlayer.name, {
2025-08-05 11:11:48 +02:00
{
type = 'number',
2025-08-05 14:07:51 +02:00
label = 'Betrag ($)',
description = 'Gib den Rechnungsbetrag ein',
2025-08-05 11:11:48 +02:00
icon = 'dollar-sign',
required = true,
min = 1,
max = 100000
},
{
type = 'input',
2025-08-05 14:07:51 +02:00
label = 'Grund',
description = 'Gib den Grund für die Rechnung ein',
placeholder = 'Erbrachte Dienstleistungen...',
2025-08-05 11:11:48 +02:00
required = true
},
{
type = 'select',
2025-08-05 14:07:51 +02:00
label = 'Empfangskonto',
2025-08-05 11:11:48 +02:00
options = accountOptions,
required = true
}
})
if not input then return end
-- Play animation
PlayBillingAnimation()
-- Send the bill
2025-08-05 15:29:57 +02:00
local billData = lib.callback.await('billing:server:sendBill', false, {
2025-08-05 11:16:01 +02:00
playerId = selectedPlayer.id,
amount = input[1],
reason = input[2],
account = input[3]
2025-08-05 11:11:48 +02:00
})
2025-08-05 15:29:57 +02:00
if billData and billData.success then
2025-08-05 11:11:48 +02:00
lib.notify({
title = 'Billing System',
2025-08-05 14:07:51 +02:00
description = 'Rechnung erfolgreich an ' .. selectedPlayer.name .. ' gesendet',
2025-08-05 11:11:48 +02:00
type = 'success'
})
-- Set cooldown
cooldown = true
SetTimeout(5000, function()
cooldown = false
end)
2025-08-05 11:19:51 +02:00
2025-08-05 15:29:57 +02:00
-- Add to pending bills
pendingBills[billData.billId] = {
player = selectedPlayer,
amount = input[1],
reason = input[2],
account = input[3],
timestamp = GetGameTimer()
}
2025-08-05 15:48:31 +02:00
-- Show waiting screen with all details
ShowWaitingScreen(billData.billId, selectedPlayer.name, input[1], input[2])
2025-08-05 11:11:48 +02:00
else
lib.notify({
title = 'Billing System',
2025-08-05 14:07:51 +02:00
description = 'Fehler beim Senden der Rechnung',
2025-08-05 11:11:48 +02:00
type = 'error'
})
2025-08-05 15:29:57 +02:00
-- Return to main menu
OpenMainBillingMenu()
2025-08-05 11:11:48 +02:00
end
end
2025-08-05 15:29:57 +02:00
-- Function to show waiting screen
2025-08-05 15:48:31 +02:00
function ShowWaitingScreen(billId, playerName, amount, reason)
-- Create a more informative waiting screen
2025-08-05 15:29:57 +02:00
lib.registerContext({
id = 'bill_waiting_screen',
2025-08-05 15:48:31 +02:00
title = 'Warte auf Zahlung',
2025-08-05 15:29:57 +02:00
options = {
{
2025-08-05 15:48:31 +02:00
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',
2025-08-05 15:29:57 +02:00
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')
2025-08-05 15:48:31 +02:00
-- 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
2025-08-05 15:29:57 +02:00
end
2025-08-05 14:27:19 +02:00
-- 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
2025-08-05 11:11:48 +02:00
-- Function to view received bills
function ViewBills()
2025-08-05 15:02:32 +02:00
-- Get all bills from ps-banking
2025-08-05 11:11:48 +02:00
local bills = lib.callback.await('ps-banking:server:getBills', false)
if not bills or #bills == 0 then
lib.notify({
title = 'Billing System',
2025-08-05 14:07:51 +02:00
description = 'Du hast keine unbezahlten Rechnungen',
2025-08-05 11:11:48 +02:00
type = 'info'
})
2025-08-05 11:19:51 +02:00
-- Return to main menu after a short delay
SetTimeout(1000, function()
OpenMainBillingMenu()
end)
2025-08-05 11:11:48 +02:00
return
end
2025-08-05 15:02:32 +02:00
-- 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
2025-08-05 11:11:48 +02:00
local billOptions = {}
for _, bill in ipairs(bills) do
local timestamp = os.date('%Y-%m-%d %H:%M', bill.date)
2025-08-05 14:07:51 +02:00
-- Check if bill is declined in our custom table
2025-08-05 15:02:32 +02:00
local billStatus = statusMap[bill.id]
local isDeclined = billStatus and billStatus.declined == 1
2025-08-05 14:07:51 +02:00
local status = 'Unbezahlt'
if bill.isPaid then
status = 'Bezahlt'
elseif isDeclined then
status = 'Abgelehnt'
end
2025-08-05 11:11:48 +02:00
table.insert(billOptions, {
title = bill.description,
2025-08-05 14:07:51 +02:00
description = ('Betrag: $%s | Datum: %s'):format(bill.amount, timestamp),
2025-08-05 11:19:51 +02:00
icon = 'file-invoice',
2025-08-05 11:11:48 +02:00
metadata = {
2025-08-05 14:07:51 +02:00
{label = 'Rechnungs-ID', value = bill.id},
{label = 'Typ', value = bill.type},
{label = 'Status', value = status}
2025-08-05 11:11:48 +02:00
},
onSelect = function()
2025-08-05 14:07:51 +02:00
if not bill.isPaid and not isDeclined then
2025-08-05 14:27:19 +02:00
-- Show account selection for payment
ShowPaymentAccountSelection(bill.id, bill.amount, bill.description)
2025-08-05 11:11:48 +02:00
end
end
})
end
lib.registerContext({
id = 'billing_menu',
2025-08-05 14:07:51 +02:00
title = 'Deine Rechnungen',
2025-08-05 11:19:51 +02:00
menu = 'main_billing_menu', -- Add back button to main menu
2025-08-05 11:11:48 +02:00
options = billOptions
})
lib.showContext('billing_menu')
end
2025-08-05 15:51:14 +02:00
2025-08-05 11:11:48 +02:00
-- Helper function to get nearby players
function GetNearbyPlayers()
local playerPed = PlayerPedId()
2025-08-05 11:16:01 +02:00
local playerCoords = GetEntityCoords(playerPed)
local players = {}
2025-08-05 11:11:48 +02:00
2025-08-05 11:16:01 +02:00
-- Get all players
local allPlayers = GetActivePlayers()
for _, player in ipairs(allPlayers) do
2025-08-05 11:11:48 +02:00
local targetPed = GetPlayerPed(player)
local targetCoords = GetEntityCoords(targetPed)
2025-08-05 11:16:01 +02:00
local distance = #(playerCoords - targetCoords)
2025-08-05 11:11:48 +02:00
if distance <= 5.0 and targetPed ~= playerPed then
local targetId = GetPlayerServerId(player)
local targetName = GetPlayerName(player)
2025-08-05 11:16:01 +02:00
table.insert(players, {id = targetId, name = targetName})
2025-08-05 11:11:48 +02:00
end
end
2025-08-05 11:16:01 +02:00
return players
2025-08-05 11:11:48 +02:00
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
2025-08-05 14:07:51 +02:00
-- 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)
2025-08-05 14:27:19 +02:00
-- 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'
})
2025-08-05 15:29:57 +02:00
-- Notify the sender
TriggerServerEvent('billing:server:notifyBillSender', data.billId, 'pay')
2025-08-05 14:27:19 +02:00
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'
})
2025-08-05 15:29:57 +02:00
-- Notify the sender
TriggerServerEvent('billing:server:notifyBillSender', data.billId, 'pay')
2025-08-05 14:27:19 +02:00
else
lib.notify({
title = 'Zahlung fehlgeschlagen',
description = 'Nicht genug Geld auf diesem Konto',
type = 'error'
})
end
end
})
end
2025-08-05 14:07:51 +02:00
-- 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',
2025-08-05 14:27:19 +02:00
description = 'Wähle ein Konto zum Bezahlen',
2025-08-05 14:07:51 +02:00
icon = 'money-bill',
2025-08-05 14:27:19 +02:00
menu = 'payment_account_selection'
2025-08-05 14:07:51 +02:00
},
{
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'
})
2025-08-05 15:29:57 +02:00
-- Notify the sender
TriggerServerEvent('billing:server:notifyBillSender', data.billId, 'decline')
2025-08-05 15:06:44 +02:00
else
lib.notify({
title = 'Fehler',
description = 'Fehler beim Ablehnen der Rechnung',
type = 'error'
})
2025-08-05 14:07:51 +02:00
end
end
end
},
{
title = 'Später bezahlen',
description = 'Rechnung für später speichern',
icon = 'clock',
onSelect = function()
2025-08-05 15:06:44 +02:00
local success = lib.callback.await('billing:server:handleBillResponse', false, {
2025-08-05 14:07:51 +02:00
action = 'later',
billId = data.billId
})
2025-08-05 15:06:44 +02:00
if success then
lib.notify({
title = 'Rechnung gespeichert',
description = 'Die Rechnung wurde für später gespeichert',
type = 'info'
})
2025-08-05 15:29:57 +02:00
-- Notify the sender
TriggerServerEvent('billing:server:notifyBillSender', data.billId, 'later')
2025-08-05 15:06:44 +02:00
else
lib.notify({
title = 'Fehler',
description = 'Fehler beim Speichern der Rechnung',
type = 'error'
})
end
2025-08-05 14:07:51 +02:00
end
}
}
})
2025-08-05 14:27:19 +02:00
-- Register the account selection submenu
lib.registerContext({
id = 'payment_account_selection',
title = 'Wähle Zahlungskonto',
menu = 'bill_payment_prompt',
options = accountOptions
})
2025-08-05 14:07:51 +02:00
-- Show the payment prompt
lib.showContext('bill_payment_prompt')
end)
2025-08-05 15:06:44 +02:00
2025-08-05 15:29:57 +02:00
-- 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',
2025-08-05 15:48:31 +02:00
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
2025-08-05 15:29:57 +02:00
})
elseif action == 'decline' then
lib.notify({
title = 'Rechnung abgelehnt',
description = playerName .. ' hat deine Rechnung über $' .. bill.amount .. ' abgelehnt',
2025-08-05 15:48:31 +02:00
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
2025-08-05 15:29:57 +02:00
})
elseif action == 'later' then
lib.notify({
title = 'Rechnung gespeichert',
description = playerName .. ' wird deine Rechnung später bezahlen',
2025-08-05 15:48:31 +02:00
type = 'info',
duration = 7000
})
-- Show a more detailed "pay later" message
lib.alertDialog({
header = 'Zahlung aufgeschoben',
content = playerName .. ' hat deine Rechnung über $' .. bill.amount .. ' für "' .. bill.reason .. '" für später gespeichert.',
centered = true,
cancel = false
2025-08-05 15:29:57 +02:00
})
end
-- Remove from pending bills
pendingBills[billId] = nil
-- Return to main menu
OpenMainBillingMenu()
end
end)
-- Event handler for opening bills menu from command
2025-08-05 15:06:44 +02:00
RegisterNetEvent('billing:client:openBillsMenu', function()
ViewBills()
end)
2025-08-05 15:29:57 +02:00
-- 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)