Kotlin Multiplatform Setup
kmp-iap brings OpenIAP-compliant in-app purchases to Kotlin Multiplatform projects. Android talks to Google Play Billing directly; iOS links the OpenIAP StoreKit framework, added with either CocoaPods or Swift Package Manager (see iOS Configuration below). Requires iOS 15.0+ and the Android minSdk shown in Android Configuration.
Prerequisites#
- Kotlin 2.4.10, Gradle 9.3.0, and JDK 17+
- Active Apple Developer account (for iOS)
- Active Google Play Developer account (for Android)
- Physical device for testing (simulators have limited IAP support)
Installation#
Add kmp-iap to your shared module's build.gradle.kts:
val commonMain by getting {
dependencies {
implementation("io.github.hyochan:kmp-iap:3.5.0")
}
}Or if using version catalogs:
# gradle/libs.versions.toml
[versions]
kmp-iap = "3.5.0"
[libraries]
kmp-iap = { module = "io.github.hyochan:kmp-iap", version.ref = "kmp-iap" }// build.gradle.kts
dependencies {
implementation(libs.kmp.iap)
}Platform Configuration#
iOS Configuration#
kmp-iap uses the OpenIAP framework on iOS. Choose CocoaPods or Swift Package Manager:
Option A: CocoaPods (Recommended)
Ensure your shared module has the CocoaPods plugin:
// shared/build.gradle.kts
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
}
kotlin {
cocoapods {
version = "1.0"
ios.deploymentTarget = "15.0"
framework {
baseName = "ComposeApp" // or "shared"
isStatic = true
}
}
}Then run cd iosApp && pod install and always open .xcworkspace (not .xcodeproj).
Option B: Swift Package Manager
- In Xcode: File > Add Package Dependencies
- Enter URL:
https://github.com/hyodotdev/openiap.git - Select "Up to Next Major" version
- Add to your iOS app target
- Verify in Build Phases > Link Binary with Libraries
Enable In-App Purchase Capability
In Xcode: Target > Signing & Capabilities > + Capability > In-App Purchase
Configure Info.plist (optional)
Declaring itms-apps in iosApp/Info.plist is only needed when your own code checks App Store links before opening them — kmp-iap itself does not require it:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>itms-apps</string>
</array>Android Configuration#
Update your androidApp/build.gradle.kts:
android {
compileSdk = 36
defaultConfig {
minSdk = 24 // Required minimum
targetSdk = 36
}
}ProGuard Rules (if using ProGuard)
# In-App Purchase
-keep class com.android.billingclient.** { *; }
-keep class io.github.hyochan.kmpiap.** { *; }
-keepattributes *Annotation*Usage#
Results stream through Kotlin Flows: initialize the connection (initConnection), attach the purchase and error flows (purchaseUpdatedListener and purchaseErrorListener), then fetch and purchase (fetchProducts, requestPurchase, finishTransaction). A successful initConnection() followed by a non-empty fetchProducts() result is the quickest way to confirm the platform setup above is working. For the full flow, see the Purchase Guide.
Creating an Instance#
Two patterns are supported. The connection, fetch, and purchase APIs are suspend functions — call them from a coroutine scope:
// Option 1: Global instance (convenient)
import io.github.hyochan.kmpiap.kmpIapInstance
scope.launch { kmpIapInstance.initConnection() }
// Option 2: Constructor (for DI / testing)
import io.github.hyochan.kmpiap.KmpIAP
val kmpIAP = KmpIAP()
scope.launch { kmpIAP.initConnection() }See initConnection for parameters and per-store behavior.
Flow-Based Architecture#
KMP IAP delivers purchase results through hot Kotlin Flows — despite the names, purchaseUpdatedListener and purchaseErrorListener are Flows, not one-shot callbacks. Collect both in a long-lived coroutine scope before requesting a purchase; events are emitted as they occur.
import io.github.hyochan.kmpiap.KmpIAP
import kotlinx.coroutines.*
val kmpIAP = KmpIAP()
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
scope.launch { kmpIAP.initConnection() }
// Collect in separate coroutines (collect is suspending and never returns)
scope.launch {
kmpIAP.purchaseUpdatedListener.collect { purchase ->
// Validate receipt with your backend or IAPKit
// CRITICAL: Android auto-refunds after 3 days if not called!
kmpIAP.finishTransaction(purchase = purchase, isConsumable = true)
}
}
scope.launch {
kmpIAP.purchaseErrorListener.collect { error ->
println("Error: ${error.code} - ${error.message}")
}
}For the possible error.code values, see Error Codes.
Products and Purchase#
Fetch products once the connection is up, then request a purchase — the result arrives on purchaseUpdatedListener, not as a return value. See fetchProducts and requestPurchase for parameters and per-store behavior.
import io.github.hyochan.kmpiap.openiap.*
scope.launch {
// Fetch products
val products = kmpIAP.fetchProducts(
ProductRequest(
skus = listOf("premium", "coins_100"),
type = ProductQueryType.InApp
)
)
// Purchase — the result arrives on purchaseUpdatedListener
kmpIAP.requestPurchase(
RequestPurchaseProps(
request = RequestPurchaseProps.Request.Purchase(
RequestPurchasePropsByPlatforms(
apple = RequestPurchaseIosProps(
sku = "premium"
),
google = RequestPurchaseAndroidProps(
skus = listOf("premium")
)
)
),
type = ProductQueryType.InApp
)
)
}Call endConnection() when the owning screen or scope is disposed — not right after requestPurchase, or the connection may close before the purchase result arrives.
Troubleshooting#
Linking error: Undefined symbol OpenIapModule (iOS)
This means the OpenIAP framework isn't linked.
- CocoaPods: Run
cd iosApp && pod installand open.xcworkspace(not.xcodeproj) - SPM: Verify OpenIAP appears in Build Phases > Link Binary with Libraries
- Clean build folder and rebuild
Products not found
- Ensure all agreements are signed in App Store Connect / Google Play Console
- Verify banking, legal, and tax information is complete and approved
- Check that bundle ID / package name matches exactly
- Products must be in "Ready to Submit" status (Apple) or "Active" (Google)
- Wait 15-30 minutes after creating products before testing
Billing unavailable (Android)
- Test on a real device, not an emulator
- Ensure Google Play Store is installed and updated
- App must be signed with the same certificate uploaded to Play Console
Next Steps#
- Purchase Guide — Complete purchase flow with validation and receipt verification
- Subscription Guide — Subscription offers, renewal, and management
- Error Codes — Full error reference and handling strategies
- API Reference — All available APIs with multi-language examples
- Store Setup — support boundaries and build configuration for alternative store targets: Meta Quest (Horizon OS) and Amazon devices (Fire OS and Vega OS)
- Maven Central: kmp-iap | GitHub Source