Exports And Integration Guide

Server and client exports, client events, legacy qb-banking compatibility exports, and the callback surface for developers integrating with oxide-banking.

Reference for the current oxide-banking integration surface.

Resource Names

Primary resource name (use this for all new integrations):

exports['oxide-banking']:<ExportName>(...)

Legacy compatibility surface (for older QB-era resources only):

exports['qb-banking']:<ExportName>(...)  -- works because the manifest declares provides { 'qb-banking' }

oxide-banking supports QBCore, QBX, and ESX through o-link. All exports below work identically on every framework.

For framework-agnostic use across many resources, prefer the o-link banking namespace, which wraps the most-used exports:

local olink = exports['o-link']:olink()
olink.banking.AddAccountMoney('police', 5000, 'Fine paid')

Server Exports

Signatures use Lua convention. ? after a type means optional/nilable. errorMessage? means the second return is a human-readable string only on failure.

Core / readiness

IsReady() -> boolean

Returns true once the banking system has finished its initial data load.

GetPlayerBankingData(citizenid) -> { account, creditScore, tier, loans, statements }

Aggregate banking profile for a citizen: player account row, credit score record, tier config, open loans keyed by loan id, and up to 100 recent statements (id, account_name, amount, reason, type, timestamp).

GetAccountData(accountName) -> table?

Returns a defensive shallow copy of a cached shared/job account (id, citizenid, account_name, account_balance, account_type, users). Returns nil if the account does not exist.

GetPlayerAccounts(citizenid) -> table[]

Every account the citizen owns or appears in the users list of, as { account_name, account_balance, account_type, is_owner }. Job accounts have no owner and an empty users list, so they are not included through job membership.

GetPlayerStatements(citizenid, limit = 50) -> table[]

Copy of up to limit most recent statements (id, account_name, amount, reason, statement_type, date). statement_type is 'deposit' or 'withdraw'; date is a Unix timestamp in seconds.

CanAfford(citizenid, amount, accountName) -> boolean

For checking (or nil) checks the player's bank balance via the o-link money bridge; for any other name checks the cached shared/job account balance.

Tiers

GetPlayerTier(citizenid) -> string

Current tier name (basic / premium / business), falling back to Config.DefaultTier.

UpgradePlayerTier(citizenid, newTier) -> boolean

Admin path. Force-sets the tier without checking requirements or charging fees.

GetAvailableTiers(citizenid) -> { [tierName] = table }

Every configured tier with its config fields (name, label, description, monthlyFee, interestRate, limits, features, requirements) annotated with isCurrent, isUpgrade, isDowngrade, meetsRequirements, requirementMessage, canUpgrade, canDowngrade. Requirements only read as met while the player is online.

GetTierLevel(tierName) -> number

Numeric hierarchy level: basic=1, premium=2, business=3, unknown=1.

CheckTierRequirements(citizenid, tierName) -> boolean, errorMessage?

Validates minBalance, creditScore, and jobBoss requirements for the named tier. Returns false, 'Player not online' for an offline player when the tier has requirements.

UpgradeAccountTier(citizenid, newTier) -> boolean, tierLabel | errorMessage

Player must be online. Verifies it is an upgrade and the requirements are met, and that the bank balance covers the first monthlyFee plus Config.Fees.services.accountUpgrade before charging either. Then charges both, resets the monthly fee date, and persists.

DowngradeAccountTier(citizenid, newTier = 'basic') -> boolean, tierLabel | errorMessage

Player must be online. Drops the player to a lower tier, charging Config.Fees.services.accountDowngrade if affordable (downgrade still proceeds if not).

Credit score

GetCreditScore(citizenid) -> number

Current numeric credit score (Config.CreditScore.minScore–maxScore, 300–850 by default). Creates a record at defaultScore if one does not exist.

UpdateCreditScore(citizenid, change, reason) -> number

Adds change to the player's credit event bonus, not to the score directly. The total bonus is clamped to ±Config.CreditScore.maxEventBonus and fades each banking day like any credit event, and the score is clamped to minScore–maxScore. Writes a credit history row and returns the new score. A change of 0 or nil changes nothing.

RecalculateCreditScore(citizenid) -> number, { factors, weightedScore, baseScore, eventBonus, thinFile, finalScore, oldScore }

Re-evaluates all five FICO-style factors (payment history 35%, utilization 30%, account age 15%, credit mix 10%, new inquiries 10%) into a base score, adds the decayed event bonus, and persists. A player with no loans and no loan payments gets defaultScore as their base (thinFile = true) while Config.CreditScore.thinFileUsesDefault is on.

GetCreditScoreBreakdown(citizenid, forceRecalc = false) -> { score, rating, stale, factors, suggestions, lastUpdated }

Returns per-factor sub-scores plus an improvement suggestion for any factor below 60. It reads the stored score unless forceRecalc is true, in which case it runs RecalculateCreditScore first. On a stored read (stale = true), suggestions leave out details and params, because the late-payment counts and utilization figures they quote only exist after a full recalculation. Each factor carries key, impact (high / medium / low) and a numeric weightValue next to the English label and weight.

GetCreditHistory(citizenid, limit = 50) -> table[]

Rows from bank_credit_history, most recent first.

RecordCreditEvent(citizenid, eventType, data) -> nil

Adds the event's delta to the player's credit event bonus and inserts a history row. Deltas come from Config.CreditScore.eventDeltas; loan_payment_late, loan_payment_missed and loan_collections read Config.LatePayment.creditScorePenalty, Config.LatePayment.missedPaymentPenalty and Config.Loans.collections.creditScoreImpact. Unknown event types are still logged with a 0 delta. data may be a table (stored as JSON) or a string, trimmed to 255 characters.

AddHardInquiry(citizenid, reason) -> nil

For resources that run their own credit check. Increments the player's stored hard-inquiry count, which the new-inquiries factor reads and the daily pass ages off after 14 days, and records a hard_inquiry credit event. The bank's own loan applications do not call this; they count through the 14-day application count instead.

Account freeze

FreezeAccount(citizenid, accountName, reason, frozenBy, frozenType = 'admin', duration?) -> boolean

Inserts a bank_frozen_accounts row keyed by citizenid or accountName with optional expiry (duration in seconds from now). The expiry is only checked when banking loads frozen accounts at startup; while the server runs, the freeze stays until UnfreezeAccount is called.

UnfreezeAccount(citizenid, accountName) -> boolean

Removes the freeze entry matching the citizenid or accountName.

SelfFreezeAccount(citizenid, duration, reason) -> boolean

Player-initiated freeze. duration accepts '1_hour', '24_hours', '7_days', 'indefinite'. Defaults invalid reasons to 'other'.

SelfUnfreezeAccount(citizenid) -> boolean, errorMessage?

Allows lifting a self-freeze only. Rejects admin-applied freezes. The bank screen cannot reach this while the account is frozen, because a frozen player cannot open the bank.

GetFreezeStatus(citizenid) -> { frozen, reason, expiresAt, frozenType, canSelfUnfreeze }

Freeze state with details from cache.

IsAccountLocked(citizenid) -> boolean, secondsRemaining?

Returns true with seconds remaining while a failed-PIN lockout is active. Config.Security.MaxLoginAttempts wrong ATM PINs lock PIN entry for Config.Security.LockoutDuration seconds. The lockout is held in memory, so a resource restart clears it.

Savings accounts

CreateSavingsAccount(citizenid, accountName, initialDeposit) -> boolean, { id, account_name, balance } | errorMessage

Player must be online. Requires a tier with features.savings. Validates against maxAccounts, requires an opening deposit of at least minimumBalance, and deducts the deposit from checking. A blank accountName becomes 'Savings N'.

GetSavingsAccounts(citizenid) -> table[]

Cached savings accounts: id, account_name, balance, interest_earned, created_at, the current effective annual interestRate, availableWithoutPenalty (the part that has cleared the notice period), unlockSchedule (up to 3 upcoming { amount, date } entries, date in epoch milliseconds), noticeDays, and penaltyRate.

DepositToSavings(citizenid, accountName, amount) -> boolean, newBalance | errorMessage

Player must be online. Moves amount (rounded down to a whole number) from checking into the named savings account and starts its notice period.

WithdrawFromSavings(citizenid, accountName, amount) -> boolean, { withdrawn, penalizedAmount, penalty, netAmount, newBalance } | errorMessage

Pulls from savings to checking, taking the oldest money in the account first. Anything drawn from deposits made within the last Config.AccountTypes.savings.withdrawalNoticeDays days is charged earlyWithdrawalPenalty; older money and earned interest are not. The account must be left either empty or at or above minimumBalance, and a withdrawal whose penalty would take the whole amount is refused. Works for an offline account holder.

Changed in 2.1.0

This function used to take a fourth applyPenalty argument. Whether the penalty applies is now decided from the account's own deposit history, so the argument no longer exists and is ignored if passed.

CloseSavingsAccount(citizenid, accountName) -> boolean, { finalAmount, refunded, penalty } | errorMessage

Refunds the remaining savings balance to checking and deletes the savings row. Charged the same way as a withdrawal: only money still inside the notice period pays the penalty.

Savings goals

CreateSavingsGoal(citizenid, goalName, targetAmount, category, targetDate?) -> boolean, { id, goal_name, target_amount, category } | errorMessage

Inserts a new active goal. Capped by Config.SavingsGoals.maxGoals. Falls back to 'other' for unknown categories. targetDate is optional and must be 'YYYY-MM-DD'; anything else is dropped.

GetSavingsGoals(citizenid) -> table[]

Active goals merged with up to 10 most recent completed goals, each with a progress percentage.

ContributeToGoal(citizenid, goalId, amount) -> boolean, { newAmount, progress, completed } | errorMessage

Player must be online. Transfers up to the goal's remaining amount from checking into the goal. Marks completed and notifies the player when reached.

WithdrawFromGoal(citizenid, goalId, amount) -> boolean, { newAmount, progress } | errorMessage

Player must be online. Withdraws from the goal back to checking. Flips completed back to false if the new amount drops below target.

DeleteSavingsGoal(citizenid, goalId) -> boolean, { refundedAmount } | errorMessage

Player must be online. Refunds the goal's current amount to checking and deletes the goal row.

Cards — issuance & lifecycle

IssueCardForAccount(source, accountName, pin, opts?) -> boolean, { cardNumber, cardNumberMasked, accountName } | errorKey

Validates PIN length (Config.Cards.PinLength), account access, card limits, and player funds; generates a unique card number, hashes the PIN, charges Config.Cards.OrderCost, adds a bank_card item to inventory. Optional opts = { cost, reason, replacingCard } charges cost instead of the order price and leaves replacingCard out of the card limit. Error keys include invalid_pin, no_access, insufficient_permission, card_limit_reached, not_enough_money, and inventory_full.

OrderReplacementCard(source, oldCardNumber, newPin) -> boolean, { cardNumber, cardNumberMasked, accountName } | errorReason

The caller must own the old card. Issues a new card for the same account, charged Config.Cards.ReplacementCost instead of the order price, then cancels the old card and removes its inventory item. If the new card cannot be issued, the old card is left as it was.

FreezeCard(cardNumber) -> boolean, errorReason?

Sets the card's status to frozen (only from active).

UnfreezeCard(cardNumber) -> boolean, errorReason?

Sets the card's status back to active (only from frozen).

CancelCard(cardNumber) -> boolean, errorReason?

Permanently marks an active or frozen card cancelled with cancelled_at timestamp. Not reactivatable. Does not remove the inventory item.

RevokeCard(cardNumber, reason) -> boolean

Sets the card's DB status to revoked with cancelled_at timestamp. Used for non-user-initiated removals (e.g. account closure). reason is only logged.

RevokeCardsForAccount(citizenid, accountName) -> nil

Revokes every card the citizen holds on the named account (for example after removing them from a shared account).

ChangeCardPIN(cardNumber, oldPin, newPin) -> boolean, errorReason?

Verifies the old PIN (auto-migrating legacy hashes), validates the new PIN is exactly 4 characters (regardless of Config.Cards.PinLength), stores the new SHA-256 hash.

Cards — queries & validation

ValidateCardAccess(citizenid, cardNumber) -> boolean, card | errorString

Confirms the card exists, is active, and the citizen still has access to its account.

ValidateCardPIN(cardNumber, pin) -> boolean, card | errorString

Verifies the PIN against the stored SHA-256 salted hash (auto-migrates legacy plain-text entries on first successful verify).

GetPlayerCards(citizenid) -> table[]

All active/frozen cards owned by the citizen, with masked/formatted numbers and account metadata.

GetPlayerInventoryCards(source) -> table[]

Walks the player's inventory for bank_card items, validates each, returns enriched metadata (masked number, account, daily limit fields, ownership). balance is only filled in when Config.Cards.ShowAccountBalanceInSelector is on. The card daily limit is stored but not enforced or counted in this version, so dailyUsed stays 0; withdrawals are limited by the tier's dailyWithdrawLimit.

GetCardsForAccount(accountName, citizenid?) -> string[]

Cached array of card numbers indexed by account name. For the personal checking account, citizenid is required and the result is limited to that character's cards; omitting it returns an empty array. Shared, job, and gang accounts ignore citizenid and return every card on the account.

GetAccountCardCount(accountName, citizenid?) -> number

Count of active and frozen cards on the account, following the same citizenid rules as GetCardsForAccount.

CanIssueCardForAccount(citizenid, accountName, replacingCard?) -> boolean, reason?

Checks the citizen's permission level against Config.Cards.SharedAccountMinPermission and ensures the account is below its MaxCardsPerAccountType limit. A replacingCard on the same account is not counted toward the limit.

HasAccountAccess(citizenid, accountName) -> boolean, permissionType

Returns true with 'owner' for personal checking and owned shared accounts, 'user' for listed shared-account users, and 'job' for any member of the job (not only the boss; the player must be online). Gangs always return false.

GetAccessibleAccounts(citizenid) -> table[]

Every account the player can access (personal checking + shared/job/gang) with account_type and permission.

MaskCardNumber(cardNumber) -> string

Formats a 16-digit number into the masked display form configured by Config.Cards.CardNumberMaskFormat (default **** **** **** XXXX).

FormatCardNumber(cardNumber) -> string

Formats a 16-digit number into four 4-digit groups separated by spaces.

Transactions — fees & previews

CalculateTransactionFee(citizenid, feeKey, amount) -> feeAmount, waiverReason?

Resolves the fee from Config.Fees (handling the percentage-based externalTransfer fee), then applies tier/balance waivers. Returns 0, 'Fees disabled' when Config.Fees.enabled is false. The bank does not charge the externalTransfer fee itself; call this if your resource wants to. For externalTransfer, a maxFee of 0 caps the fee at 0.

GetFeePreview(citizenid, feeKey, amount) -> { feeKey, feeDescription, amount, isWaived, waiverReason, transactionAmount, totalAmount }

UI-friendly fee preview including description and total.

CalculateATMFee(citizenid, atmType) -> feeAmount, waiverReason?

atmType is 'own', 'other', or 'outOfNetwork'. The bank's own ATMs are free and never call this; it is here for an ATM resource that wants to charge Config.Fees.transactions.atm.

CalculateWireTransferFee(citizenid, transferType = 'domestic') -> feeAmount, waiverReason?

Builds the fee key wireTransfer.<transferType> and resolves.

CheckDailyLimit(citizenid, limitType, amount) -> allowed, remaining

Checks whether adding amount would exceed the player's tier dailyWithdrawLimit or dailyTransferLimit.

GetExpeditedTransferFee() -> number

Configured expedited transfer fee (default 25) when Config.Fees.transactions.expeditedTransfer.enabled, else 0.

IsExpeditedTransferEnabled() -> boolean

Whether expedited transfers are enabled in config.

Transactions — wire & pending transfers

ProcessWireTransfer(fromCitizenId, toCitizenId, amount, transferType = 'domestic') -> boolean, message

Both players must be online. amount is whole dollars, at least $1 and at most Config.Transactions.MaxTransferAmount. Checks balance plus fee and the daily transfer limit, moves the money instantly (no Config.TransferDelays wait), applies the wire fee, updates daily transferred totals. If the recipient cannot be paid, the sender gets the money back and the call returns false, 'Transfer failed'. The bank screen has no wire transfer option; this export is the only way to send one.

CreatePendingTransfer(fromCitizenId, toCitizenId, amount, transferType, reason, expedited) -> result, statusString

Returns nil, 'instant' if no delay is configured for transferType ('external' or 'wire') or expedited == true. Otherwise inserts a bank_pending_transfers row and returns { id, scheduledCompletion, delayHours }, 'pending'. It does not take money from the sender; deduct it yourself first. The recipient is credited at the first hourly check after scheduledCompletion.

GetPendingTransfers(citizenid) -> table[]

Pending transfers in either direction, ordered by scheduled completion.

CancelPendingTransfer(citizenid, transferId) -> boolean, message

Only the sender can cancel, and only while the transfer is pending. Refunds the sender, marks the transfer cancelled, writes a refund statement.

Loans

ApplyForLoan(citizenid, loanType, amount, termDays, collateralType, collateralId) -> boolean, loanId | errorMessage, status?

Validates loan type/amount, checks daily loan cap, eligibility, calculates terms, inserts a pending loan, records a loan_opened credit event, then routes it: 'active' (funded now), 'pending_approval' (admin review for amounts at or over manualApprovalThreshold), 'processing' (funded after Config.LoanApproval.processingTime), or 'pending' (waiting for an admin). The fourth value is that status. If an automatic approval fails to pay out, returns false, errorMessage and the application stays pending.

CheckLoanEligibility(citizenid, loanType, amount) -> boolean, message, requirementsData

Validates min/max amount, active loan cap, the total borrowing limit (Config.Loans.exposure), credit score, account age, optional job-boss requirement (player must be online), and optional minIncome (checked against bank balance). On success requirementsData holds creditScore, accountAge, minBalance, and, when a borrowing limit applies, exposure ({ required, current, met } each). On failure it is nil.

CalculateLoanTerms(citizenid, loanType, amount, termDays) -> { principal, interestRate, rateModifier, termDays, totalInterest, totalAmount, paymentAmount, paymentsTotal, intervalDays, creditScore }?

Picks the credit-score rate modifier, computes the effective interest rate (floored at Config.Loans.minimumInterestRate), prorates interest over the term, and splits the total into paymentsTotal instalments intervalDays (Config.TimeSettings.LoanPaymentInterval) apart. A termDays that is not one of the product's termOptions is replaced by the first option. Returns nil for an unknown product.

GetPlayerLoans(citizenid) -> table[]

All loans in open statuses (pending, pending_approval, processing, approved, active, collections), read from the database and decorated with product label/icon, an externalManaged flag, instalments_total, interval_days, days_late, and late_fee_estimate.

GetLoanProducts(citizenid) -> { [productName] = { label, personalizedRate, requirements, meetsRequirements, ... } }

Every configured loan product with the player's personalized interest rate and per-requirement eligibility data.

MakeLoanPayment(citizenid, loanId, amount, options?) -> boolean, { amount, principalPaid, interestPaid, lateFeesPaid, interestRebate, remainingBalance, status, isLate } | errorMessage

Allocates payment to late fees first, then the balance, and never takes more than is owed. Works on active and collections loans, and for a borrower who is offline. Marks the loan paid if fully repaid. Loans registered by another resource are refused.

Only one payment runs on a loan at a time; a second call made while one is in progress returns false. A payment builds credit only when it finishes an instalment or clears the loan. options is used internally ({ payoff = true } applies the early-payoff interest rebate); leave it out.

PayOffLoan(citizenid, loanId) -> boolean, paymentResult | errorMessage

Computes the full payoff amount via CalculatePayoffAmount and submits it through MakeLoanPayment. Paying off before the term ends gives back the interest for the instalments that never ran.

CalculatePayoffAmount(loanId, citizenid?) -> { total, principal, lateFees, interestRebate }

Remaining balance, less the interest rebate, plus any current late fee (higher of flat or percent fee, capped at maxLateFee). Pass citizenid to have a loan that belongs to someone else read as not found (all zeros).

GetLoanPaymentHistory(loanId) -> table[]

All bank_loan_payments rows for the loan, most recent first.

StartCollections(citizenid, loanId) -> boolean

Marks the loan collections and records a loan_collections credit event worth Config.Loans.collections.creditScoreImpact (applied once, through the event). A loan in collections is no longer collected by the daily run; it is repaid by hand or by wage garnishment.

Vehicle financing

See Vehicle Financing Integration for the full integration contract.

RegisterVehicleLoan(data) -> boolean, loanId | errorMessage

data requires citizenid, plate, principal, totalAmount, paymentAmount, paymentsTotal; optional paymentsRemaining (defaults to paymentsTotal), interestRate (defaults to 0.06), and vehicleModel. Guards against duplicate active loans on the same plate. Inserts an externalManaged vehicle loan in active status with the upper-cased plate as collateral_id and a remaining balance of paymentAmount x paymentsRemaining, and records a loan_opened credit event.

RecordVehicleLoanPayment(plate, amount) -> boolean, { loanId, amount, remainingBalance, status, paymentsMade } | errorMessage

Finds the active vehicle loan by plate, decrements remaining amount, counts one payment, records the payment and a loan_payment_ontime credit event, marks paid if fully repaid.

CloseVehicleLoan(plate, reason = 'paid') -> boolean, errorMessage?

Marks the active vehicle loan paid or defaulted (when reason == 'repossessed'), zeros the remaining amount, records the appropriate credit event.

GetVehicleLoan(plate) -> loan?

Active/collections loan row for the plate from bank_loans, with notes JSON pre-decoded.

Interest

CalculateInterest(balance, dailyRate, days, method?) -> number

Returns 0 if interest disabled or balance below Config.Interest.minimumBalance. Otherwise computes simple (balance * rate * days) or compound (balance * ((1+rate)^days - 1)) interest, banker's-rounded. method defaults to Config.Interest.calculationMethod.

GetEffectiveInterestRate(citizenid, accountType) -> number

Tier annual rate × Config.Interest.accountTypeRates[accountType], divided by Config.TimeSettings.DaysPerYear to yield a daily rate.

ProcessLoginInterest(source) -> number

Runs automatically on login. Once at least one real day has passed since last_interest_payout, pays interest for the elapsed real days on the player's checking balance (capped at Config.Interest.maxInterestPayout per whole day) and on each shared account the player owns (capped at maxInterestPayout), updates last_interest_payout, notifies the player. Returns the total paid.

ProcessMonthlyFees(source) -> number

Runs automatically on login. After 30 real days since last_fee_date, charges the tier's monthlyFee × months due. Downgrades to basic (charging nothing) if the player cannot pay. Returns the amount charged.

Investments & CDs

CreateInvestment(citizenid, productType, amount) -> boolean, { id, product, maturityDate } | errorMessage

Player must be online. Enforces a 5-second per-product lock and DB duplicate check, validates the tier's investments feature, the product's tierRequired, the position limits (maxPositionsPerProduct, maxTotalPositions), min/max amount, and funds, then deducts funds and inserts the investment. product in the result is the product's label.

GetPlayerInvestments(citizenid) -> table[]

All open (active and matured) investment rows for the citizen, most recent first, each with an is_matured flag.

WithdrawInvestment(citizenid, investmentId) -> boolean, { payout, penalty, gain, matured } | errorMessage

Cashes out an open position. The server decides from the maturity date whether it is early; a matured position pays its full value. An early exit is charged the product's earlyWithdrawalPenalty on the earnings only, scaled down as the position approaches maturity. Atomically updates status to withdrawn, and pays out even if the player is offline.

Changed in 2.1.0

This function used to take a third early argument, and refused to cash out a position that had not matured unless early was true. The argument no longer exists and is ignored if passed.

PurchaseCD(citizenid, productType, amount) -> boolean, { id, maturityDate, maturityValue } | errorMessage

Player must be online. Requires a tier with the savings feature (Premium/Business on the shipped config), checks the position limits (maxPerProduct, maxTotal) and min/max deposit, deducts funds, inserts a bank_certificates row with prorated interest baked into maturity_value.

GetPlayerCDs(citizenid) -> table[]

All active or matured certificates of deposit for the citizen, each with an is_matured flag.

RedeemCD(citizenid, cdId) -> boolean, { payout, interest } | errorMessage

Pays out the CD's full maturity_value if the maturity date has passed. Rejects pre-maturity redemption (use EarlyWithdrawCD).

EarlyWithdrawCD(citizenid, cdId) -> boolean, { payout, penalty, interest } | errorMessage

For a CD that has not matured. Pays the deposit plus the interest accrued so far (prorated by time elapsed), less the product's earlyWithdrawalPenalty share of that accrued interest; interest is what the player keeps. Never pays less than the deposit. A matured CD is refused and must be redeemed with RedeemCD.

GetMarketState() -> { trend, modifier, dailyDrift, trendDaysRemaining, lastUpdate, previousDayChange, momentum }

Current market simulation state.

GetMarketHistory(days = Config.TimeSettings.DaysPerYear) -> table[]

Up to days records from bank_market_history (trend, modifier, day_number, recorded_at) in chronological order.

Security — 2FA

Enable2FA(citizenid, securityPin) -> boolean, backupCodes | errorMessage

Refused when Config.Security.Enable2FA is false or 2FA is already on. Hashes the 6-digit PIN with SHA-256+salt, generates 8 single-use backup codes, persists to bank_security_2fa. Returns the plaintext backup codes for the player to save.

Disable2FA(citizenid, verificationCode) -> boolean, errorMessage?

Requires a successful Verify2FA call first, then clears the secret and backup codes.

Verify2FA(citizenid, code) -> boolean, errorMessage?

Returns true straight away if the player has not turned 2FA on. Otherwise accepts either the 6-digit PIN (verified against the salted SHA-256 hash, with legacy-format migration) or one of the unused 8-character backup codes. Tracks failed attempts and locks out after 5 failures for 5 minutes.

Requires2FA(citizenid, amount) -> boolean

True when Config.Security.Enable2FA, amount >= Config.Security.TwoFactorThreshold, and the player has 2FA enabled. The bank's own withdrawals, transfers between accounts and transfers to other players use this and ask for the code on screen. Deposits do not. Exports that move money (RemoveMoney, ProcessWireTransfer, and so on) do not check it; a resource that wants a security-PIN step has to ask for it.

Get2FAStatus(citizenid) -> { enabled, backupCodesRemaining, threshold }

Enable state, count of unused backup codes, configured threshold.

GenerateBackupCodes(citizenid) -> boolean, codes | errorMessage

Replaces existing backup codes with 8 fresh single-use codes (invalidating the old set).

Security — fraud & alerts

TrackPlayerLogin(source, citizenid) -> nil

Captures the player's license: identifier and compares against bank_player_accounts.last_license. Alerts the player and logs a security event when a different device is detected.

CheckForFraud(citizenid, transactionType, amount, targetCitizenId) -> flagged, flags

Returns false, nil when Config.Security.EnableFraudDetection is off. Otherwise records the transaction for velocity tracking and emits flags for large_amount (at or above Config.Security.SuspiciousTransactionAmount) and rapid_transactions (5 or more in 60s). When either fires and targetCitizenId is someone the player has not paid before, a new_recipient flag is added as detail; it never fires on its own. Calls FlagSuspiciousActivity when any flag fires. The bank runs this on its own withdrawals, deposits and transfers to other players, and records those transfers in bank_transactions for the new_recipient check.

FlagSuspiciousActivity(citizenid, reason, data) -> nil

Writes a security log entry, notifies all online admins, prints to console, logs via the banking logger with priority = 'high'.

SendTransactionAlert(citizenid, transactionType, amount, details) -> nil

If alerts are enabled and amount is at or above the player's threshold and the type is enabled in preferences, fires a qb-banking:client:transactionAlert event to the player.

GetAlertPreferences(citizenid) -> { enabled, threshold, types }

Cached alert preferences, initializing with defaults from Config.Security.AlertThreshold if not present.

SetAlertPreferences(citizenid, prefs) -> boolean

Stores enabled, threshold, and types (deposit/withdraw/transfer/received) in the in-memory cache. Nothing is saved to the database, so preferences reset when the resource restarts.

Business — invoices

CreateInvoice(fromCitizenId, fromJob, toCitizenId, amount, description, dueDate) -> boolean, { invoiceId, invoiceNumber, dueDate } | errorMessage

Inserts into bank_invoices and notifies the recipient if they are online (and in the tablet app, if oxide-tablet is running). The invoice number is derived from the row id (first four letters of the job upper-cased, or PERS, then -YYYYMM- and the id padded to 5 digits), so two invoices raised at the same moment can never collide. If fromJob has no business account yet, one is created so the invoice can be paid into it.

amount is rounded down to a whole number, and anything not above 0 is rejected. Also rejected: a recipient who does not exist, a player invoicing themselves (personal invoices only), and a due date before today. description is trimmed to 255 characters. dueDate accepts 'YYYY-MM-DD' or a Unix timestamp, and falls back to Config.Business.InvoiceDueDays from now when it is missing or unreadable. Rejections for missing parties, a bad amount, or an unknown recipient are written to the server log with the arguments that caused them.

The per-player limits (InvoiceMaxAmount, MaxOutstandingInvoicesPerRecipient, rate limiting) apply only to invoices raised from the banking screen. Invoices raised by another resource are not subject to them.

PayInvoice(citizenid, invoiceId) -> boolean, { invoiceNumber, amountPaid, lateFee } | errorMessage

Charges the invoice amount plus any accrued late fee to the payer's bank and credits the business account or the player who raised it.

The invoice is claimed before the money moves, so a second attempt on an invoice already being paid is refused rather than charging twice. If the debit or the credit fails, the claim is released and the invoice stays payable.

SettleInvoice(invoice, payerIdentifier) -> boolean, { invoiceNumber, amountPaid, lateFee } | errorMessage

The same payment, taking an already-loaded invoice row. Used by automatic collection. The payer may be offline.

CancelInvoice(citizenid, invoiceId) -> boolean, message | errorMessage

Sets the invoice status to cancelled if the caller raised it and it is still pending or overdue. Notifies the recipient. Overdue invoices can be cancelled so that one raised by mistake can be withdrawn before automatic collection takes it.

GetInvoices(citizenid, jobName, filter) -> table[]

Invoices filtered by 'sent'/'received'/'pending'/'overdue' (default: all involving the citizen), enriched with from_name, to_name, is_sender. With 'sent', a jobName returns that job's invoices; without one, the citizen's personal invoices. The 'sent' filter returns up to 100 rows ordered by most recent activity, so an older invoice that was just paid still appears; the others return up to 50. 'received' rows that are still unpaid also carry late_fee_due, the late fee paying today would add.

Business — expenses & reports

RecordExpense(jobName, category, amount, description, recordedBy) -> boolean, { expenseId } | errorMessage

Validates category against Config.Business.ExpenseCategories, deducts from the job account, reimburses the recorder's bank, logs to bank_expenses.

GetExpenses(jobName, filters) -> table[]

Expense rows filtered by startDate, endDate, category, with pagination (limit, offset), enriched with recorded_by_name.

GetExpenseSummary(jobName, dateRange) -> { categories, grandTotal, startDate, endDate }

Groups expenses by category over a date range (defaults to current month). Adds percentage, label, icon from config.

GenerateIncomeStatement(jobName, startDate, endDate) -> { totalIncome, operatingExpenses, expenseBreakdown, netIncome, profitMargin, ... }

Sums job-account deposits (income) and expenses for the period. Defaults to current month.

GenerateBalanceReport(jobName) -> { balance, receivables, payables, netPosition, receivableCount }

Current job-account balance, sum of outstanding sent invoices (receivables), net position. Payables always 0.

GenerateCashFlowReport(jobName, startDate, endDate) -> { totalInflows, totalOutflows, netCashFlow, byMonth, startDate, endDate }

Sums statement deposits and withdrawals for the job account over the period, broken down by month.


Legacy qb-banking Compatibility Exports

These exist only for older resources that were built against qb-banking. New integrations should use the primary exports above regardless of framework.

CreatePlayerAccount(playerId, accountName, accountBalance, accountUsers) -> boolean, errorMessage?

playerId is the server ID of an online player. Creates a shared bank account owned by that player after validating the name and checking tier-imposed maxSharedAccounts. accountBalance becomes the starting balance; nothing is taken from the player. Persists to bank_accounts and caches.

CreateJobAccount(accountName, accountBalance) -> boolean

Creates a job-type bank account if it does not already exist (rehydrates cache if the DB row exists).

CreateGangAccount(accountName, accountBalance) -> boolean

Always returns false — gang accounts are not supported in oxide-banking. Use shared accounts instead.

CreateBankStatement(playerId, account, amount, reason, statementType, accountType) -> boolean

Accepts a server id (number) or citizenid (string). Title-cases the reason. Inserts a bank_statements row and prepends to the cached statements list (capped at 100).

AddMoney(accountName, amount, reason) -> boolean

Atomic UPDATE adding to the shared/job account balance, syncs cache, auto-creates a deposit statement. Delegates to ESXBridge for ESX job accounts when applicable. Returns false for an unknown or frozen account.

RemoveMoney(accountName, amount, reason) -> boolean

Atomic UPDATE with account_balance >= ? guard preventing overdrafts. Syncs cache and creates a withdraw statement on success. Returns false for an unknown or frozen account.

GetAccount(accountName) -> table?

Returns the cached account table directly (raw reference — do not mutate).

GetAccountBalance(accountName) -> number

Cached balance for the shared/job account, or 0 if missing.

AddGangMoney(accountName, amount, reason) -> boolean

Alias for AddMoney.

RemoveGangMoney(accountName, amount, reason) -> boolean

Alias for RemoveMoney.

GetGangAccount(accountName) -> table?

Alias for GetAccount.

GetGangAccountBalance(accountName) -> number

Alias for GetAccountBalance.


Client Exports

OpenBank() -> nil

Requests banking data via qb-banking:server:openBank, opens the NUI bank UI, starts the ESC close handler. Aborts if any UI is already open. Its optional first argument is used internally by the tablet app; leave it out.

CloseBank() -> nil

Resets all UI state flags (bank/ATM/card-selector/session), releases NUI focus, sends closeApp to the UI. If the bank is open inside the tablet app, closes the tablet instead.

OpenATM() -> nil

Verifies the player has a bank_card item, enters the card-selector flow (OpenCardSelector).

OpenCardSelector() -> nil

Fetches the player's inventory cards from the server. Auto-selects if only one card exists, otherwise shows the multi-card NUI selector.

CloseCardSelector() -> nil

Clears card selector state, releases NUI focus, sends closeCardSelector to the UI.

IsBankOpen() -> boolean

Bank UI open state.

IsATMOpen() -> boolean

ATM UI open state.

IsCardSelectorOpen() -> boolean

Card selector open state.

GetActiveATMSession() -> { cardNumber, accountName, accountType, dailyLimit, dailyUsed, dailyRemaining } | nil

Active ATM session populated when an ATM is opened with a selected card.

GetSelectedCard() -> card | nil

Card currently selected for an ATM session.

IsInBankZone() -> boolean

Cached bank-zone proximity flag. Always returns false when Config.UseTarget is enabled (zone tracking is bypassed).

IsNearATM() -> boolean, atmHandle?

Iterates Config.ATMModels and returns true with the entity handle if any ATM prop is within 1.5m of the player.


Client Events

qb-banking:client:useCard

Plays the card animation and progress bar, then opens the ATM flow if the player is within 1.5m of an ATM. This is what using the bank_card item triggers.

TriggerClientEvent('qb-banking:client:useCard', source)

qb-banking:client:atmInteract

Same ATM flow without the distance check. Ignored if a bank, ATM, or card selector is already open.

qb-banking:client:transactionAlert

Sent by SendTransactionAlert. The client shows a notification and passes the alert to the banking screen.

RegisterNetEvent('qb-banking:client:transactionAlert', function(alert)
    -- alert: { type = 'deposit'|'withdraw'|'transfer'|'received', amount, details, timestamp }
end)

Callback Surface

Legacy lib.callback names still registered by the resource. New integrations should call exports directly instead of using these callbacks.

Bank, ATM, transfers, and tiers

  • qb-banking:server:openBank
  • qb-banking:server:openATM
  • qb-banking:server:withdraw
  • qb-banking:server:deposit
  • qb-banking:server:internalTransfer
  • qb-banking:server:externalTransfer
  • qb-banking:server:orderCard
  • qb-banking:server:openAccount
  • qb-banking:server:renameAccount
  • qb-banking:server:deleteAccount
  • qb-banking:server:addUser
  • qb-banking:server:removeUser
  • qb-banking:server:getTierInfo
  • qb-banking:server:upgradeTier
  • qb-banking:server:downgradeTier
  • qb-banking:server:getPlayerTier
  • qb-banking:server:checkDailyLimits
  • qb-banking:server:getBalanceHistory

Cards

  • qb-banking:server:getInventoryCards
  • qb-banking:server:validateCardAndOpenATM
  • qb-banking:server:getCardableAccounts
  • qb-banking:server:orderCardForAccount
  • qb-banking:server:selectCardForATM
  • qb-banking:server:verifyCardPIN
  • qb-banking:server:getPlayerCards
  • qb-banking:server:freezeCard
  • qb-banking:server:unfreezeCard
  • qb-banking:server:changeCardPIN
  • qb-banking:server:cancelCard
  • qb-banking:server:orderReplacementCard

Savings and recurring payments

  • qb-banking:server:createSavingsAccount
  • qb-banking:server:getSavingsAccounts
  • qb-banking:server:depositToSavings
  • qb-banking:server:quoteSavingsWithdrawal
  • qb-banking:server:withdrawFromSavings
  • qb-banking:server:closeSavingsAccount
  • qb-banking:server:createSavingsGoal
  • qb-banking:server:getSavingsGoals
  • qb-banking:server:contributeToGoal
  • qb-banking:server:withdrawFromGoal
  • qb-banking:server:deleteSavingsGoal
  • qb-banking:server:createRecurringPayment
  • qb-banking:server:getRecurringPayments
  • qb-banking:server:cancelRecurringPayment
  • qb-banking:server:toggleRecurringPayment
  • qb-banking:server:updateRecurringPayment
  • qb-banking:server:getPendingTransfers
  • qb-banking:server:cancelPendingTransfer
  • qb-banking:server:getTransferSettings
  • qb-banking:server:getTransferDelayInfo

Loans, credit, investments, and security

  • qb-banking:server:getLoans
  • qb-banking:server:getLoanProducts
  • qb-banking:server:checkLoanEligibility
  • qb-banking:server:getLoanTerms
  • qb-banking:server:applyForLoan
  • qb-banking:server:payLoan
  • qb-banking:server:payOffLoan
  • qb-banking:server:getLoanPayments
  • qb-banking:server:getLoanPayoffAmount
  • qb-banking:server:getCreditBreakdown
  • qb-banking:server:getCreditHistory
  • qb-banking:server:recalculateCreditScore
  • qb-banking:server:getInvestments
  • qb-banking:server:createInvestment
  • qb-banking:server:withdrawInvestment
  • qb-banking:server:getCDs
  • qb-banking:server:purchaseCD
  • qb-banking:server:redeemCD
  • qb-banking:server:earlyWithdrawCD
  • qb-banking:server:getMarketState
  • qb-banking:server:getMarketHistory
  • qb-banking:server:enable2FA
  • qb-banking:server:disable2FA
  • qb-banking:server:verify2FA
  • qb-banking:server:get2FAStatus
  • qb-banking:server:getBackupCodes
  • qb-banking:server:regenerateBackupCodes
  • qb-banking:server:getSecuritySettings
  • qb-banking:server:getSecurityLog
  • qb-banking:server:getAlertPreferences
  • qb-banking:server:setAlertPreferences
  • qb-banking:server:selfFreezeAccount
  • qb-banking:server:selfUnfreezeAccount
  • qb-banking:server:getFreezeStatus

Business

  • qb-banking:server:recordExpense
  • qb-banking:server:getExpenses
  • qb-banking:server:getExpenseSummary
  • qb-banking:server:createInvoice
  • qb-banking:server:payInvoice
  • qb-banking:server:cancelInvoice
  • qb-banking:server:getInvoices
  • qb-banking:server:getIncomeStatement
  • qb-banking:server:getBalanceReport
  • qb-banking:server:getCashFlowReport
  • qb-banking:server:getExpenseCategories
  • qb-banking:server:getJobActivity

Recommendation

Use exports['oxide-banking'] for new integration work.

Reserve exports['qb-banking'] and qb-banking:server:* callback names for legacy resources that still need them.

On this page

Resource NamesServer ExportsCore / readinessIsReady() -> booleanGetPlayerBankingData(citizenid) -> { account, creditScore, tier, loans, statements }GetAccountData(accountName) -> table?GetPlayerAccounts(citizenid) -> table[]GetPlayerStatements(citizenid, limit = 50) -> table[]CanAfford(citizenid, amount, accountName) -> booleanTiersGetPlayerTier(citizenid) -> stringUpgradePlayerTier(citizenid, newTier) -> booleanGetAvailableTiers(citizenid) -> { [tierName] = table }GetTierLevel(tierName) -> numberCheckTierRequirements(citizenid, tierName) -> boolean, errorMessage?UpgradeAccountTier(citizenid, newTier) -> boolean, tierLabel | errorMessageDowngradeAccountTier(citizenid, newTier = 'basic') -> boolean, tierLabel | errorMessageCredit scoreGetCreditScore(citizenid) -> numberUpdateCreditScore(citizenid, change, reason) -> numberRecalculateCreditScore(citizenid) -> number, { factors, weightedScore, baseScore, eventBonus, thinFile, finalScore, oldScore }GetCreditScoreBreakdown(citizenid, forceRecalc = false) -> { score, rating, stale, factors, suggestions, lastUpdated }GetCreditHistory(citizenid, limit = 50) -> table[]RecordCreditEvent(citizenid, eventType, data) -> nilAddHardInquiry(citizenid, reason) -> nilAccount freezeFreezeAccount(citizenid, accountName, reason, frozenBy, frozenType = 'admin', duration?) -> booleanUnfreezeAccount(citizenid, accountName) -> booleanSelfFreezeAccount(citizenid, duration, reason) -> booleanSelfUnfreezeAccount(citizenid) -> boolean, errorMessage?GetFreezeStatus(citizenid) -> { frozen, reason, expiresAt, frozenType, canSelfUnfreeze }IsAccountLocked(citizenid) -> boolean, secondsRemaining?Savings accountsCreateSavingsAccount(citizenid, accountName, initialDeposit) -> boolean, { id, account_name, balance } | errorMessageGetSavingsAccounts(citizenid) -> table[]DepositToSavings(citizenid, accountName, amount) -> boolean, newBalance | errorMessageWithdrawFromSavings(citizenid, accountName, amount) -> boolean, { withdrawn, penalizedAmount, penalty, netAmount, newBalance } | errorMessageCloseSavingsAccount(citizenid, accountName) -> boolean, { finalAmount, refunded, penalty } | errorMessageSavings goalsCreateSavingsGoal(citizenid, goalName, targetAmount, category, targetDate?) -> boolean, { id, goal_name, target_amount, category } | errorMessageGetSavingsGoals(citizenid) -> table[]ContributeToGoal(citizenid, goalId, amount) -> boolean, { newAmount, progress, completed } | errorMessageWithdrawFromGoal(citizenid, goalId, amount) -> boolean, { newAmount, progress } | errorMessageDeleteSavingsGoal(citizenid, goalId) -> boolean, { refundedAmount } | errorMessageCards — issuance & lifecycleIssueCardForAccount(source, accountName, pin, opts?) -> boolean, { cardNumber, cardNumberMasked, accountName } | errorKeyOrderReplacementCard(source, oldCardNumber, newPin) -> boolean, { cardNumber, cardNumberMasked, accountName } | errorReasonFreezeCard(cardNumber) -> boolean, errorReason?UnfreezeCard(cardNumber) -> boolean, errorReason?CancelCard(cardNumber) -> boolean, errorReason?RevokeCard(cardNumber, reason) -> booleanRevokeCardsForAccount(citizenid, accountName) -> nilChangeCardPIN(cardNumber, oldPin, newPin) -> boolean, errorReason?Cards — queries & validationValidateCardAccess(citizenid, cardNumber) -> boolean, card | errorStringValidateCardPIN(cardNumber, pin) -> boolean, card | errorStringGetPlayerCards(citizenid) -> table[]GetPlayerInventoryCards(source) -> table[]GetCardsForAccount(accountName, citizenid?) -> string[]GetAccountCardCount(accountName, citizenid?) -> numberCanIssueCardForAccount(citizenid, accountName, replacingCard?) -> boolean, reason?HasAccountAccess(citizenid, accountName) -> boolean, permissionTypeGetAccessibleAccounts(citizenid) -> table[]MaskCardNumber(cardNumber) -> stringFormatCardNumber(cardNumber) -> stringTransactions — fees & previewsCalculateTransactionFee(citizenid, feeKey, amount) -> feeAmount, waiverReason?GetFeePreview(citizenid, feeKey, amount) -> { feeKey, feeDescription, amount, isWaived, waiverReason, transactionAmount, totalAmount }CalculateATMFee(citizenid, atmType) -> feeAmount, waiverReason?CalculateWireTransferFee(citizenid, transferType = 'domestic') -> feeAmount, waiverReason?CheckDailyLimit(citizenid, limitType, amount) -> allowed, remainingGetExpeditedTransferFee() -> numberIsExpeditedTransferEnabled() -> booleanTransactions — wire & pending transfersProcessWireTransfer(fromCitizenId, toCitizenId, amount, transferType = 'domestic') -> boolean, messageCreatePendingTransfer(fromCitizenId, toCitizenId, amount, transferType, reason, expedited) -> result, statusStringGetPendingTransfers(citizenid) -> table[]CancelPendingTransfer(citizenid, transferId) -> boolean, messageLoansApplyForLoan(citizenid, loanType, amount, termDays, collateralType, collateralId) -> boolean, loanId | errorMessage, status?CheckLoanEligibility(citizenid, loanType, amount) -> boolean, message, requirementsDataCalculateLoanTerms(citizenid, loanType, amount, termDays) -> { principal, interestRate, rateModifier, termDays, totalInterest, totalAmount, paymentAmount, paymentsTotal, intervalDays, creditScore }?GetPlayerLoans(citizenid) -> table[]GetLoanProducts(citizenid) -> { [productName] = { label, personalizedRate, requirements, meetsRequirements, ... } }MakeLoanPayment(citizenid, loanId, amount, options?) -> boolean, { amount, principalPaid, interestPaid, lateFeesPaid, interestRebate, remainingBalance, status, isLate } | errorMessagePayOffLoan(citizenid, loanId) -> boolean, paymentResult | errorMessageCalculatePayoffAmount(loanId, citizenid?) -> { total, principal, lateFees, interestRebate }GetLoanPaymentHistory(loanId) -> table[]StartCollections(citizenid, loanId) -> booleanVehicle financingRegisterVehicleLoan(data) -> boolean, loanId | errorMessageRecordVehicleLoanPayment(plate, amount) -> boolean, { loanId, amount, remainingBalance, status, paymentsMade } | errorMessageCloseVehicleLoan(plate, reason = 'paid') -> boolean, errorMessage?GetVehicleLoan(plate) -> loan?InterestCalculateInterest(balance, dailyRate, days, method?) -> numberGetEffectiveInterestRate(citizenid, accountType) -> numberProcessLoginInterest(source) -> numberProcessMonthlyFees(source) -> numberInvestments & CDsCreateInvestment(citizenid, productType, amount) -> boolean, { id, product, maturityDate } | errorMessageGetPlayerInvestments(citizenid) -> table[]WithdrawInvestment(citizenid, investmentId) -> boolean, { payout, penalty, gain, matured } | errorMessagePurchaseCD(citizenid, productType, amount) -> boolean, { id, maturityDate, maturityValue } | errorMessageGetPlayerCDs(citizenid) -> table[]RedeemCD(citizenid, cdId) -> boolean, { payout, interest } | errorMessageEarlyWithdrawCD(citizenid, cdId) -> boolean, { payout, penalty, interest } | errorMessageGetMarketState() -> { trend, modifier, dailyDrift, trendDaysRemaining, lastUpdate, previousDayChange, momentum }GetMarketHistory(days = Config.TimeSettings.DaysPerYear) -> table[]Security — 2FAEnable2FA(citizenid, securityPin) -> boolean, backupCodes | errorMessageDisable2FA(citizenid, verificationCode) -> boolean, errorMessage?Verify2FA(citizenid, code) -> boolean, errorMessage?Requires2FA(citizenid, amount) -> booleanGet2FAStatus(citizenid) -> { enabled, backupCodesRemaining, threshold }GenerateBackupCodes(citizenid) -> boolean, codes | errorMessageSecurity — fraud & alertsTrackPlayerLogin(source, citizenid) -> nilCheckForFraud(citizenid, transactionType, amount, targetCitizenId) -> flagged, flagsFlagSuspiciousActivity(citizenid, reason, data) -> nilSendTransactionAlert(citizenid, transactionType, amount, details) -> nilGetAlertPreferences(citizenid) -> { enabled, threshold, types }SetAlertPreferences(citizenid, prefs) -> booleanBusiness — invoicesCreateInvoice(fromCitizenId, fromJob, toCitizenId, amount, description, dueDate) -> boolean, { invoiceId, invoiceNumber, dueDate } | errorMessagePayInvoice(citizenid, invoiceId) -> boolean, { invoiceNumber, amountPaid, lateFee } | errorMessageSettleInvoice(invoice, payerIdentifier) -> boolean, { invoiceNumber, amountPaid, lateFee } | errorMessageCancelInvoice(citizenid, invoiceId) -> boolean, message | errorMessageGetInvoices(citizenid, jobName, filter) -> table[]Business — expenses & reportsRecordExpense(jobName, category, amount, description, recordedBy) -> boolean, { expenseId } | errorMessageGetExpenses(jobName, filters) -> table[]GetExpenseSummary(jobName, dateRange) -> { categories, grandTotal, startDate, endDate }GenerateIncomeStatement(jobName, startDate, endDate) -> { totalIncome, operatingExpenses, expenseBreakdown, netIncome, profitMargin, ... }GenerateBalanceReport(jobName) -> { balance, receivables, payables, netPosition, receivableCount }GenerateCashFlowReport(jobName, startDate, endDate) -> { totalInflows, totalOutflows, netCashFlow, byMonth, startDate, endDate }Legacy qb-banking Compatibility ExportsCreatePlayerAccount(playerId, accountName, accountBalance, accountUsers) -> boolean, errorMessage?CreateJobAccount(accountName, accountBalance) -> booleanCreateGangAccount(accountName, accountBalance) -> booleanCreateBankStatement(playerId, account, amount, reason, statementType, accountType) -> booleanAddMoney(accountName, amount, reason) -> booleanRemoveMoney(accountName, amount, reason) -> booleanGetAccount(accountName) -> table?GetAccountBalance(accountName) -> numberAddGangMoney(accountName, amount, reason) -> booleanRemoveGangMoney(accountName, amount, reason) -> booleanGetGangAccount(accountName) -> table?GetGangAccountBalance(accountName) -> numberClient ExportsOpenBank() -> nilCloseBank() -> nilOpenATM() -> nilOpenCardSelector() -> nilCloseCardSelector() -> nilIsBankOpen() -> booleanIsATMOpen() -> booleanIsCardSelectorOpen() -> booleanGetActiveATMSession() -> { cardNumber, accountName, accountType, dailyLimit, dailyUsed, dailyRemaining } | nilGetSelectedCard() -> card | nilIsInBankZone() -> booleanIsNearATM() -> boolean, atmHandle?Client Eventsqb-banking:client:useCardqb-banking:client:atmInteractqb-banking:client:transactionAlertCallback SurfaceBank, ATM, transfers, and tiersCardsSavings and recurring paymentsLoans, credit, investments, and securityBusinessRecommendation