Implementing Virtual Currency (Points/Coins) in Mobile Application
Virtual currency—not just a counter in database. It's an economic system within the app with inflation, cheating, and transactional requirements. Bugs in coin issuance—either empty user wallets (angry reviews) or overflow (business losses).
Transactionality—Foundation
Every balance operation—transaction with debit/credit ledger entry, not just UPDATE balance = balance + N. Reason: atomicity. Issue 100 coins and simultaneously deduct 50 for purchase—race condition on balance field causes incorrect result. Ledger + SELECT FOR UPDATE or optimistic locking (UPDATE balance WHERE balance = :expected) on server.
Client receives complete response on any transaction: {balance: 1250, delta: +100, transaction_id: "uuid", reason: "daily_bonus"}. Never recalculate balance on client—only display value from server.
Cheating Protection
Debug proxy. Charles Proxy or mitmproxy intercepts and modifies responses. If /rewards/daily-bonus returns {"coins": 100} unsigned—client can show "got 100 coins", but server independently issues and doesn't trust response value. Balance on client—display only.
SSL Pinning. Basic MITM defense: TrustKit (iOS) / OkHttp CertificatePinner (Android). Not absolute—Frida or rooted SSL Kill Switch bypasses. But blocks 90% script-kiddie attempts.
Operation rate limiting. Double-tap "Collect bonus" button—debounce on client (250ms throttle) plus server protection via idempotency_key (client UUID). Server ignores duplicate with same key within 60 seconds.
UI: Coin Animation
Coins "fly" to counter—standard reward animation. Implementation: particles via CAEmitterLayer (iOS) or Jetpack Compose Canvas with custom animated Modifier. Several coin icons launch from source (button position/event), fly by Bezier curve to balance widget, each "hit" increments counter by 1. Smooth cumulative effect via CAKeyframeAnimation with path.
Balance counter on update—number rolling animation: digits scroll top-to-bottom like odometer. iOS: UILabel with CATransition or custom AnimatedNumberView in SwiftUI. Android: ValueAnimator with TextSwitcher.
Buying Coins via IAP
Consumable products in StoreKit 2: "100 coins for $0.99", "550 coins for $4.99" etc. Product.purchase() → Transaction.finish() after server balance issue—important order: credit coins first (server receipt validation), then finish transaction. If app crashes before finish and crashes after—user paid, didn't get coins.
On Android: BillingClient.launchBillingFlow() → Purchase.getPurchaseState() == PURCHASED → server validation via Google Play Developer API → BillingClient.consumeAsync() (consumable must be consumed, else unavailable for repurchase).
Pending transactions (deferred Android via Family Google Pay or bank approval)—handle via BillingClient.PurchasesUpdatedListener, wait confirmation before issuing coins.
Transaction History
User wants to understand where coins went. TransactionHistory—paginated list (cursor-pagination) with types: earned_daily_bonus, earned_referral, spent_purchase, spent_unlock, purchased_iap. Filter by type. Local cache last 50 operations in Core Data / Room for instant display without network request.
Process
Ledger scheme and cheating protection design → IAP (consumable) + server validation → balance UI + animations → transaction history → QA (parallel requests test, pending transactions) → publication.
Timeline Estimates
Full virtual currency system with IAP, animations, history: 3–5 working days with ready server API. Including server ledger design: 1–2 weeks.







