Реалізація системи віртуальної валюти (баллы/монети) у мобільному застосунку
Віртуальна валюта — це не просто счітчик у базі даних. Це економічна система всередину застосунку, у якої є інфляція, читерство та вимоги до трансакційності. Баги у начісленні монет — це або пусті гаманці користувачів (злі відзиви), або переповнення (збитки для бізнесу).
Трансакційність — основа
Кожна операція з балансом — це трансакція з debit/credit записом у ledger таблиці, не просто UPDATE balance = balance + N. Причина: атомарність. Якщо начислити 100 монет та одночасно списати 50 за покупку — при гонці двох запитів до поля балансу отримуємо некоректний результат. Ledger + SELECT FOR UPDATE або оптимістична блокування (UPDATE balance WHERE balance = :expected) на сервері.
Клієнт при будь-якій трансакції отримує повну відповідь: {balance: 1250, delta: +100, transaction_id: "uuid", reason: "daily_bonus"}. Ніколи не пересчитуємо баланс на клієнті — тільки відображаємо значення з сервера.
Захист від читерства
Отладочний прокси. Charles Proxy або mitmproxy дозволяють перехоплювати запити та змінювати відповіді. Якщо відповідь /rewards/daily-bonus повертає {"coins": 100} без підпису сервера — клієнт може показати «отримав 100 монет», але сервер у це час повинен самостійно начислювати та не довіряти значенню з відповіді. Баланс на клієнті — тільки для відображення.
SSL Pinning. Базова захист від MITM: TrustKit (iOS) / OkHttp CertificatePinner (Android). Не абсолютна захист — Frida або root + SSL Kill Switch обходять pinning. Але відсікає 90% script-kiddie спроб.
Rate limiting операцій. Подвійне натискання кнопки «Зібрати бонус» — debounce на клієнті (250ms throttle) плюс серверна захист через idempotency_key (UUID від клієнта). Сервер ігнорує повторний запит з тим же ключем протягом 60 секунд.
UI: анімація начисления монет
Монети «летять» до счітчика — стандартна анімація для reward. Реалізація: частинки через CAEmitterLayer (iOS) або Jetpack Compose Canvas з кастомним анімованим Modifier. Кілька монет-іконок вилітають з джерела (позиція кнопки/икони события), летять по кривій Безье до віджету балансу, при кожному «попаданні» счітчик інкрементується на 1. Плавний нарастаючий ефект через CAKeyframeAnimation з path.
Счітчик балансу при оновленні — number rolling animation: цифри прокручуються зверху вниз як одометр. На iOS через UILabel з CATransition або кастомний AnimatedNumberView у SwiftUI. На Android — ValueAnimator з TextSwitcher.
Покупка монет через IAP
Consumable продукти у StoreKit 2: «100 монет за $0.99», «550 монет за $4.99» і т.д. Product.purchase() → Transaction.finish() після начисления серверного балансу — важливий порядок: спочатку зачисляємо монети через сервер (серверна валідація чека), потім завершуємо трансакцію. Якщо завершити транзакцію до зачисления та застосунок упаде — користувач заплатив, монети не отримав.
На Android: BillingClient.launchBillingFlow() → Purchase.getPurchaseState() == PURCHASED → серверна валідація через Google Play Developer API → BillingClient.consumeAsync() (consumable повинен бути consumed, інакше недоступний для повторної покупки).
Pending transactions (відкладені покупки на Android через сімейний Google Pay або банківське підтвердження) — обробляємо через BillingClient.PurchasesUpdatedListener, чекаємо підтвердження до начисления монет.
Історія транзакцій
Користувач хочет зрозуміти, куди пішли монети. TransactionHistory — пагінований список (cursor-pagination) з типами: earned_daily_bonus, earned_referral, spent_purchase, spent_unlock, purchased_iap. Фільтр за типом. Локальний кеш останніх 50 операцій у Core Data / Room для миттєвого відображення без мережевого запиту.
Процес роботи
Проектування схеми ledger та захисту від читерства → розроблення IAP (consumable) + серверна валідація → UI балансу + анімації → історія транзакцій → QA (включаючи тест parallel requests, pending transactions) → публікація.
Ориентири по срокам
Повна реалізація системи віртуальної валюти з IAP, анімаціями та історією транзакцій — 3–5 робочих днів при готовому серверному API. Якщо включає проектування серверного ledger — 1–2 тижні.







