From 12740ce5d5638375eb02569e624d30d208615879 Mon Sep 17 00:00:00 2001 From: Nordi98 Date: Tue, 5 Aug 2025 14:07:51 +0200 Subject: [PATCH] ed --- .../[tools]/nordi_billing/billing_client.lua | 191 ++++++++++++++---- .../[tools]/nordi_billing/billing_server.lua | 112 ++++++++-- 2 files changed, 246 insertions(+), 57 deletions(-) diff --git a/resources/[tools]/nordi_billing/billing_client.lua b/resources/[tools]/nordi_billing/billing_client.lua index efa8f9812..084f11e47 100644 --- a/resources/[tools]/nordi_billing/billing_client.lua +++ b/resources/[tools]/nordi_billing/billing_client.lua @@ -45,7 +45,7 @@ function OpenCreateBillMenu() if cooldown then lib.notify({ title = 'Billing System', - description = 'Please wait before using the billing system again', + description = 'Bitte warte, bevor du das Abrechnungssystem erneut verwendest', type = 'error' }) return @@ -56,7 +56,7 @@ function OpenCreateBillMenu() if #players == 0 then lib.notify({ title = 'Billing System', - description = 'No players nearby to bill', + description = 'Keine Spieler in der Nähe zum Abrechnen', type = 'error' }) return @@ -69,7 +69,7 @@ function OpenCreateBillMenu() -- Add default bank account table.insert(accountOptions, { value = 'personal', - label = 'Personal Bank Account' + label = 'Persönliches Bankkonto' }) -- Add other accounts @@ -97,7 +97,7 @@ function OpenCreateBillMenu() -- Register and show the player selection menu lib.registerContext({ id = 'nearby_players_menu', - title = 'Select Player to Bill', + title = 'Spieler zum Abrechnen auswählen', menu = 'main_billing_menu', -- Add back button to main menu options = playerOptions }) @@ -107,11 +107,11 @@ end -- Function to show the billing form after selecting a player function ShowBillingForm(selectedPlayer, accountOptions) - local input = lib.inputDialog('Create Bill for ' .. selectedPlayer.name, { + local input = lib.inputDialog('Rechnung erstellen für ' .. selectedPlayer.name, { { type = 'number', - label = 'Amount ($)', - description = 'Enter the bill amount', + label = 'Betrag ($)', + description = 'Gib den Rechnungsbetrag ein', icon = 'dollar-sign', required = true, min = 1, @@ -119,14 +119,14 @@ function ShowBillingForm(selectedPlayer, accountOptions) }, { type = 'input', - label = 'Reason', - description = 'Enter the reason for the bill', - placeholder = 'Services provided...', + label = 'Grund', + description = 'Gib den Grund für die Rechnung ein', + placeholder = 'Erbrachte Dienstleistungen...', required = true }, { type = 'select', - label = 'Receiving Account', + label = 'Empfangskonto', options = accountOptions, required = true } @@ -148,7 +148,7 @@ function ShowBillingForm(selectedPlayer, accountOptions) if success then lib.notify({ title = 'Billing System', - description = 'Bill sent successfully to ' .. selectedPlayer.name, + description = 'Rechnung erfolgreich an ' .. selectedPlayer.name .. ' gesendet', type = 'success' }) @@ -163,7 +163,7 @@ function ShowBillingForm(selectedPlayer, accountOptions) else lib.notify({ title = 'Billing System', - description = 'Failed to send bill', + description = 'Fehler beim Senden der Rechnung', type = 'error' }) end @@ -176,7 +176,7 @@ function ViewBills() if not bills or #bills == 0 then lib.notify({ title = 'Billing System', - description = 'You have no unpaid bills', + description = 'Du hast keine unbezahlten Rechnungen', type = 'info' }) @@ -190,38 +190,52 @@ function ViewBills() 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 billData = lib.callback.await('billing:server:getBillStatus', false, bill.id) + local isDeclined = billData and billData.declined == 1 + + local status = 'Unbezahlt' + if bill.isPaid then + status = 'Bezahlt' + elseif isDeclined then + status = 'Abgelehnt' + end + table.insert(billOptions, { title = bill.description, - description = ('Amount: $%s | Date: %s'):format(bill.amount, timestamp), + description = ('Betrag: $%s | Datum: %s'):format(bill.amount, timestamp), icon = 'file-invoice', metadata = { - {label = 'Bill ID', value = bill.id}, - {label = 'Type', value = bill.type}, - {label = 'Status', value = bill.isPaid and 'Paid' or 'Unpaid'} + {label = 'Rechnungs-ID', value = bill.id}, + {label = 'Typ', value = bill.type}, + {label = 'Status', value = status} }, onSelect = function() - local confirm = lib.alertDialog({ - header = 'Pay Bill', - content = ('Do you want to pay $%s for %s?'):format(bill.amount, bill.description), - centered = true, - cancel = true - }) - - if confirm == 'confirm' then - local success = lib.callback.await('ps-banking:server:payBill', false, bill.id) - if success then - lib.notify({ - title = 'Billing System', - description = 'Bill paid successfully', - type = 'success' - }) - ViewBills() -- Refresh the list - else - lib.notify({ - title = 'Billing System', - description = 'Failed to pay bill. Insufficient funds.', - type = 'error' - }) + if not bill.isPaid and not isDeclined then + local confirm = lib.alertDialog({ + header = 'Rechnung bezahlen', + content = ('Möchtest du $%s für %s bezahlen?'):format(bill.amount, bill.description), + centered = true, + cancel = true + }) + + if confirm == 'confirm' then + local success = lib.callback.await('ps-banking:server:payBill', false, bill.id) + 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 end @@ -230,7 +244,7 @@ function ViewBills() lib.registerContext({ id = 'billing_menu', - title = 'Your Bills', + title = 'Deine Rechnungen', menu = 'main_billing_menu', -- Add back button to main menu options = billOptions }) @@ -290,3 +304,98 @@ function PlayBillingAnimation() 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) + + -- 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 = 'Rechnung sofort bezahlen', + icon = 'money-bill', + onSelect = function() + local success = lib.callback.await('billing:server:handleBillResponse', false, { + action = 'pay', + billId = data.billId + }) + + if success then + lib.notify({ + title = 'Rechnung bezahlt', + description = 'Du hast die Rechnung erfolgreich bezahlt', + type = 'success' + }) + else + lib.notify({ + title = 'Zahlung fehlgeschlagen', + description = 'Du hast nicht genug Geld auf deinem Konto', + type = 'error' + }) + end + end + }, + { + 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' + }) + end + end + end + }, + { + title = 'Später bezahlen', + description = 'Rechnung für später speichern', + icon = 'clock', + onSelect = function() + lib.callback.await('billing:server:handleBillResponse', false, { + action = 'later', + billId = data.billId + }) + + lib.notify({ + title = 'Rechnung gespeichert', + description = 'Die Rechnung wurde für später gespeichert', + type = 'info' + }) + end + } + } + }) + + -- Show the payment prompt + lib.showContext('bill_payment_prompt') +end) diff --git a/resources/[tools]/nordi_billing/billing_server.lua b/resources/[tools]/nordi_billing/billing_server.lua index 3f0da91c9..60fc19a6f 100644 --- a/resources/[tools]/nordi_billing/billing_server.lua +++ b/resources/[tools]/nordi_billing/billing_server.lua @@ -1,3 +1,6 @@ +-- Initialize QBCore +local QBCore = exports['qb-core']:GetCoreObject() + -- Register callback for sending bills lib.callback.register('billing:server:sendBill', function(source, data) local src = source @@ -9,7 +12,7 @@ lib.callback.register('billing:server:sendBill', function(source, data) end local senderName = player.PlayerData.charinfo.firstname .. ' ' .. player.PlayerData.charinfo.lastname - local description = data.reason .. ' (From: ' .. senderName .. ')' + local description = data.reason .. ' (Von: ' .. senderName .. ')' -- Create the bill exports["ps-banking"]:createBill({ @@ -19,9 +22,16 @@ lib.callback.register('billing:server:sendBill', function(source, data) amount = data.amount, }) - -- Store additional data about the bill if needed - -- You could create a separate table to track which account the payment should go to - MySQL.insert.await('INSERT INTO billing_accounts (bill_description, sender_id, receiver_id, account_id, amount, created_at) VALUES (?, ?, ?, ?, ?, NOW())', { + -- Get the latest bill ID for this user (assuming it's the one we just created) + local result = MySQL.query.await('SELECT id FROM ps_banking_bills WHERE identifier = ? ORDER BY id DESC LIMIT 1', { + target.PlayerData.citizenid + }) + + local billId = result[1] and result[1].id or nil + + -- Store additional data about the bill + MySQL.insert.await('INSERT INTO billing_accounts (bill_id, bill_description, sender_id, receiver_id, account_id, amount, created_at) VALUES (?, ?, ?, ?, ?, ?, NOW())', { + billId, description, player.PlayerData.citizenid, target.PlayerData.citizenid, @@ -29,18 +39,77 @@ lib.callback.register('billing:server:sendBill', function(source, data) data.amount }) - -- Notify the target player - TriggerClientEvent('QBCore:Notify', data.playerId, 'You received a bill for $' .. data.amount .. ' - ' .. data.reason, 'primary', 7500) + -- Send payment prompt to target player + TriggerClientEvent('billing:client:showPaymentPrompt', data.playerId, { + billId = billId, + amount = data.amount, + reason = data.reason, + sender = senderName + }) return true end) --- Event handler for when a bill is paid -RegisterNetEvent('billing:server:billPaid', function(billId) +-- Add a callback to get bill status +lib.callback.register('billing:server:getBillStatus', function(source, billId) local src = source local player = QBCore.Functions.GetPlayer(src) - if not player then return end + if not player then return nil end + + local result = MySQL.query.await('SELECT * FROM billing_accounts WHERE bill_id = ?', {billId}) + if result and #result > 0 then + return result[1] + end + + return nil +end) + +-- Add a new callback for handling bill responses +lib.callback.register('billing:server:handleBillResponse', function(source, data) + local src = source + local player = QBCore.Functions.GetPlayer(src) + + if not player then return false end + + if data.action == 'pay' then + -- Process payment + local success = lib.callback.await('ps-banking:server:payBill', src, data.billId) + + if success then + -- Payment successful + return true + else + -- Payment failed (likely insufficient funds) + return false + end + elseif data.action == 'decline' then + -- Mark as declined in our system + MySQL.update.await('UPDATE billing_accounts SET declined = 1 WHERE bill_id = ?', {data.billId}) + + -- Find the sender to notify them + local billInfo = MySQL.query.await('SELECT * FROM billing_accounts WHERE bill_id = ?', {data.billId}) + if billInfo and #billInfo > 0 then + local senderId = billInfo[1].sender_id + local sender = QBCore.Functions.GetPlayerByCitizenId(senderId) + + if sender then + TriggerClientEvent('QBCore:Notify', sender.PlayerData.source, 'Deine Rechnung wurde abgelehnt', 'error') + end + end + + return true + elseif data.action == 'later' then + -- No action needed, bill remains in system + return true + end + + return false +end) + +-- Event handler for when a bill is paid +RegisterNetEvent('ps-banking:server:billPaid', function(billId) + local src = source -- Find the bill in our custom table local billData = MySQL.query.await('SELECT * FROM billing_accounts WHERE bill_id = ?', {billId}) @@ -57,13 +126,13 @@ RegisterNetEvent('billing:server:billPaid', function(billId) if receiver then -- Add money directly to the receiver's bank account receiver.Functions.AddMoney('bank', amount, 'bill-payment') - TriggerClientEvent('QBCore:Notify', receiver.PlayerData.source, 'You received $' .. amount .. ' from a bill payment', 'success') + TriggerClientEvent('QBCore:Notify', receiver.PlayerData.source, 'Du hast $' .. amount .. ' von einer Rechnungszahlung erhalten', 'success') else - -- Handle offline player (could store in a separate table for when they log in) + -- Handle offline player MySQL.insert.await('INSERT INTO offline_payments (citizen_id, amount, reason) VALUES (?, ?, ?)', { receiverId, amount, - 'Bill payment' + 'Rechnungszahlung' }) end else @@ -92,6 +161,7 @@ MySQL.ready(function() amount DECIMAL(10,2), created_at DATETIME, paid TINYINT DEFAULT 0, + declined TINYINT DEFAULT 0, paid_at DATETIME ) ]]) @@ -127,7 +197,7 @@ RegisterNetEvent('QBCore:Server:PlayerLoaded', function() if totalAmount > 0 then player.Functions.AddMoney('bank', totalAmount, 'offline-bill-payments') - TriggerClientEvent('QBCore:Notify', src, 'You received $' .. totalAmount .. ' from bill payments while offline', 'success') + TriggerClientEvent('QBCore:Notify', src, 'Du hast $' .. totalAmount .. ' von Rechnungszahlungen erhalten, während du offline warst', 'success') -- Mark payments as processed MySQL.update.await('UPDATE offline_payments SET processed = 1 WHERE citizen_id = ? AND processed = 0', {citizenId}) @@ -135,7 +205,17 @@ RegisterNetEvent('QBCore:Server:PlayerLoaded', function() end end) --- Listen for bill payment events from ps-banking -RegisterNetEvent('ps-banking:server:billPaid', function(billId) - TriggerEvent('billing:server:billPaid', billId) +-- Alternative hook method if you can't modify ps-banking +-- This listens for the MySQL query that deletes a bill (which happens when a bill is paid) +AddEventHandler('oxmysql:query', function(query, params) + if string.find(query, "DELETE FROM ps_banking_bills WHERE id = ?") then + -- This is likely a bill payment + local billId = params[1] + if billId then + -- Small delay to ensure the deletion completes + SetTimeout(100, function() + TriggerEvent('ps-banking:server:billPaid', billId) + end) + end + end end)