-
Notifications
You must be signed in to change notification settings - Fork 20
Open
Labels
Description
Issue Summary
The Qonversion.shared.restore() method is returning empty entitlements for users who have valid, active purchases through Google Play. This issue started on October 28, 2025 and has affected approximately 30 users who have contacted support (probably more). On this date I migrated to the new One-Time products from Google Play which are supposed to be supported and be backwards compatible.
Impact
- Severity: Critical - Paying customers cannot access premium features they purchased
- Affected Users: ~30 users (and growing) who purchased through Google Play
- Scenarios:
- Users who clear app data and try to restore purchases
- Users who install the app on a new device with the same Google account
- Purchase Flow: Initial purchases work correctly, but restoration fails (success in the callback restore but empty entitlements)
Environment
- Qonversion SDK Version: 8.2.6 and 9.0.2
- Platform: Android
- Min SDK: 24
- Target SDK: 36
- Compile SDK: 36
- Kotlin: 2.2.21
- Build Environment: Production (tested and confirmed)
- Launch Mode:
QLaunchMode.SubscriptionManagement
SDK Initialization
private fun setupBilling() {
Qonversion.initialize(
QonversionConfig.Builder(
this,
"<MY KEY>",
QLaunchMode.SubscriptionManagement,
)
.setEnvironment(if (BuildConfig.DEBUG) QEnvironment.Sandbox else QEnvironment.Production)
.setFallbackFileIdentifier(R.raw.qonversion_android_fallbacks)
.build(),
)
tryOrLog {
Qonversion.shared.syncPurchases()
Qonversion.shared.products(object : QonversionProductsCallback {
override fun onError(error: QonversionError) {
Timber.e("Qonversion error: ${error.description}")
}
override fun onSuccess(products: Map<String, QProduct>) {
Timber.d("Products fetched successfully. Count: ${products.size}")
}
})
}
FirebaseAnalytics.getInstance(this).appInstanceId.addOnCompleteListener { task ->
task.result?.let { appInstanceId ->
Qonversion.shared.setUserProperty(
QUserPropertyKey.FirebaseAppInstanceId,
appInstanceId,
)
}
}
}Restore Purchase Implementation
// Extension function wrapper
suspend fun Qonversion.restorePurchases(): QEntitlement? =
suspendCoroutine { continuation ->
this.restore(
object : QonversionEntitlementsCallback {
override fun onError(error: QonversionError) {
continuation.resume(null)
}
override fun onSuccess(entitlements: Map<String, QEntitlement>) {
// Here I validate if entitlement are active, but it's not returning anything in the map
continuation.resume(entitlements.values.find {
it.hasPremiumEntitlement(validateActive = true)
})
}
},
)
}
// Repository method
override suspend fun restorePurchases(): Boolean {
try {
val hasRestoredPurchases =
Qonversion.shared.restorePurchases().hasPremiumEntitlement()
} catch (ex: Throwable) {
Timber.e(ex)
return false
}
}
// Entitlement validation
fun QEntitlement?.hasPremiumEntitlement(validateActive: Boolean = true): Boolean {
val isPremiumEntitlement = this?.id in premiumEntitlements
val isValid = if (validateActive) {
this?.isActive == true
} else {
true
}
return isPremiumEntitlement && isValid
}Expected Behavior
When a user with a valid Google Play purchase calls restore():
- The SDK should communicate with Google Play Billing
- Retrieve the user's active subscriptions/purchases
- Return a non-empty entitlements map containing the user's premium entitlement
- The app should successfully restore premium access
Actual Behavior
When restore() is called:
- The
onSuccesscallback is triggered (notonError) - The entitlements map is completely empty (
{}) - Production logs confirm:
restorePurchases#onSuccess: {} - Users cannot access premium features they legitimately purchased
- This happens consistently for affected users across app reinstalls and device changes
Steps to Reproduce
- Purchase a premium subscription/product through Google Play (this works)
- Either:
- Clear the app data in Android settings
- OR install the app on a new device using the same Google account
- Open the app and tap "Restore Purchases"
- Observe: Restore returns empty entitlements despite valid Google Play purchase
Timeline
- Before October 28, 2025: Restore purchases worked correctly
- October 28, 2025: Issue started - restore began returning empty entitlements, One Time Product Migrated on Google Play
- October 31, 2025: Temporarily upgraded to SDK 9.0.2 to attempt fix
- November 2, 2025: Reverted to 8.2.6 as 9.0.2 had API breaking changes
- No code changes to our restore logic during this period
Additional Context
- The initial purchase flow works perfectly - users can successfully purchase and receive entitlements
- Only the restore functionality is broken
- Issue is consistent and reproducible in production environment
- The
restore()callback does not error - it succeeds but returns empty data - We have implemented comprehensive logging which confirms empty entitlements map
- We also call
syncPurchases()during initialization, but this doesn't resolve the restore issue
Can you help me with this issue? Is there something else I should be doing in order to be able to restore user's purchases?
wesjon
