Frequently Asked Questions
Common questions and answers about kmp-iap.
General Questions
Q: What platforms does kmp-iap support?
A: The library supports:
- iOS: 15.0+ with StoreKit 2
- Android: API 21+ (Android 5.0) with Google Play Billing Library v7+
- Desktop & Web: Planned for future releases
Windows, Linux, and Web support is on the roadmap as Kotlin Multiplatform expands to these platforms.
Q: How is kmp-iap different from flutter_inapp_purchase?
A: Key differences:
- Technology: kmp-iap uses Kotlin Multiplatform vs Flutter/Dart
- State Management: StateFlow vs Streams
- Integration: Works with Compose Multiplatform, native Android/iOS
- API Design: Coroutines-based vs Future/async-await
Q: Is this library free to use?
A: Yes, kmp-iap is open source and free to use under the MIT license. However, both Apple and Google charge fees for in-app purchases (typically 15-30%).
Setup & Configuration
Q: Do I need to configure anything in Xcode or Android Studio?
A: Yes, minimal setup is required:
iOS:
- Enable In-App Purchase capability in Xcode
- Configure products in App Store Connect
Android:
- Add
<uses-permission android:name="com.android.vending.BILLING" />
to AndroidManifest.xml - Configure products in Google Play Console
See our setup guides for detailed instructions.
Q: Can I test purchases without publishing my app?
A: Yes:
iOS: Use sandbox testing with sandbox Apple IDs Android: Upload to Internal Testing track in Play Console
Both platforms require proper store setup but don't require public app release.
Q: How long does it take for products to appear after configuration?
A: Product availability varies:
- iOS: Usually within a few hours, up to 24 hours
- Android: Can take 24-48 hours after app upload
Products must be properly configured and approved in the respective stores.
Products & Subscriptions
Q: What's the difference between consumable and non-consumable products?
A:
- Consumable: Can be purchased multiple times (coins, gems, power-ups)
- Non-consumable: Purchased once, owned forever (remove ads, premium features)
- Subscriptions: Recurring purchases with auto-renewal
Q: How do I handle different product types?
A: Use the isConsumable
parameter:
// For consumable products
val success = kmpIapInstance.finishTransaction(
purchase = purchase,
isConsumable = true
)
// For non-consumable products and subscriptions
val success = kmpIapInstance.finishTransaction(
purchase = purchase,
isConsumable = false
)
Q: Can I offer subscription trials?
A: Yes, but setup varies by platform:
- iOS: Configure introductory offers in App Store Connect
- Android: Configure free trials in Play Console
The library will return trial information in the product data.
Q: How do I handle subscription renewals?
A: Subscriptions auto-renew by default. To check status:
scope.launch {
val purchases = kmpIapInstance.getAvailablePurchases()
val activeSubscriptions = purchases.filter { purchase ->
subscriptionIds.contains(purchase.productId) &&
isActive(purchase)
}
}
}
Implement server-side receipt validation for accurate expiration checking.
Purchases & Transactions
Q: Why isn't my purchase completing?
A: Common causes:
- Not finishing transactions: Always call
finishTransaction()
- No state observers: Set up StateFlow collectors before requesting purchases
- Network issues: Ensure device has internet connectivity
- Account issues: Verify store account is properly set up
Q: How do I restore purchases?
A: Purchases are automatically available via StateFlow:
suspend fun restorePurchases() {
try {
// Get available purchases
val purchases = kmpIapInstance.getAvailablePurchases()
purchases.forEach { purchase ->
// Re-deliver non-consumable products
if (isNonConsumable(purchase.productId)) {
deliverProduct(purchase)
}
}
} catch (e: PurchaseError) {
println("Restore failed: $e")
}
}
Q: Can users purchase the same product multiple times?
A: Depends on product type:
- Consumable: Yes, after finishing transaction as consumable
- Non-consumable: No, will get
PRODUCT_ALREADY_OWNED
error - Subscription: Can upgrade/downgrade, but not duplicate
Q: How do I handle pending purchases on Android?
A: Monitor purchase state:
private fun handlePurchase(purchase: Purchase) {
when (purchase.purchaseState) {
PurchaseState.PURCHASED -> {
// Purchase completed
deliverProduct(purchase)
}
PurchaseState.PENDING -> {
// Purchase pending - show pending UI
showPendingMessage()
}
}
}
Security & Validation
Q: Do I need to validate receipts?
A: Yes, for production apps you should always validate receipts server-side to prevent fraud. The library provides receipt data, but validation must be implemented separately.
Q: How do I validate iOS receipts?
A: Send receipt to Apple's verification servers:
// Get receipt data
val receipt = purchase.transactionReceipt
// Send to your server for validation with Apple
val isValid = api.validateIOSReceipt(
receipt = receipt,
sharedSecret = "your-shared-secret"
)
Q: How do I validate Android purchases?
A: Use the purchase token with Google Play Developer API:
// Get purchase token
val token = purchase.purchaseToken
// Validate on your server using Google Play Developer API
val isValid = api.validateAndroidPurchase(
token = token,
productId = purchase.productId
)
Q: Can I trust client-side purchase data?
A: No, never trust client-side data for security-critical operations. Always validate receipts server-side before granting paid content.
Error Handling
Q: What does "Billing is unavailable" mean?
A: This indicates the billing system isn't ready. Common causes:
- Google Play Store not installed/updated (Android)
- App not uploaded to store (Android)
- Network connectivity issues
- Store service temporarily unavailable
Q: Why do I get "Product not found" errors?
A: Product ID mismatches are common:
- Verify exact product ID spelling
- Check product is active in store console
- Wait for product propagation (up to 24 hours)
- Ensure app bundle ID matches store configuration
Q: How do I handle purchase cancellations?
A: Monitor error state:
scope.launch {
kmpIapInstance.purchaseErrorListener.collect { error ->
when (error.code) {
ErrorCode.E_USER_CANCELLED.name -> {
// User cancelled - no error message needed
}
else -> {
// Show error message
showErrorDialog(error.message)
}
}
// No need to clear error with new API
}
}
Development & Testing
Q: Can I test purchases on simulators/emulators?
A:
- iOS Simulator: Limited support, use StoreKit testing
- Android Emulator: Not recommended, use real devices
- Best practice: Always test on real devices with test accounts
Q: How do I test subscriptions?
A: Both platforms offer accelerated testing:
- iOS: Subscriptions renew every few minutes in sandbox
- Android: Test subscriptions renew quickly in test environment
Q: Do test purchases cost real money?
A: No:
- iOS: Sandbox purchases are free
- Android: License tester purchases are free and auto-refund
Q: How do I clear test purchase history?
A:
- iOS: Settings > App Store > Sandbox Account > Reset
- Android: Google Play Store > Account > Purchase history (cancel test purchases)
Performance & Best Practices
Q: When should I initialize the IAP connection?
A: Initialize as early as possible, typically in your ViewModel initialization. Don't initialize on every screen.
Q: How do I handle app lifecycle events?
A: Clean up properly:
class PurchaseViewModel : ViewModel() {
private val kmpIAP = KmpIAP()
init {
viewModelScope.launch {
kmpIAP.initConnection()
}
}
override fun onCleared() {
super.onCleared()
kmpIAP.dispose()
}
}
Kotlin Multiplatform Specific
Q: Can I use kmp-iap with SwiftUI?
A: Yes, the library works with SwiftUI through Kotlin/Native interop:
// In your SwiftUI view
let iapManager = IAPManager() // Your KMP wrapper
Button("Purchase") {
iapManager.purchaseProduct(productId: "premium")
}
Q: Does it work with Compose Multiplatform?
A: Yes, kmp-iap is designed to work seamlessly with Compose Multiplatform:
@Composable
fun PurchaseButton(productId: String) {
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
kmpIapInstance.requestPurchase(
UnifiedPurchaseRequest(
sku = productId,
quantity = 1
)
)
}
}) {
Text("Purchase")
}
}
Q: How do I share IAP logic between platforms?
A: Create a common interface:
// In commonMain
expect class IAPManager {
suspend fun purchaseProduct(productId: String)
fun observePurchases(): Flow<Purchase?>
}
// Platform-specific implementations
actual class IAPManager {
// iOS/Android specific implementation
}
Troubleshooting
Q: My app was rejected for IAP issues. What should I check?
A: Common rejection reasons:
- Missing restore functionality: Always provide restore purchases option
- Incorrect product types: Ensure consumable/non-consumable types match usage
- Price display: Show localized prices from store data
- Terms compliance: Follow platform guidelines for IAP UI
Q: Why are my products not loading in production but work in testing?
A: Check:
- App review status: App must be approved and live
- Product review status: Products must be approved
- Regional availability: Products might not be available in all regions
- Time delay: Products can take 24+ hours to propagate globally
Q: How do I debug IAP issues?
A: Enable debug logging:
// Add logging to your IAP operations
scope.launch {
kmpIapInstance.purchaseErrorListener.collect { error ->
println("[IAP Debug] Error: ${error.code} - ${error.message}")
}
}
Check console output for detailed error information.
Support & Community
Q: Where can I get help?
A: Multiple support channels:
- GitHub Issues for bugs
- GitHub Discussions for questions
- Discord Community for real-time chat
Q: How do I report bugs?
A: Create detailed GitHub issues with:
- Platform and version information
- Steps to reproduce
- Expected vs actual behavior
- Relevant code snippets
- Console logs with debug mode enabled
Q: Can I contribute to the project?
A: Yes! Contributions are welcome:
- Report bugs and issues
- Submit pull requests for fixes
- Improve documentation
- Help answer community questions
See the Contributing Guide for details.
Still have questions? Check our Troubleshooting Guide or open a discussion on GitHub.