Skip to content

Commit

Permalink
feat(all): Financing
Browse files Browse the repository at this point in the history
  • Loading branch information
renzuzu committed Nov 13, 2022
1 parent ec3ce28 commit 4c67515
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 27 deletions.
64 changes: 62 additions & 2 deletions client/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1843,12 +1843,23 @@ self.Handlers = function()
for k,v in pairs(shop) do
itemdata[v.metadata and v.metadata.name or v.name] = v
end
local totalamount = 0
for k,v in pairs(data.items) do
totalamount += 1
total = total + tonumber(itemdata[v.data.metadata and v.data.metadata.name or v.data.name].price) * tonumber(v.count)
end
data.type = self.PaymentMethod({amount = total, total = totalamount, type = self.Active.shop.type}) or 'money'
local financedata
if data.type == 'finance' then
finance, financedata = self.Finance({amount = total, total = totalamount, type = self.Active.shop.type})
total = financedata.downpayment
end
if data.type == 'finance' and finance == 'cancel' then
return
end
local confirm = lib.alertDialog({
header = 'Confirm Buy',
content = 'Are you sure you want to buy? \n Amount : '..total..' $ \n Method : '..data.type,
content = 'Are you sure you want to pay? \n Amount : '..total..' $ \n Method : '..data.type,
centered = true,
cancel = true
})
Expand Down Expand Up @@ -1916,7 +1927,7 @@ self.Handlers = function()
end
cb(true)
end
end,{items = data.items, data = itemdata, index = self.Active.index, type = data.type, shop = self.Active.shop.type or self.shopidentifier, moneytype = self.moneytype})
end,{finance = financedata, items = data.items, data = itemdata, index = self.Active.index, type = data.type, shop = self.Active.shop.type or self.shopidentifier, moneytype = self.moneytype})
end
elseif data.msg == 'close' then
self.Closeui()
Expand Down Expand Up @@ -3176,6 +3187,55 @@ self.TransferOwnerShip = function(store)
lib.showContext('transfershop')
end

self.PaymentMethod = function(data)
local options = {}
local index = 3
table.insert(options,{ type = "input", label = "Total in Cart", placeholder = data.total , disabled = true})
table.insert(options,{ type = "input", label = "Amount to Pay", placeholder = data.amount , disabled = true})
local dropdownmenu = {
{ value = 'money', label = 'Cash' },
{ value = 'bank', label = 'Bank' }
}
if data.amount >= shared.FinanceMinimum and data.type ~= 'BlackMarketArms' then
table.insert(dropdownmenu, { value = 'finance', label = 'Finance' })
end
table.insert(options,{ type = 'select', label = 'Payment Type', options = dropdownmenu })
local input = lib.inputDialog('Select Payment', options)
return input[index]
end

self.Finance = function(data) -- simple financing only. since ox_lib input does not return real time (like the ox_lib menus) changed amount ex. from slider value. so we cant make advanced finance. unless i implement another NEW UI just for this.
local options = {}
local retval = 'cancel'
local downpayment = data.amount * shared.FinanceDownPayment/100
local interest = shared.FinanceInterest/100
table.insert(options,{ type = "slider", label = "Down Payment", min = downpayment, max = downpayment * 2, step = 1})
--table.insert(options,{ type = "input", label = "Amount Financed (Interest Rate: "..shared.FinanceInterest.."%)", placeholder = amountfinanced , disabled = true})
table.insert(options,{ type = "slider", label = "Finance Duration (days)", min = 5, max = shared.FinanceMaxDays, step = 1})
local input = lib.inputDialog('Finance Page 1', options)
local initialpayment, days = table.unpack(input)
local finaldailyamount
if input ~= 'cancel' then
local options = {}
local total = data.amount - initialpayment
local amountfinanced = data.amount * (1.0 - (initialpayment / data.amount))
local dailyamount = amountfinanced / days
local daytomax = days / shared.FinanceMaxDays
local interestrate = shared.FinanceInterest/100 - (shared.FinanceInterest - (shared.FinanceInterest * daytomax)) / 100
finaldailyamount = dailyamount * ( 1.0 + interestrate )
local interestpercent = interestrate * 100
table.insert(options,{ type = "input", label = "Initial Payment", placeholder = initialpayment , disabled = true})
table.insert(options,{ type = "input", label = "Amount Financed (Interest Rate: "..interestpercent.."%)", placeholder = amountfinanced , disabled = true})
table.insert(options,{ type = "input", label = "Daily Amount", placeholder = finaldailyamount , disabled = true})
local input = lib.inputDialog('Finance Confirmation', options)
retval = true
if not input or input == 'cancel' then
retval = 'cancel'
end
end
return retval, {daily = finaldailyamount, downpayment = initialpayment, days = days}
end

self.RemoveShop = function(data)

end
Expand Down
5 changes: 5 additions & 0 deletions init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ shared.framework = 'ESX' -- ESX || QBCORE
shared.oxShops = false -- if true this resource will use ox_inventory Shops UI instead of built in UI
shared.allowplayercreateitem = false -- if false only admin can create new items via /stores
shared.target = false -- if true all lib zones for markers and oxlib textui will be disable.
shared.FinanceMinimum = 10000 -- minimum amount for financing to be enable
shared.FinanceDownPayment = 20 -- 20%. this is the amount of minimum initial payment
shared.FinanceInterest = 10 -- 10%
shared.FinanceMaxDays = 30
shared.MaxDebt = 1000000 -- max amount of total debt from player in able to finance
shared.defaultStock = {
General = 100,
Ammunation = 20,
Expand Down
136 changes: 119 additions & 17 deletions server/main.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
GlobalState.Shipping = json.decode(GetResourceKvpString('shippingcompany') or '[]') or {}
GlobalState.Stores = json.decode(GetResourceKvpString('renzu_stores') or '[]') or {}
GlobalState.MovableShops = json.decode(GetResourceKvpString('movableshops') or '[]') or {}
GlobalState.FinanceData = json.decode(GetResourceKvpString('financedata') or '[]') or {}
GlobalState.JobShop = {}
local Items = {}
local purchaseorders = {}
Expand Down Expand Up @@ -142,13 +143,33 @@ exports.ox_inventory:registerHook('buyItem', function(payload)
end
end)

sendMoneyToBankOffline = function(id,amount)
ModifyFromBankOffline = function(id,amount,minus)
local result = SqlFunc('oxmysql','fetchAll','SELECT '..playeraccounts..' FROM '..playertable..' WHERE '..playeridentifier..' = ?', { id })
local accounts = json.decode(result[1][playeraccounts])
accounts.bank += amount
if not minus then
accounts.bank += amount
else
accounts.bank -= amount
end
SqlFunc('oxmysql','execute','UPDATE '..playertable..' SET '..playeraccounts..' = ? WHERE '..playeridentifier..' = ?', {json.encode(accounts), id})
end

GetMoneyFromBankOffline = function(id)
local result = SqlFunc('oxmysql','fetchAll','SELECT '..playeraccounts..' FROM '..playertable..' WHERE '..playeridentifier..' = ?', { id })
if not result[1] then return end
local accounts = json.decode(result[1][playeraccounts])
return accounts.bank
end

SendMoneytoStoreAccount = function(identifier,money,plus)
local owner = GetPlayerFromIdentifier(identifier)
if owner then
owner.addAccountMoney('bank',money)
else
ModifyFromBankOffline(identifier, money)
end
end

RemoveStockFromStore = function(data)
local stores = GlobalState.Stores
local success = false
Expand All @@ -167,12 +188,7 @@ RemoveStockFromStore = function(data)
-- todo
-- exports.bankingresource:SendToBank(source,money)
local receive = (tonumber(price) * data.amount) * 0.95 -- 5% fee
local owner = GetPlayerFromIdentifier(stores[v.label].owner)
if owner then
owner.addAccountMoney(data.money,receive)
else
sendMoneyToBankOffline(stores[v.label].owner, receive)
end
SendMoneytoStoreAccount(stores[v.label].owner,receive)
else
if v.cashier then -- if cashier is enable store money to cashier
if not stores[v.label].cashier then stores[v.label].cashier = {} end
Expand Down Expand Up @@ -452,24 +468,28 @@ lib.callback.register('renzu_shops:buyitem', function(source,data)
end
end
end
if data.finance then
total = data.finance.downpayment
end
if not hasitem then
return 'invalidamount'
end

data.type = data.type:gsub('Wallet',data.moneytype) -- check payment type
local money = xPlayer.getAccount(data.type:lower()).money
if xPlayer.getAccount(data.type:lower()).money >= total then
xPlayer.removeAccountMoney(data.type:lower(),total)
local moneytype = data.type
moneytype = moneytype:gsub('Wallet',moneytype) -- check payment type
moneytype = moneytype:gsub('finance','money')
local money = xPlayer.getAccount(moneytype:lower()).money
if money >= total then
xPlayer.removeAccountMoney(moneytype:lower(),total)
callback = 'success'
for k,v in pairs(customparts) do -- remove custom items from cart as its inserted as custom item metadatas from the recent loop above
for k,item in pairs(v) do
for k2,v in pairs(data.items) do
local name = v.data.metadata and v.data.metadata.name or v.data.name
if item.name == name then
if storeowned then -- storeowned Ownableshops data handler
RemoveStockFromStore({shop = data.shop, metadata = v.data.metadata, require = v.data.require, index = data.index, item = v.data.name, amount = tonumber(v.count), price = data.data[v.data.name].price, money = data.type:lower()})
RemoveStockFromStore({shop = data.shop, metadata = v.data.metadata, require = v.data.require, index = data.index, item = v.data.name, amount = tonumber(v.count), price = data.data[v.data.name].price, money = moneytype:lower()})
elseif movableshop then -- movable shops logic data handler
RemoveStockFromStash({addmoney = true, identifier = data.shop, metadata = v.data.metadata, item = v.data.name, amount = tonumber(v.count), price = data.data[v.data.name].price, type = data.index, money = data.type:lower()})
RemoveStockFromStash({addmoney = true, identifier = data.shop, metadata = v.data.metadata, item = v.data.name, amount = tonumber(v.count), price = data.data[v.data.name].price, type = data.index, money = moneytype:lower()})
end
if v.count > item.count then
data.items[k2].count -= item.count
Expand All @@ -483,9 +503,9 @@ lib.callback.register('renzu_shops:buyitem', function(source,data)
end
for k,v in pairs(data.items) do
if storeowned then -- storeowned Ownableshops data handler
RemoveStockFromStore({shop = data.shop, metadata = v.data.metadata, require = v.data.require, index = data.index, item = v.data.name, amount = tonumber(v.count), price = data.data[v.data.name].price, money = data.type:lower()})
RemoveStockFromStore({shop = data.shop, metadata = v.data.metadata, require = v.data.require, index = data.index, item = v.data.name, amount = tonumber(v.count), price = data.data[v.data.name].price, money = moneytype:lower()})
elseif movableshop then -- movable shops logic data handler
RemoveStockFromStash({addmoney = true, identifier = data.shop, metadata = v.data.metadata, item = v.data.name, amount = tonumber(v.count), price = data.data[v.data.name].price, type = data.index, money = data.type:lower()})
RemoveStockFromStash({addmoney = true, identifier = data.shop, metadata = v.data.metadata, item = v.data.name, amount = tonumber(v.count), price = data.data[v.data.name].price, type = data.index, money = moneytype:lower()})
end
if data.shop ~= 'VehicleShop' then -- add new item if its not a vehicle type
exports.ox_inventory:AddItem(source,v.data.name,v.count,v.data.metadata, false)
Expand All @@ -504,12 +524,94 @@ lib.callback.register('renzu_shops:buyitem', function(source,data)
end
Wait(500)
end
local stores = GlobalState.Stores
if data.finance and stores[storeowned] then
print(data.finance.daily,data.finance.days)
local daily = data.finance.daily
local days = data.finance.days
local finance = {
days = days,
daily = daily,
total = daily * days,
owner = stores[storeowned].owner,
shop = storeowned,
identifier = xPlayer.identifier,
bank = xPlayer.getAccount('bank').money
}
RegisterFinance(finance)
end
return callback
else
return 'notenoughmoney'
end
end)

RegisterFinance = function(data)
local finance = GlobalState.FinanceData
if not finance[data.identifier] then
finance[data.identifier] = {max = shared.MaxDebt + data.bank, financed = {}}
end
if finance[data.identifier].financed then
local financedata = {
total = data.total,
daily = data.daily,
shop = data.shop,
owner = data.owner,
days = data.days
}
finance[data.identifier].max -= data.total
print(financedata,data.total,data.daily,data.shop,data.owner,data.days)
table.insert(finance[data.identifier].financed,financedata)
GlobalState.FinanceData = finance
SetResourceKvp('financedata', json.encode(finance))
end
end

Citizen.CreateThread(function()
while true do
time = os.date("*t")
if time.hour == 0 and time.min == 0 and time.sec == 1 then
local finance = GlobalState.FinanceData
for debtor,v in pairs(finance) do
for k,v in pairs(v.financed) do
local xPlayer = GetPlayerFromIdentifier(debtor)
if xPlayer then
if xPlayer.getAccount('bank').money >= v.daily then
xPlayer.removeAccountMoney('bank',v.daily)
finance[debtor].max += v.daily
finance[debtor].financed[k].total -= v.daily
finance[debtor].financed[k].days -= 1
if finance[debtor].financed[k].total <= 0 or finance[debtor].financed[k].days == 0 then
finance[debtor].financed[k] = nil
end
else -- penalty fee
finance[debtor].max -= finance[debtor].max/10
finance[debtor].financed[k].total += v.daily/10
end
else -- if player is offline
local money = GetMoneyFromBankOffline(debtor)
if money and tonumber(money) >= v.daily then
ModifyFromBankOffline(debtor,v.daily,true)
finance[debtor].max += v.daily
finance[debtor].financed[k].days -= 1
finance[debtor].financed[k].total -= v.daily
if finance[debtor].financed[k].total <= 0 or finance[debtor].financed[k].days == 0 then
finance[debtor].financed[k] = nil
end
else -- penalty fee
finance[debtor].max -= finance[debtor].max/10
finance[debtor].financed[k].total += v.daily/10
end
end
end
end
SetResourceKvp('financedata', json.encode(finance))
GlobalState.FinanceData = finance
end
Wait(1000)
end
end)

lib.callback.register("renzu_shops:buystore", function(source,data)
local source = source
local xPlayer = GetPlayerFromId(source)
Expand Down
8 changes: 2 additions & 6 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,9 @@ <h5 class="text-white m-0">Total in Cart (<span id="totalitem">0</span>)</h5>
<div class="card-header bg-dark p-3">
<div class="card-header-flex" style="justify-content: space-evenly;">
<h5 class="text-white m-0">Total <span id="totalamount"></span></h5>
<button class="btn btn-success mt-0 btn-sm" onclick="pay('Wallet')">
<i class="fa fa-wallet mr-2" aria-hidden="true"></i>
<span> Pay in Wallet </span>
</button>
<button id="bank" class="btn btn-success mt-0 btn-sm" onclick="pay('Bank')">
<button id="bank" class="btn btn-success mt-0 btn-sm" onclick="pay()">
<i aria-hidden="true" class="fa fa-credit-card mr-2"></i>
<span> Pay in Bank </span>
<span> Pay Now </span>
</button>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions web/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ function plus(cartid,item) {
totalitem()
}

function pay(type) {
SendData({type:type, items:cart, msg : 'buy'})
function pay() {
SendData({items:cart, msg : 'buy'})
}

function CloseModal() {
Expand Down

0 comments on commit 4c67515

Please sign in to comment.