Troubleshooting
Common issues and solutions when implementing in-app purchases with kmp-iap.
Installation & Setup Issues
Gradle Configuration Problems
Problem: Build fails with dependency conflicts
Solution:
// In your module's build.gradle.kts
dependencies {
implementation("io.github.hyochan:kmp-iap:1.0.0-beta.2")
// Ensure correct Kotlin version
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.20")
}
// Clean and rebuild
./gradlew clean
./gradlew build
iOS Build Issues
Problem: Build fails with StoreKit related errors
Solution:
-
Ensure iOS deployment target is 15.0+:
# In your iOS project settings
platform :ios, '15.0' -
Clean and rebuild:
cd iosApp
rm -rf build
pod deintegrate
pod install
cd ..
./gradlew clean
./gradlew build
Android Build Issues
Problem: Build fails with billing library conflicts
Solution:
-
Check
android
block in build.gradle.kts:android {
compileSdk = 34
defaultConfig {
minSdk = 21 // Required minimum
}
} -
Add billing permission to AndroidManifest.xml:
<uses-permission android:name="com.android.vending.BILLING" />
Connection & Initialization Issues
"Billing is unavailable" Error
Problem: IAP initialization returns billing unavailable error
Possible Causes & Solutions:
-
Google Play Store not installed/updated:
- Ensure Google Play Store is installed and updated
- Test on real device, not emulator
-
App not uploaded to Play Console:
- Upload app to Internal Testing track minimum
- Wait for processing (can take several hours)
-
Package name mismatch:
// Ensure applicationId matches Play Console
android {
defaultConfig {
applicationId = "com.yourcompany.yourapp"
}
} -
Developer account issues:
- Verify Google Play Developer account is active
- Complete merchant agreement
iOS Connection Issues
Problem: Connection fails on iOS
Solutions:
-
Sandbox environment:
- Test on real device with sandbox account
- Don't sign in to sandbox account in Settings
- Only sign in when prompted during purchase
-
App Store Connect setup:
- Verify products are "Ready to Submit"
- Complete agreements in ASC
- Wait up to 24 hours for product propagation
Product Loading Issues
Products Not Loading
Problem: getProducts()
returns empty list
Debugging Steps:
-
Verify product IDs:
// Enable debug logging
// Check exact product ID matching
try {
val products = kmpIapInstance.requestProducts(
ProductRequest(
skus = listOf("exact.product.id.from.store"),
type = ProductType.INAPP
)
)
println("Loaded products: ${products.size}")
} catch (e: PurchaseError) {
println("Error loading products: $e")
} -
Check store console:
- iOS: Products "Ready to Submit" in App Store Connect
- Android: Products "Active" in Play Console
-
Wait for propagation:
- New products can take 24+ hours to be available
- Try with existing, known-working products first
iOS Products Not Loading
Specific Solutions:
-
Bundle ID verification:
- Check bundle ID in Xcode matches App Store Connect
- Verify in Info.plist
-
Agreements verification:
- Check App Store Connect > Agreements, Tax, and Banking
- Ensure Paid Applications Agreement is active
-
Product status:
- Products must be "Ready to Submit" or "Approved"
- Check in App Store Connect > Features > In-App Purchases
Android Products Not Loading
Specific Solutions:
-
App upload requirement:
# Build and upload APK/AAB to Play Console
./gradlew bundleRelease -
Package name verification:
- Verify
applicationId
in build.gradle.kts - Must exactly match Play Console
- Verify
-
License testing:
- Add test accounts in Play Console > Setup > License testing
- Use test accounts for testing
Purchase Issues
Purchase Flow Not Working
Problem: Purchase request doesn't trigger system dialog
Debugging:
-
Check initialization:
// Ensure IAP is initialized before purchase
val isConnected = kmpIapInstance.isConnected()
if (!isConnected) {
println("IAP not initialized")
return
} -
Verify product exists:
// Check if products are loaded
val products = loadedProducts
if (products.none { it.productId == productId }) {
println("Product not found: $productId")
return
} -
Check state observers:
// Ensure observers are set up before purchase
scope.launch {
kmpIapInstance.purchaseUpdatedListener.collect { purchase ->
purchase?.let {
println("Purchase updated: ${it.productId}")
}
}
}
scope.launch {
kmpIAP.purchaseErrorListener.collect { error ->
error?.let {
println("Purchase error: ${it.message}")
}
}
}
Transaction Not Completing
Problem: Purchase succeeds but transaction doesn't complete
Solution:
private suspend fun handlePurchaseUpdate(purchase: Purchase) {
// Assuming kmpIAP is available as class property or use kmpIapInstance
try {
// IMPORTANT: Always complete transactions
val success = kmpIapInstance.finishTransaction(
purchase = purchase,
isConsumable = isConsumable(purchase.productId)
)
if (success) {
println("Transaction completed successfully")
} else {
println("Failed to complete transaction")
}
// Clear purchase state
// No need to clear purchase with new API
} catch (e: Exception) {
println("Failed to complete transaction: $e")
}
}
"Already Owned" Error
Problem: Getting "already owned" error on Android
Solutions:
-
Check for existing purchases:
// For consumable products
scope.launch {
val purchases = kmpIapInstance.getAvailablePurchases()
purchases.filter { isConsumable(it.productId) }
.forEach { purchase ->
// Consume the purchase
kmpIapInstance.finishTransaction(
purchase = purchase,
isConsumable = true
)
}
} -
Clear test purchases:
- In Google Play Store app: Menu > Account > Purchase history
- Cancel test purchases
Testing Issues
Sandbox Testing Problems (iOS)
Problem: Sandbox purchases not working
Solutions:
-
Account management:
- Create fresh sandbox accounts in App Store Connect
- Don't sign in to sandbox account in Settings
- Only sign in when prompted during purchase
-
Purchase history:
- Clear purchase history in Settings > App Store > Sandbox Account
- Use different sandbox accounts for different test scenarios
-
Network issues:
- Test on real device with cellular/different WiFi
- Sandbox can be unstable, try multiple times
Test Purchases Not Working (Android)
Problem: Test purchases failing on Android
Solutions:
-
License testers:
Play Console > Setup > License testing > License Testers
Add Gmail accounts for testing -
Test tracks:
- Upload app to Internal Testing minimum
- Join testing program with test account
- Install from Play Store (not sideload)
-
Account verification:
- Use Gmail account added as license tester
- Clear Play Store cache/data if needed
Runtime Errors
StateFlow Collection Errors
Problem: Multiple collectors or flow errors
Solution:
class PurchaseManager : ViewModel() {
private val kmpIAP = KmpIAP()
init {
initializeIAP()
setupObservers()
}
private fun initializeIAP() {
viewModelScope.launch {
kmpIAP.initConnection()
}
}
private fun setupObservers() {
// Use viewModelScope for automatic cancellation
viewModelScope.launch {
kmpIAP.purchaseUpdatedListener.collect { purchase ->
handlePurchase(purchase)
}
}
viewModelScope.launch {
kmpIAP.purchaseErrorListener.collect { error ->
handleError(error)
}
}
}
override fun onCleared() {
super.onCleared()
// Dispose KmpIAP resources
kmpIAP.dispose()
}
}
Memory Leaks
Problem: App crashes or memory issues
Solution: Always clean up resources:
class IAPService : ViewModel() {
private val kmpIAP = KmpIAP()
init {
viewModelScope.launch {
kmpIAP.initConnection()
}
}
override fun onCleared() {
super.onCleared()
// Dispose IAP resources
kmpIAP.dispose()
}
}
Receipt Validation Issues
iOS Receipt Validation
Problem: Receipt validation fails
Solutions:
-
Receipt data retrieval:
private suspend fun validateIOSPurchase(purchase: Purchase) {
val receipt = purchase.transactionReceipt
if (receipt == null) {
println("No receipt data available")
return
}
// Send to your server for validation
val isValid = api.validateIOSReceipt(
receipt = receipt,
isProduction = !BuildConfig.DEBUG
)
} -
Server validation:
- Use production URL for live app:
https://buy.itunes.apple.com/verifyReceipt
- Use sandbox URL for testing:
https://sandbox.itunes.apple.com/verifyReceipt
- Use production URL for live app:
Android Receipt Validation
Problem: Purchase token validation fails
Solution:
// Use Google Play Developer API for server-side validation
private suspend fun validateAndroidPurchase(purchase: Purchase) {
val purchaseToken = purchase.purchaseToken
if (purchaseToken == null) {
println("No purchase token available")
return
}
// Send to your server
val isValid = api.validateAndroidPurchase(
token = purchaseToken,
productId = purchase.productId,
packageName = BuildConfig.APPLICATION_ID
)
}
Debug Tools
Enable Debug Logging
// Add logging to track IAP flow
class DebugIAPHelper(scope: CoroutineScope) {
private val kmpIAP = KmpIAP()
init {
scope.launch {
val connected = kmpIAP.initConnection()
println("[IAP] Connection state: $connected")
}
scope.launch {
kmpIAP.purchaseErrorListener.collect { error ->
println("[IAP] Error: ${error.code} - ${error.message}")
}
}
}
}
Check Connection Status
suspend fun debugConnection() {
try {
val connected = kmpIapInstance.initConnection()
println("Connection initialized: $connected")
// Test with known product
val testProductId = when (getCurrentPlatform()) {
IapPlatform.ANDROID -> "android.test.purchased"
IapPlatform.IOS -> "your.test.product"
}
val products = kmpIapInstance.requestProducts(
ProductRequest(
skus = listOf(testProductId),
type = ProductType.INAPP
)
)
println("Test products loaded: ${products.size}")
} catch (e: PurchaseError) {
println("Debug error: $e")
}
}
Common Error Codes
Understanding Error Codes
fun handleError(error: PurchaseError) {
when (error.code) {
ErrorCode.E_USER_CANCELLED.name -> {
// User cancelled - no action needed
}
ErrorCode.E_NETWORK_ERROR.name -> {
showRetryDialog("Network error. Please try again.")
}
ErrorCode.E_ITEM_ALREADY_OWNED.name -> {
// Refresh purchases
scope.launch {
kmpIapInstance.getAvailablePurchases()
}
}
ErrorCode.E_SERVICE_DISCONNECTED.name -> {
// Reconnect
scope.launch {
kmpIapInstance.initConnection()
}
}
else -> {
showError("Purchase failed: ${error.message}")
}
}
}
Getting Help
If you're still experiencing issues:
- Check logs: Enable debug logging and check console output
- Minimal reproduction: Create minimal example that reproduces issue
- Platform testing: Test on both iOS and Android
- Version check: Ensure you're using latest library version
- GitHub Issues: Report bugs with detailed information
Issue Report Template
When reporting issues, include:
**Platform:** iOS/Android/Both
**Library Version:** kmp-iap x.x.x
**Kotlin Version:** x.x.x
**Device:** Real device/Simulator/Emulator
**Issue Description:**
Clear description of the problem
**Steps to Reproduce:**
1. Step one
2. Step two
3. Step three
**Expected Behavior:**
What should happen
**Actual Behavior:**
What actually happens
**Logs:**
```text
Relevant console output
Sample Code:
// Minimal code that reproduces the issue
This comprehensive troubleshooting guide should help developers resolve most common issues with kmp-iap.