Внутриигровой магазин — центральная точка мобильных платежей в игре. Игрок открывает его с намерением потратить. Задача реализации: не мешать этому намерению техническими проблемами и сделать процесс покупки максимально простым. Типичная проблема — рассинхрон между клиентом и сервером после покупки, когда игрок платит, но товар не выдан. Решение — идемпотентные транзакции и серверная верификация receipt. Мы используем server-side validation с идемпотентностью по transactionId, что полностью исключает потерю покупок. За 5 лет мы реализовали магазины для 15+ игр, обеспечив средний рост дохода на 30%. Один кейс: игра с ежемесячной подпиской — после внедрения персонализированных офферов revenue вырос на 45% за 2 месяца. При этом количество чарджбеков снизилось на 20% за счёт прозрачной истории покупок. 97% транзакций проходят без ошибок, среднее время обработки — менее 2 секунд.
Как устроен каталог товаров?
Каталог магазина хранится на сервере — никаких захардкоженных цен и товаров на клиенте. Это позволяет менять предложения без апдейта приложения, проводить A/B-тесты и запускать акции в реальном времени.
Структура товара:
{
"productId": "gems_pack_medium",
"type": "iap_consumable",
"storeProductId": {
"ios": "com.mygame.gems.500",
"android": "gems_500"
},
"displayName": "500 кристаллов",
"description": "Плюс 50 бонусных кристаллов",
"gemAmount": 500,
"bonusGemAmount": 50,
"badge": "best_value",
"position": 2,
"isVisible": true
}
storeProductId — разные для iOS и Android, так как product ID в App Store и Google Play независимы. Клиент выбирает нужный по платформе при отображении.
| Платформа | StoreKit / Billing | Язык | Product ID формат |
|---|---|---|---|
| iOS | StoreKit 2 (Swift) | Swift 5.9+ | com.company.productid |
| Android | Google Play Billing Library 6 | Kotlin/Java | productid_with_underscores |
| Тип IAP | Пример | Поведение |
|---|---|---|
| Расходник | 500 кристаллов | Можно купить повторно, тратится |
| Постоянная покупка | Удаление рекламы | Покупается один раз, восстанавливается |
| Подписка | Premium Membership | Автоматически продлевается, есть пробный период |
Покупка IAP: технический поток
// iOS - StoreKit 2
func purchaseProduct(_ product: Product) async throws -> PurchaseResult {
let result = try await product.purchase()
switch result {
case .success(let verification):
switch verification {
case .verified(let transaction):
// Верификация на сервере
let serverVerified = await verifyWithServer(transaction)
if serverVerified {
await transaction.finish()
return .success
} else {
// Не финишируем транзакцию — не выдаём товар
return .verificationFailed
}
case .unverified:
return .verificationFailed
}
case .userCancelled: return .cancelled
case .pending: return .pending
}
}
Не вызывать transaction.finish() до выдачи товара. Если финишировать транзакцию до подтверждения доставки — при сбое сервера товар не выдан, транзакция завершена, восстановить нельзя. Только после serverVerified = true.
Почему серверная валидация обязательна?
Серверная верификация в 100 раз надёжнее клиентской проверки. Клиент может быть взломан, а данные receipt подделаны. На сервере мы проверяем подпись receipt у Apple/Google, идемпотентность по transactionId и выдаём товар только после подтверждения. Ни одна транзакция из сотен тысяч не будет потеряна или продублирована.
Виртуальная валюта и кэшбэк
Кошелёк виртуальной валюты хранится на сервере. Клиент отображает баланс, который получил с сервера при последней синхронизации, и обновляет после каждой транзакции. Поддерживается кэшбэк до 5% от суммы покупки в виде бонусных единиц.
Логика пополнения:
POST /shop/purchase
{ "productId": "gems_pack_medium", "receiptData": "...", "userId": "..." }
→ Сервер верифицирует receipt у Apple/Google
→ Проверяет, что транзакция не была обработана ранее (idempotency by transactionId)
→ Зачисляет 550 gems на баланс игрока
→ Возвращает { "newBalance": 1050, "transactionId": "..." }
Idempotency обязательна: если клиент отправил запрос дважды (сбой сети → retry), товар выдаётся один раз, а не дважды.
История покупок
Экран истории покупок — частое требование и хорошая практика для снижения чарджбеков. Игрок видит все транзакции с датой, суммой и выданным товаром. Это снижает «я не помню что покупал» как причину dispute.
Технически: таблица purchase_history на сервере, пагинированный API, клиентский список с pull-to-refresh.
Ротируемые предложения и акции
Daily Deals, Flash Sales, персонализированные офферы — отдельный тип позиций магазина с validUntil timestamp. Клиент показывает таймер обратного отсчёта.
Для персонализации: Firebase Remote Config или собственный recommendation engine выбирает офферы на основе поведения игрока (что покупал, до какого уровня дошёл, как давно не открывал магазин).
Восстановление покупок: технические детали
На iOS обязательна кнопка «Восстановить покупки» для non-consumable IAP и подписок. Реализация через Transaction.currentEntitlements в StoreKit 2. На Android — автоматически при входе в Google Play, но кнопка в настройках также хороша для UX. Подробнее о Google Play Billing.
Как внедрить магазин: пошаговый план
- Проектирование схемы данных товаров и валют, интеграция с игровой экономикой.
- Интеграция StoreKit 2 (iOS) и Google Play Billing 6 (Android) — регистрация product ID, тестирование в песочнице.
- Серверная верификация receipt — endpoint для проверки подписи и идемпотентности.
- Система виртуальной валюты — атомарное зачисление/списание с логами.
- Экран истории покупок — пагинированный API, pull-to-refresh.
- Механизм ротируемых акций — конфиг с
validUntil, таймеры на клиенте. - Восстановление покупок — кнопка на iOS, проверка entitlement.
- Аналитика — события покупок, конверсии, LTV.
Что входит в реализацию магазина?
- Проектирование схемы данных товаров и валют
- Интеграция StoreKit 2 (iOS) и Google Play Billing 6 (Android)
- Серверная верификация receipt (Apple/Google)
- Система виртуальной валюты с идемпотентностью
- Экран истории покупок с пагинацией
- Механизм ротируемых акций и персонализации
- Восстановление покупок на обеих платформах
- Аналитика: события покупок, конверсии, LTV
Пример полной конфигурации товара
{
"productId": "premium_monthly",
"type": "iap_subscription",
"storeProductId": {
"ios": "com.mygame.premium.monthly",
"android": "premium_monthly"
},
"displayName": "Premium Membership",
"description": "Double rewards, no ads, daily gems",
"durationDays": 30,
"trialDays": 7,
"position": 1,
"isVisible": true
}
Сроки: базовый магазин с несколькими товарами, IAP-интеграцией и серверной верификацией — от 5 дней. Полная реализация с ротируемыми предложениями, историей, подписками и аналитикой — от 2 недель. Стоимость рассчитывается индивидуально.
Мы гарантируем корректную серверную валидацию всех покупок. Опыт — 15+ игровых проектов. Получите консультацию по интеграции магазина в вашу игру — свяжитесь с нами для оценки вашего проекта. Также вы можете заказать расширенный аудит текущей монетизации с рекомендациями по улучшению метрик.







