Implementing VIP/Premium Subscription System for Mobile Games
Subscription in mobile games is technically more complex than one-time IAP: you manage state, handle cancellation, restoration, upgrades/downgrades between tiers, grace period on failed billing. Errors here are expensive — user loses access to paid content and writes angry review.
VIP Subscription Models in Games
Daily VIP — daily bonus while active: +20% coin drop, +1 free attempt, exclusive daily chest. Low price ($1.99–4.99/mo), high conversion.
Premium subscription — removes ads + monthly resource package + VIP-badge. Price $4.99–9.99/mo.
VIP Levels — multiple tiers (VIP Bronze, Silver, Gold) with growing privileges. More complex implementation but monetizes different audience segments.
Implementation on 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)
}
}
}
Critical: listen to Transaction.updates from app launch — not just when opening shop. Otherwise you'll miss renewal or revocation while app is in background.
Implementation on 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 is mandatory for subscription! Google auto-cancels unacknowledged purchases after 3 days. Forgotten acknowledge — most common cause of complaints "money charged but subscription didn't activate".
Server-Side Verification and Sync
Subscription status cannot be stored only on client. Architecture:
- Client gets receipt/purchaseToken after purchase
- Client sends it to backend
- Backend verifies via App Store Server API (iOS) or Google Play Developer API (Android)
- Backend saves subscription status in DB with expiration date
- On each app start, client fetches current status from server
Apple App Store Server Notifications V2 — webhook Apple sends to your endpoint on renewal, cancellation, grace period entry, billing retry. Real-time without polling:
POST /apple/subscription-notifications
{
"signedPayload": "...", // JWT signed by Apple
"notificationType": "DID_RENEW" | "EXPIRED" | "GRACE_PERIOD_EXPIRED" | ...
}
Grace Period
On failed auto-billing (no money on card) Apple and Google give grace period: iOS — 6 days for monthly, Android — 3 days. During grace period subscription is active but show warning: "Payment problem, update payment info".
Without grace period handling, players lose access suddenly and don't understand why — chargebacks and 1-star reviews.
Managing Tiers
With multiple subscription tiers (VIP1, VIP2, VIP3), handle upgrades and downgrades:
-
Upgrade (VIP1 → VIP2): on iOS via
Product.SubscriptionInfo.upgradePolicies, immediate with prorated calculation - Downgrade (VIP2 → VIP1): applies at next period start
Different product IDs for different tiers must be in same subscriptionGroupId (iOS) / basePlanId (Android) — lets platform handle transitions correctly.
Subscription Analytics
Key events to track: subscription_started, subscription_renewed, subscription_cancelled, subscription_expired, grace_period_entered. Send to Firebase/Amplitude + Subscription Analytics Dashboard.
Metrics: MRR (Monthly Recurring Revenue), churn rate (% cancelled per month), subscriber LTV. Churn above 10%/mo signals subscription doesn't provide enough value.
Timeline: basic subscription with one tier, server verification and renewal handling — 3–5 days. System with multiple tiers, App Store Server Notifications, grace period and analytics — 7–10 days.







