Реализация системы VIP/Premium подписки мобильной игры
Подписка в мобильной игре технически сложнее разовой IAP-покупки: нужно управлять состоянием, обрабатывать отмену, восстановление, upgrade/downgrade между тирами и grace period при неудачном списании. Ошибки здесь стоят дорого — пользователь теряет доступ к контенту, за который заплатил, и пишет гневный отзыв.
Модели VIP-подписок в играх
Daily VIP — ежедневный бонус пока активна подписка: +20% к дропу монет, +1 бесплатный попытка, эксклюзивный ежедневный сундук. Низкая цена ($1.99–4.99/мес), высокая конверсия.
Premium подписка — снятие рекламы + пакет ресурсов ежемесячно + VIP-badge. Цена $4.99–9.99/мес.
VIP Levels — несколько тиров (VIP Bronze, Silver, Gold) с нарастающими привилегиями. Сложнее в реализации, но позволяет монетизировать разные сегменты аудитории.
Реализация на iOS (StoreKit 2)
import StoreKit
class SubscriptionManager: ObservableObject {
@Published var isVIPActive = false
private var updateTask: Task<Void, Error>?
func startListeningForTransactions() {
updateTask = Task.detached {
for await result in Transaction.updates {
await self.handle(result)
}
}
}
private func handle(_ result: VerificationResult<Transaction>) async {
guard case .verified(let transaction) = result else { return }
if transaction.productType == .autoRenewable {
let isActive = transaction.revocationDate == nil
&& (transaction.expirationDate ?? .distantPast) > Date()
await MainActor.run { self.isVIPActive = isActive }
}
await transaction.finish()
}
func checkCurrentEntitlements() async {
for await result in Transaction.currentEntitlements {
await handle(result)
}
}
}
Критично: слушать Transaction.updates нужно с момента запуска приложения — не только при открытии магазина. Иначе пропустите renewal или revocation, пока приложение работает в фоне.
Реализация на Android (Google Play Billing Library 6)
class BillingManager(private val context: Context) {
private val billingClient = BillingClient.newBuilder(context)
.setListener { billingResult, purchases ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
purchases?.forEach { handlePurchase(it) }
}
}
.enablePendingPurchases()
.build()
private fun handlePurchase(purchase: Purchase) {
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged) {
val params = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient.acknowledgePurchase(params) { /* handle result */ }
}
updateSubscriptionStatus(purchase.products, isActive = true)
}
}
}
Acknowledge обязателен для subscription! Google автоматически отменяет неподтверждённые покупки через 3 дня. Забытый acknowledge — самая частая причина жалоб «деньги списали, подписка не активировалась».
Серверная верификация и синхронизация
Статус подписки нельзя хранить только на клиенте. Архитектура:
- Клиент получает receipt/purchaseToken после покупки
- Клиент отправляет его на backend
- Backend верифицирует через App Store Server API (iOS) или Google Play Developer API (Android)
- Backend записывает статус подписки в БД с датой истечения
- При каждом старте приложения клиент получает актуальный статус с сервера
Apple App Store Server Notifications V2 — webhook, который Apple посылает на ваш endpoint при каждом renewal, cancellation, grace period entry, billing retry. Это реальное время без необходимости polling:
POST /apple/subscription-notifications
{
"signedPayload": "...", // JWT подписанный Apple
"notificationType": "DID_RENEW" | "EXPIRED" | "GRACE_PERIOD_EXPIRED" | ...
}
Grace Period
При неудачном автосписании (нет денег на карте) Apple и Google дают игроку grace period: iOS — 6 дней для monthly, Android — 3 дня. В этот период подписка считается активной, но нужно показать предупреждение: «Проблема с оплатой, обновите платёжные данные».
Без обработки grace period игроки теряют доступ внезапно и не понимают почему — чарджбеки и 1-звёздочные отзывы.
Управление тирами
Если у вас несколько тиров подписки (VIP1, VIP2, VIP3), нужно обрабатывать upgrade и downgrade:
-
Upgrade (VIP1 → VIP2): на iOS — через
Product.SubscriptionInfo.upgradePolicies, немедленное применение с пропорциональным расчётом - Downgrade (VIP2 → VIP1): применяется с начала следующего периода
Разные продуктовые ID для разных тиров нужно зарегистрировать в одной subscriptionGroupId (iOS) / одном basePlanId (Android) — это позволяет платформе правильно обрабатывать переходы.
Аналитика подписок
Ключевые события для трекинга: subscription_started, subscription_renewed, subscription_cancelled, subscription_expired, grace_period_entered. Отправляем в Firebase/Amplitude + amplitude Subscription Analytics Dashboard.
Метрики: MRR (Monthly Recurring Revenue), churn rate (% отменивших за месяц), subscriber LTV. Churn выше 10%/мес — сигнал, что подписка не даёт достаточно ценности.
Сроки: базовая подписка с одним тиром, серверной верификацией и обработкой renewal — 3–5 дней. Система с несколькими тирами, App Store Server Notifications, grace period и аналитикой — 7–10 дней.







