2025-08-05 14:07:51 +02:00
-- Initialize QBCore
local QBCore = exports [ ' qb-core ' ] : GetCoreObject ( )
2025-08-05 11:11:48 +02:00
-- Register callback for sending bills
lib.callback . register ( ' billing:server:sendBill ' , function ( source , data )
local src = source
local player = QBCore.Functions . GetPlayer ( src )
local target = QBCore.Functions . GetPlayer ( data.playerId )
if not player or not target then
return false
end
local senderName = player.PlayerData . charinfo.firstname .. ' ' .. player.PlayerData . charinfo.lastname
2025-08-05 14:07:51 +02:00
local description = data.reason .. ' (Von: ' .. senderName .. ' ) '
2025-08-05 11:11:48 +02:00
-- Create the bill
exports [ " ps-banking " ] : createBill ( {
identifier = target.PlayerData . citizenid ,
description = description ,
type = " Invoice " ,
amount = data.amount ,
} )
2025-08-05 14:07:51 +02:00
-- 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 ,
2025-08-05 11:11:48 +02:00
description ,
player.PlayerData . citizenid ,
target.PlayerData . citizenid ,
data.account ,
data.amount
} )
2025-08-05 14:07:51 +02:00
-- Send payment prompt to target player
TriggerClientEvent ( ' billing:client:showPaymentPrompt ' , data.playerId , {
billId = billId ,
amount = data.amount ,
reason = data.reason ,
sender = senderName
} )
2025-08-05 11:11:48 +02:00
2025-08-05 15:06:44 +02:00
-- Notify the sender
TriggerClientEvent ( ' QBCore:Notify ' , src , ' Rechnung über $ ' .. data.amount .. ' an ' .. target.PlayerData . charinfo.firstname .. ' gesendet ' , ' success ' )
2025-08-05 11:11:48 +02:00
return true
end )
2025-08-05 14:07:51 +02:00
-- Add a callback to get bill status
lib.callback . register ( ' billing:server:getBillStatus ' , function ( source , billId )
2025-08-05 11:11:48 +02:00
local src = source
local player = QBCore.Functions . GetPlayer ( src )
2025-08-05 14:07:51 +02:00
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 )
2025-08-05 15:02:32 +02:00
-- Add a callback to get all bill statuses for a player
lib.callback . register ( ' billing:server:getAllBillStatuses ' , function ( source )
local src = source
local player = QBCore.Functions . GetPlayer ( src )
if not player then return nil end
local citizenId = player.PlayerData . citizenid
-- Get all bill statuses for this player
local result = MySQL.query . await ( ' SELECT * FROM billing_accounts WHERE receiver_id = ? ' , { citizenId } )
if result and # result > 0 then
return result
end
return { }
end )
2025-08-05 14:07:51 +02:00
-- 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
2025-08-05 14:27:19 +02:00
-- Process payment based on selected account
if data.accountId == ' personal ' then
2025-08-05 15:08:53 +02:00
-- Pay from personal bank account
2025-08-05 14:43:36 +02:00
local billResult = MySQL.query . await ( ' SELECT * FROM ps_banking_bills WHERE id = ? ' , { data.billId } )
if not billResult or # billResult == 0 then return false end
local bill = billResult [ 1 ]
local amount = tonumber ( bill.amount )
-- Check if player has enough money in their bank account
if player.PlayerData . money [ " bank " ] < amount then
return false
end
-- Process payment manually instead of using callback to avoid issues
player.Functions . RemoveMoney ( " bank " , amount , " bill-payment " )
2025-08-05 15:08:53 +02:00
-- IMPORTANT: Delete the bill from ps_banking_bills table
2025-08-05 14:43:36 +02:00
MySQL.query . await ( ' DELETE FROM ps_banking_bills WHERE id = ? ' , { data.billId } )
-- Process the payment to the recipient's account
ProcessBillPayment ( data.billId )
2025-08-05 15:06:44 +02:00
-- Notify the player
TriggerClientEvent ( ' QBCore:Notify ' , src , ' Du hast die Rechnung über $ ' .. amount .. ' bezahlt ' , ' success ' )
2025-08-05 14:43:36 +02:00
return true
2025-08-05 14:07:51 +02:00
else
2025-08-05 14:27:19 +02:00
-- Pay from shared account
local success = PayBillFromSharedAccount ( src , data.billId , data.accountId )
return success
2025-08-05 14:07:51 +02:00
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 } )
2025-08-05 15:08:53 +02:00
-- IMPORTANT: Delete the bill from ps_banking_bills table when declined
MySQL.query . await ( ' DELETE FROM ps_banking_bills WHERE id = ? ' , { data.billId } )
2025-08-05 14:07:51 +02:00
-- 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
2025-08-05 15:08:53 +02:00
end
2025-08-05 15:06:44 +02:00
-- Notify the player
TriggerClientEvent ( ' QBCore:Notify ' , src , ' Du hast die Rechnung abgelehnt ' , ' info ' )
2025-08-05 14:07:51 +02:00
return true
elseif data.action == ' later ' then
2025-08-05 15:02:32 +02:00
-- Simply close the prompt without any action
-- The bill will remain in the system as unpaid
2025-08-05 15:06:44 +02:00
-- Notify the player
TriggerClientEvent ( ' QBCore:Notify ' , src , ' Die Rechnung wurde für später gespeichert ' , ' info ' )
2025-08-05 14:07:51 +02:00
return true
end
return false
end )
2025-08-05 14:27:19 +02:00
-- Function to pay bill from a shared account
function PayBillFromSharedAccount ( source , billId , accountId )
2025-08-05 14:07:51 +02:00
local src = source
2025-08-05 14:27:19 +02:00
local player = QBCore.Functions . GetPlayer ( src )
if not player then return false end
-- Get bill details
local billResult = MySQL.query . await ( ' SELECT * FROM ps_banking_bills WHERE id = ? ' , { billId } )
if not billResult or # billResult == 0 then return false end
local bill = billResult [ 1 ]
2025-08-05 14:43:36 +02:00
local amount = tonumber ( bill.amount )
2025-08-05 14:27:19 +02:00
-- Get account details
local accountResult = MySQL.query . await ( ' SELECT * FROM ps_banking_accounts WHERE id = ? ' , { accountId } )
if not accountResult or # accountResult == 0 then return false end
local account = accountResult [ 1 ]
-- Check if player has access to this account
local hasAccess = false
local accountOwner = json.decode ( account.owner )
local accountUsers = json.decode ( account.users )
if accountOwner.identifier == player.PlayerData . citizenid then
hasAccess = true
else
for _ , user in ipairs ( accountUsers ) do
if user.identifier == player.PlayerData . citizenid then
hasAccess = true
break
end
end
end
2025-08-05 11:11:48 +02:00
2025-08-05 15:06:44 +02:00
if not hasAccess then
TriggerClientEvent ( ' QBCore:Notify ' , src , ' Du hast keinen Zugriff auf dieses Konto ' , ' error ' )
return false
end
2025-08-05 14:27:19 +02:00
-- Check if account has enough balance
2025-08-05 15:06:44 +02:00
if tonumber ( account.balance ) < amount then
TriggerClientEvent ( ' QBCore:Notify ' , src , ' Nicht genug Geld auf diesem Konto ' , ' error ' )
return false
end
2025-08-05 14:27:19 +02:00
-- Process payment
MySQL.update . await ( ' UPDATE ps_banking_accounts SET balance = balance - ? WHERE id = ? ' , { amount , accountId } )
2025-08-05 15:08:53 +02:00
-- IMPORTANT: Delete the bill from ps_banking_bills table
2025-08-05 14:27:19 +02:00
MySQL.query . await ( ' DELETE FROM ps_banking_bills WHERE id = ? ' , { billId } )
-- Process the payment to the recipient's account
ProcessBillPayment ( billId )
2025-08-05 15:06:44 +02:00
-- Notify the player
TriggerClientEvent ( ' QBCore:Notify ' , src , ' Du hast die Rechnung über $ ' .. amount .. ' vom Konto ' .. account.holder .. ' bezahlt ' , ' success ' )
2025-08-05 14:27:19 +02:00
return true
end
-- Add a callback for paying bill from selected account
lib.callback . register ( ' billing:server:payBillFromAccount ' , function ( source , data )
local src = source
2025-08-05 14:43:36 +02:00
local player = QBCore.Functions . GetPlayer ( src )
if not player then return false end
2025-08-05 14:27:19 +02:00
if data.accountId == ' personal ' then
-- Pay from personal bank account
2025-08-05 14:43:36 +02:00
local billResult = MySQL.query . await ( ' SELECT * FROM ps_banking_bills WHERE id = ? ' , { data.billId } )
if not billResult or # billResult == 0 then return false end
local bill = billResult [ 1 ]
local amount = tonumber ( bill.amount )
-- Check if player has enough money in their bank account
if player.PlayerData . money [ " bank " ] < amount then
2025-08-05 15:06:44 +02:00
TriggerClientEvent ( ' QBCore:Notify ' , src , ' Nicht genug Geld auf deinem Konto ' , ' error ' )
2025-08-05 14:43:36 +02:00
return false
end
-- Process payment manually
player.Functions . RemoveMoney ( " bank " , amount , " bill-payment " )
2025-08-05 15:08:53 +02:00
-- IMPORTANT: Delete the bill from ps_banking_bills table
2025-08-05 14:43:36 +02:00
MySQL.query . await ( ' DELETE FROM ps_banking_bills WHERE id = ? ' , { data.billId } )
-- Process the payment to the recipient's account
ProcessBillPayment ( data.billId )
2025-08-05 15:06:44 +02:00
-- Notify the player
TriggerClientEvent ( ' QBCore:Notify ' , src , ' Du hast die Rechnung über $ ' .. amount .. ' bezahlt ' , ' success ' )
2025-08-05 14:43:36 +02:00
return true
2025-08-05 14:27:19 +02:00
else
-- Pay from shared account
return PayBillFromSharedAccount ( src , data.billId , data.accountId )
end
end )
-- Function to process bill payment to recipient
function ProcessBillPayment ( billId )
2025-08-05 11:11:48 +02:00
-- Find the bill in our custom table
local billData = MySQL.query . await ( ' SELECT * FROM billing_accounts WHERE bill_id = ? ' , { billId } )
if billData and # billData > 0 then
local bill = billData [ 1 ]
local receiverId = bill.sender_id
local accountId = bill.account_id
2025-08-05 14:43:36 +02:00
local amount = tonumber ( bill.amount )
2025-08-05 11:11:48 +02:00
-- If it's a personal account
if accountId == ' personal ' then
local receiver = QBCore.Functions . GetPlayerByCitizenId ( receiverId )
if receiver then
-- Add money directly to the receiver's bank account
receiver.Functions . AddMoney ( ' bank ' , amount , ' bill-payment ' )
2025-08-05 14:07:51 +02:00
TriggerClientEvent ( ' QBCore:Notify ' , receiver.PlayerData . source , ' Du hast $ ' .. amount .. ' von einer Rechnungszahlung erhalten ' , ' success ' )
2025-08-05 11:11:48 +02:00
else
2025-08-05 14:07:51 +02:00
-- Handle offline player
2025-08-05 11:11:48 +02:00
MySQL.insert . await ( ' INSERT INTO offline_payments (citizen_id, amount, reason) VALUES (?, ?, ?) ' , {
receiverId ,
amount ,
2025-08-05 14:07:51 +02:00
' Rechnungszahlung '
2025-08-05 11:11:48 +02:00
} )
end
else
-- Add money to the specified account
MySQL.update . await ( ' UPDATE ps_banking_accounts SET balance = balance + ? WHERE id = ? ' , {
amount ,
accountId
} )
2025-08-05 15:06:44 +02:00
-- Try to notify the account owner if online
local accountResult = MySQL.query . await ( ' SELECT * FROM ps_banking_accounts WHERE id = ? ' , { accountId } )
if accountResult and # accountResult > 0 then
local account = accountResult [ 1 ]
local owner = json.decode ( account.owner )
local ownerPlayer = QBCore.Functions . GetPlayerByCitizenId ( owner.identifier )
if ownerPlayer then
TriggerClientEvent ( ' QBCore:Notify ' , ownerPlayer.PlayerData . source , ' Das Konto ' .. account.holder .. ' hat $ ' .. amount .. ' von einer Rechnungszahlung erhalten ' , ' success ' )
end
end
2025-08-05 11:11:48 +02:00
end
-- Update the bill status
MySQL.update . await ( ' UPDATE billing_accounts SET paid = 1, paid_at = NOW() WHERE bill_id = ? ' , { billId } )
end
2025-08-05 14:27:19 +02:00
end
2025-08-05 11:11:48 +02:00
-- Create the necessary database tables if they don't exist
MySQL.ready ( function ( )
2025-08-05 15:24:24 +02:00
-- First check if the billing_accounts table exists
local tableExists = MySQL.query . await ( " SHOW TABLES LIKE 'billing_accounts' " )
2025-08-05 11:11:48 +02:00
2025-08-05 15:24:24 +02:00
if # tableExists > 0 then
-- Table exists, check if the declined column exists
local columnExists = MySQL.query . await ( " SHOW COLUMNS FROM billing_accounts LIKE 'declined' " )
if # columnExists == 0 then
-- Add the declined column if it doesn't exist
MySQL.query . await ( " ALTER TABLE billing_accounts ADD COLUMN declined TINYINT DEFAULT 0 " )
print ( " ^2[nordi_billing] Added 'declined' column to billing_accounts table^7 " )
end
else
-- Create the table with all required columns
MySQL.query . await ( [ [
CREATE TABLE IF NOT EXISTS billing_accounts (
id INT AUTO_INCREMENT PRIMARY KEY ,
bill_id INT ,
bill_description VARCHAR ( 255 ) ,
sender_id VARCHAR ( 50 ) ,
receiver_id VARCHAR ( 50 ) ,
account_id VARCHAR ( 50 ) ,
amount DECIMAL ( 10 , 2 ) ,
created_at DATETIME ,
paid TINYINT DEFAULT 0 ,
declined TINYINT DEFAULT 0 ,
paid_at DATETIME
)
] ] )
print ( " ^2[nordi_billing] Created billing_accounts table^7 " )
end
-- Create offline_payments table if it doesn't exist
2025-08-05 11:11:48 +02:00
MySQL.query . await ( [ [
CREATE TABLE IF NOT EXISTS offline_payments (
id INT AUTO_INCREMENT PRIMARY KEY ,
citizen_id VARCHAR ( 50 ) ,
amount DECIMAL ( 10 , 2 ) ,
reason VARCHAR ( 255 ) ,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ,
processed TINYINT DEFAULT 0
)
] ] )
2025-08-05 15:24:24 +02:00
print ( " ^2[nordi_billing] Database tables initialized^7 " )
end ) -- Added closing parenthesis here
2025-08-05 11:11:48 +02:00
-- Handle offline payments when a player logs in
RegisterNetEvent ( ' QBCore:Server:PlayerLoaded ' , function ( )
local src = source
local player = QBCore.Functions . GetPlayer ( src )
if not player then return end
local citizenId = player.PlayerData . citizenid
local offlinePayments = MySQL.query . await ( ' SELECT * FROM offline_payments WHERE citizen_id = ? AND processed = 0 ' , { citizenId } )
if offlinePayments and # offlinePayments > 0 then
local totalAmount = 0
for _ , payment in ipairs ( offlinePayments ) do
totalAmount = totalAmount + payment.amount
end
if totalAmount > 0 then
player.Functions . AddMoney ( ' bank ' , totalAmount , ' offline-bill-payments ' )
2025-08-05 14:07:51 +02:00
TriggerClientEvent ( ' QBCore:Notify ' , src , ' Du hast $ ' .. totalAmount .. ' von Rechnungszahlungen erhalten, während du offline warst ' , ' success ' )
2025-08-05 11:11:48 +02:00
-- Mark payments as processed
MySQL.update . await ( ' UPDATE offline_payments SET processed = 1 WHERE citizen_id = ? AND processed = 0 ' , { citizenId } )
end
end
2025-08-05 15:06:44 +02:00
-- Check for unpaid bills
local unpaidBills = MySQL.query . await ( ' SELECT COUNT(*) as count FROM ps_banking_bills WHERE identifier = ? AND isPaid = 0 ' , { citizenId } )
if unpaidBills and unpaidBills [ 1 ] . count > 0 then
TriggerClientEvent ( ' QBCore:Notify ' , src , ' Du hast ' .. unpaidBills [ 1 ] . count .. ' unbezahlte Rechnungen. Tippe /bills um sie anzuzeigen. ' , ' info ' )
end
2025-08-05 11:11:48 +02:00
end )
2025-08-05 14:07:51 +02:00
-- 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 ( )
2025-08-05 14:27:19 +02:00
ProcessBillPayment ( billId )
2025-08-05 14:07:51 +02:00
end )
end
end
2025-08-05 11:11:48 +02:00
end )
2025-08-05 15:06:44 +02:00
-- Register a command to check bills
QBCore.Commands . Add ( ' bills ' , ' Zeige deine unbezahlten Rechnungen an ' , { } , false , function ( source )
TriggerClientEvent ( ' QBCore:Notify ' , source , ' Öffne Rechnungsübersicht... ' , ' info ' )
-- This will trigger the client to open the bills menu
TriggerClientEvent ( ' billing:client:openBillsMenu ' , source )
end )
-- Event handler for opening bills menu from command
RegisterNetEvent ( ' billing:client:openBillsMenu ' , function ( )
-- This is handled on the client side
end )
-- Add a server event to notify bill sender when a bill is paid
RegisterServerEvent ( ' billing:server:notifyBillPaid ' , function ( senderId , amount , receiverName )
local sender = QBCore.Functions . GetPlayerByCitizenId ( senderId )
if sender then
TriggerClientEvent ( ' QBCore:Notify ' , sender.PlayerData . source , receiverName .. ' hat deine Rechnung über $ ' .. amount .. ' bezahlt ' , ' success ' )
end
end )
-- Add a server event to notify bill sender when a bill is declined
RegisterServerEvent ( ' billing:server:notifyBillDeclined ' , function ( senderId , amount , receiverName )
local sender = QBCore.Functions . GetPlayerByCitizenId ( senderId )
if sender then
TriggerClientEvent ( ' QBCore:Notify ' , sender.PlayerData . source , receiverName .. ' hat deine Rechnung über $ ' .. amount .. ' abgelehnt ' , ' error ' )
end
end )
2025-08-05 15:35:51 +02:00
-- 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 based on selected account
if data.accountId == ' personal ' then
-- Pay from personal bank account
local billResult = MySQL.query . await ( ' SELECT * FROM ps_banking_bills WHERE id = ? ' , { data.billId } )
if not billResult or # billResult == 0 then return false end
local bill = billResult [ 1 ]
local amount = tonumber ( bill.amount )
-- Check if player has enough money in their bank account
if player.PlayerData . money [ " bank " ] < amount then
return false
end
-- Process payment manually instead of using callback to avoid issues
player.Functions . RemoveMoney ( " bank " , amount , " bill-payment " )
-- IMPORTANT: Delete the bill from ps_banking_bills table
MySQL.query . await ( ' DELETE FROM ps_banking_bills WHERE id = ? ' , { data.billId } )
-- Process the payment to the recipient's account
ProcessBillPayment ( data.billId )
-- Notify the player
TriggerClientEvent ( ' QBCore:Notify ' , src , ' Du hast die Rechnung über $ ' .. amount .. ' bezahlt ' , ' success ' )
return true
else
-- Pay from shared account
local success = PayBillFromSharedAccount ( src , data.billId , data.accountId )
return success
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 } )
-- IMPORTANT: Delete the bill from ps_banking_bills table when declined
MySQL.query . await ( ' DELETE FROM ps_banking_bills WHERE 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
-- Notify the player
TriggerClientEvent ( ' QBCore:Notify ' , src , ' Du hast die Rechnung abgelehnt ' , ' info ' )
return true
elseif data.action == ' later ' then
-- Simply close the prompt without any action
-- The bill will remain in the system as unpaid
-- Notify the player
TriggerClientEvent ( ' QBCore:Notify ' , src , ' Die Rechnung wurde für später gespeichert ' , ' info ' )
return true
end
return false
end )