📱 kioskgaming_cashier

Cashier App
Cashier Application

Android application specifically for cashiers at gaming kiosks — manage shifts, count cash by face value, process credits transactions with players.

Android Native · Capacitor 7 React 18 CRA TypeScript 4.9 CRACO Config react-hook-form v7 Tailwind CSS 3 @kioskgaming/ui
7 Main page
13 Denomination of money
5 Step in shift
3 TT method
Android Native Shell
👤

Role of Thu Ngan

The cashier is the direct operator at the gaming kiosk counter. They use this app onDedicated Android tabletlocated at the cashier counter, acting as a bridge between the player's actual cash and the credits system.

💰

Cash conversion → Credits

Receive cash from the player, enter the amount and payment method, the system automatically adds credits to the player's account.

💸

Payment Credits → Cash

When the player wants to exchange credits for cash, the cashier processes the redeem order and spends money directly at the counter.

🧾

Count money according to face value

Each time you open a shift, close a shift, or recharge mid-shift, you require the amount of each denomination to be entered — ensuring accuracy and accountability.

👥

Manage player accounts

Look up players by phone number or username. Create a new account if you don't have one — right at the counter, no need to leave.

📊

Track case history

Review your entire shift list, including transaction totals, fund amounts, and delivery status.

🔒

Accountability

All transactions are recorded in shifts. Device fingerprint ensures each Android device is uniquely identified.

🔄

Work Shift Lifecycle

Each shift goes through 5 steps in a mandatory order. The app guides the cashier step by step and prevents any steps from being skipped.

Step 1 · OpeningCashPage

Open a fund at the beginning of the shift

The cashier counts all the money in the safe before the shift starts. Enter the quantity of each denomination into the Denomination Grid. The system automatically calculates totals and creates new shift records.

DenominationGrid computeDenominationTotal() cashierGetShift()
Step 2 · ActiveShiftPage

Shift is active

Main working screen. Cashier searches for players, tops up credits, exchanges credits, creates new players. The transaction list of the shift is displayed in real time next to it.

cashierFetchPlayers() cashierDepositCredits() cashierRedeemCredits() cashierCreatePlayer() cashierListTransactions()
Step 3 (optional) · CashDropPage

Recharge mid-shift

When the safe needs more cash during the shift, the cashier records this refill using the Denomination Grid. Each cash drop is recorded with full face value details.

cashierCreateCashDrop() DenominationGrid
Step 4 · ClosingCountPage

Closing funds at the end of shift

At the end of the shift, the cashier counts all the cash and enters it into the Denomination Grid. The system compares with the expected balance and records the difference (if any).

DenominationGrid emptyDenominationGrid()
Step 5 · HandoverPage

Shift handover

Transfer the shift to the next cashier. Handover records are saved including recipient information, notes, and cash drawer status.

HandoverPage CashierProtectedRoute

* CashDropPage can be performed multiple times in one shift, it is not a mandatory step. The remaining steps must be in the correct order.

💵

Denomination Grid — Money Counting Grid

DenominationGrid.tsxis the central component of the application — used on all 3 cash counting pages (Opening, Cash Drop, Closing). Supports 7 denominations of notes and 6 denominations of coins according to USD standards.

💵 Sheet of paper — BILL_DENOMINATIONS

$1 × $1.00
$2 × $2.00
$5 × $5.00
$10 × $10.00
$20 × $20.00
$50 × $50.00
$100 × $100.00
Total sheet of paper $760.00

🪙 Xu — COIN_DENOMINATIONS

¢1 × $0.01
¢5 × $0.05
¢10 × $0.10
¢25 × $0.25
¢50 × $0.50
$1 × $1.00
Total coins $9.50
Total (Bills + Coins) $769.50

Utils — denominations.ts

// Constant denomination const BILL_DENOMINATIONS = [1,2,5,10,20,50,100] const COIN_DENOMINATIONS = [0.01,0.05,0.1,0.25,0.5,1.0] // Create an empty grid emptyDenominationGrid() // Calculate total from grid computeDenominationTotal(grid) // Label format: "$20", "¢25" formatDenominationLabel(denomination) // Currency format: "$1,234.56" formatCurrency(amount)

Reasons to use Denomination Grid

  • Completely eliminate mental math — the cashier just needs to count the number of bills/coins
  • The total amount is updated instantly when entering the quantity
  • Detailed data by denomination is saved for audit purposes
  • In accordance with industry standard treasury procedures
  • Supports discrepancies detection when comparing Opening vs Closing
🎮

Trade with Players

ActiveShiftPage.tsxis the main working screen. The cashier handles all transactions with players from here — search, top up credits, exchange credits, create new accounts.

Active Shift · Active Shift
NV
nguyen.van
📞 (555) 012-3456
$248.00
credits
💰 Top up Credits
💸 Redeem Credits
Method: Cash Bank card Transfer

Credits (Deposit) Flow

1️⃣Search for players
2️⃣Enter deposit amount
3️⃣Select method (cash/card/bank)
4️⃣Confirm →cashierDepositCredits()
5️⃣Credits added to player account

Credits Redeem Flow (Redeem)

1️⃣Search for players
2️⃣Enter the number of credits you want to exchange
3️⃣Confirm →cashierRedeemCredits()
4️⃣The cashier pays cash to the player
5️⃣Credits deducted from account

Create New Player — cashierCreatePlayer()

📱PhoneInput (phone number)
📧 Email
react-hook-form validate
🎮Create an account right at the counter

Component PhoneInputfrom@kioskgaming/uiHandles international standard phone number formatting, cashiers don't need to worry about formatting.

🤖

Android Native with Capacitor 7

The app is packaged into an Android APK via Capacitor 7, running directly on a dedicated Android tablet located at the counter. This provides a native app experience while still developing with React/TypeScript.

@capacitor/android v7

Convert React web app to native Android APK. Unique codebase, deployed like a real Android app.

🔑

Device Fingerprint

deviceFingerprint.tsCreate unique Device ID for each Android device. Attach the device to the work shift.

⚙️

CRACO Config

Create React App is extended via CRACO — adding aliases, Tailwind plugins, custom build configuration without eject.

Benefits of Android Native Shell

  • WebView loads content fromserver.urlremote — requires stable network
  • Device fingerprint UUID saved in localStorage, case mounted + device registration
  • APK build via npm run apk:dev / apk:prod + gradlew
  • Package id: club.kioskservice.kioskgaming.cashier
  • New devices require store management to browse in the Shop Portal
  • CRACO patch source-map-loader frompackages/ui/scripts/

capacitor.config.ts

const remoteUrl = process.env.CAP_SERVER_URL?.trim() || 'https://cashier.kioskservice.club/'; const config: CapacitorConfig = { appId: 'club.kioskservice.kioskgaming.cashier', appName: 'Cashier - Kiosk Gaming', // or [Dev] ... webDir: 'build', server: { url: remoteUrl, // ★ WebView loads web live allowNavigation: ['dev-cashier.kioskservice.club', 'cashier.kioskservice.club'], }, }

Build flow: npm run buildnpx cap sync androidnpx cap run android → APK deployed to device

🛡️

Security & Accountability

The cashier app handles the actual cash, so every layer of security and audit trail is a requirement, not optional.

🔐

CashierProtectedRoute

Every page is protected byCashierProtectedRoute.tsx. No valid session → redirect to login page.

🪪

useCashierAuth Hook

Separate hooksuseCashierAuth.tsxManage the cashier's authentication, token, and session state.

📱

Device Fingerprint

deviceFingerprint.tsCreate an unforgeable Device ID. Each shift is tied to specific equipment.

📋

Denomination Audit Trail

Every money count is saved in detail according to each denomination. History cannot be edited after submission — any discrepancies will be detected.

🔄

Shift Lifecycle Enforcement

Applications must go through in the correct order: Opening → Active → Closing → Handover. No step can be skipped.

🔑

axios Client

cashierApi.tsUse singleton axios with interceptor Bearer token and catch 401 → logout. Base URL included/api.

MyShiftsPage.tsx— Cashiers can view their entire shift history: fund amounts, total transactions, status, and handover time. This is a source of proof if there is a dispute over money.

⚙️

Technical Stack

Services structure

// cashierApi.ts — all API calls cashierGetShift() // Get current shift information cashierListTransactions() // List of transactions in shift cashierFetchPlayers(query) // Find players by phone/username cashierDepositCredits(playerId, amount, method) // Load credits for the player cashierRedeemCredits(playerId, amount) // Change player credits cashierCreateCashDrop(denomGrid) // Record mid-shift top-up cashierCreatePlayer(phone, email) // Create new player

All Pages

FilePurpose
CashierLoginPageLogin cashier
OpeningCashPageCount money to open a fund
ActiveShiftPageTransactions in shifts
CashDropPageRecharge mid-shift
ClosingCountPageCount money to contribute to the fund
HandoverPageShift handover
MyShiftsPageShift history
Library / Tools Version Role
React 18 Main UI framework
TypeScript 4.9 Type safety for the entire codebase
CRACO Extend CRA config (aliases, plugins) does not eject
Capacitor 7 Android native shell — packages React into an APK
@capacitor/android v7 Android platform bridge
react-hook-form v7 Form validation (login, create player, reset password)
react-hot-toast Toast notifications for successful/failed transactions
Tailwind CSS 3 Styling utility-first
@kioskgaming/ui file:../packages/ui Modal, PhoneInput — shared UI (symlink packages/ui)
@kioskgaming/page-loading file:../packages/page-loading PageLoadingIndicator for route guard
axios ^1.3 HTTP client singleton + interceptors

Components Share

CashierLayout.tsx DenominationGrid.tsx CashierProtectedRoute.tsx useCashierAuth.tsx denominations.ts deviceFingerprint.ts cashierApi.ts