We're thrilled to announce the most comprehensive update to KMP-IAP yet! Version 1.0.0-beta brings complete platform parity, 100% compliance with the OpenIAP specification, improved naming conventions, and extensive field additions for both Android and iOS platforms.
๐ข OpenIAP Specification: The New Standardโ
KMP-IAP v1.0.0-beta achieves 100% compliance with the OpenIAP specification, providing a standardized approach to in-app purchases across different platforms and libraries. By adopting this specification, KMP-IAP now offers:
- Standardized Types: Consistent data structures across platforms
- Unified API: Common interface patterns for all IAP operations
- Cross-Platform Compatibility: Seamless integration with other OpenIAP-compliant libraries
- Future-Proof Architecture: Ready for emerging platforms and specifications
Visit openiap.dev to learn more about the specification.
๐ What's Newโ
Enhanced Android Product Supportโ
Complete Google Play Billing ProductDetails
API parity with new fields:
Product Type Fieldsโ
typeAndroid
:String?
- Product type ("inapp" or "subs") fromproductDetails.productType
nameAndroid
:String?
- Product display name fromproductDetails.name
(different from title)displayPriceAndroid
:String?
- Formatted display price ready for UI
One-Time Purchase Detailsโ
oneTimePurchaseOfferDetails
:OneTimePurchaseOfferDetails?
- Complete pricing information:data class OneTimePurchaseOfferDetails(
val priceCurrencyCode: String, // ISO 4217 currency code (e.g., "USD")
val formattedPrice: String, // Human-readable price (e.g., "$0.99")
val priceAmountMicros: String // Price in micros (e.g., "990000")
)
Enhanced iOS Transaction Supportโ
Following StoreKit 2 Transaction API with comprehensive field mapping for iOS 15.0+:
Core iOS Fields (Already Supported)โ
quantityIOS
: Purchase quantityoriginalTransactionDateIOS
: Original purchase dateoriginalTransactionIdIOS
: Original transaction identifierappBundleIdIOS
: App bundle identifierproductTypeIOS
: Product type (consumable, non-consumable, etc.)subscriptionGroupIdIOS
: Subscription group identifier
Enhanced iOS Fields for Future StoreKit 2 Supportโ
When StoreKit 2 support is added, these fields will be available:
signedDateIOS
: Transaction signing datedeviceVerificationIOS
: Device verification datadeviceVerificationNonceIOS
: Device verification nonceofferIdIOS
: Promotional offer identifierofferTypeIOS
: Offer type (introductory, promotional, code)subscriptionPeriodIOS
: Subscription period unitenvironmentIOS
: Store environment (Sandbox/Production) - iOS 16.0+storefrontCountryCodeIOS
: Storefront country code - iOS 17.0+reasonIOS
: Transaction reason - iOS 17.0+
Enhanced Purchase Type Supportโ
Comprehensive purchase data capture with new Android fields:
New Android Purchase Fieldsโ
dataAndroid
:String?
- Original JSON data from purchaseobfuscatedAccountIdAndroid
:String?
- Account identifier for purchase attributionobfuscatedProfileIdAndroid
:String?
- Profile identifier for user segmentation
Enhanced Error Handlingโ
subResponseCode
:Int?
- Android billing v8.0.0+ sub-response code for detailed error infosubResponseMessage
:String?
- Human-readable message for sub-response codes
// Example: Enhanced error handling
try {
val purchase = kmpIapInstance.requestPurchase(request)
} catch (error: PurchaseError) {
when (error.subResponseCode) {
1 -> println("Payment declined: ${error.subResponseMessage}")
// Handle specific billing errors
else -> println("General error: ${error.message}")
}
}
๐ฏ Complete Type System Overhaulโ
OpenIAP-Compliant Base Typesโ
Following the OpenIAP specification, all types now implement standardized interfaces:
// ProductCommon interface - OpenIAP base specification
interface ProductCommon {
val id: String // Unified product identifier
val title: String // Product title
val description: String // Product description
val type: ProductType // "inapp" or "subs"
val displayName: String? // Optional display name
val displayPrice: String // Formatted price for display
val currency: String // ISO currency code
val price: Double? // Numeric price value
val debugDescription: String?
val platform: String? // Platform identifier
}
// PurchaseCommon interface - OpenIAP base specification
interface PurchaseCommon {
val id: String // Transaction identifier
val productId: String // Product that was purchased
val ids: List<String>? // Multiple product IDs (Android)
val transactionId: String? // @deprecated - use id instead
val transactionDate: Double // Unix timestamp
val transactionReceipt: String
val purchaseToken: String? // Unified token field
val platform: String?
}
Platform-Specific Implementation Typesโ
Following OpenIAP naming conventions with proper platform suffixes:
// iOS Product (ProductIOS)
data class ProductIOS(
// ProductCommon fields
override val id: String,
override val title: String,
// ... other base fields
// iOS-specific fields with IOS suffix
val displayNameIOS: String,
val isFamilyShareableIOS: Boolean,
val jsonRepresentationIOS: String,
val subscriptionInfoIOS: SubscriptionInfoIOS? = null,
// Backward compatibility (deprecated)
@Deprecated("Use displayNameIOS") val displayName: String? = null,
override val platform: String = "ios"
) : ProductCommon
// Android Product (ProductAndroid)
data class ProductAndroid(
// ProductCommon fields
override val id: String,
override val title: String,
// ... other base fields
// Android-specific fields with Android suffix
val nameAndroid: String,
val oneTimePurchaseOfferDetailsAndroid: ProductAndroidOneTimePurchaseOfferDetail? = null,
val subscriptionOfferDetailsAndroid: List<ProductSubscriptionAndroidOfferDetail>? = null,
// Backward compatibility (deprecated)
@Deprecated("Use nameAndroid") val name: String? = null,
override val platform: String = "android"
) : ProductCommon
๐ Improved Naming Conventionsโ
Consistent Platform Suffixesโ
Following OpenIAP and our internal CLAUDE.md guidelines:
// โ
CORRECT: Platform suffix at the end
val quantityIOS: Int
val environmentIOS: String
val appBundleIdIOS: String
val purchaseTokenAndroid: String
val packageNameAndroid: String
// โ INCORRECT: Platform prefix
val iosQuantity: Int
val androidPurchaseToken: String
Type Name Changesโ
Old Name | New Name |
---|---|
IosTransactionState | TransactionStateIOS |
IosSubscriptionPeriodUnit | SubscriptionPeriodUnitIOS |
IosDiscountPaymentMode | DiscountPaymentModeIOS |
IosDiscountType | DiscountTypeIOS |
SubscriptionIosPeriod | SubscriptionPeriodIOS |
IapPlatform | IapPlatform |
ID Naming Consistencyโ
// โ
CORRECT: Use "Id" not "ID"
val productId: String
val transactionId: String
val subscriptionGroupId: String
val orderIdAndroid: String
val originalTransactionIdIOS: String
// โ INCORRECT: Using "ID"
val productID: String
val transactionID: String
IAP Acronym Usageโ
// โ
CORRECT: IAP as final word
class KmpIAP
val kmpIAP = KmpIAP()
// โ
CORRECT: Iap when followed by other words
val kmpIapInstance: KmpIAP
enum class IapPlatform { IOS, ANDROID }
// โ INCORRECT: Inconsistent usage
val kmpIAPInstance: KmpIAP
๐ Platform API Mappingโ
Android Product Mappingโ
// Native Android ProductDetails mapping
mapOf(
"id" to productDetails.productId, // -> id
"title" to productDetails.title, // -> title
"description" to productDetails.description, // -> description
"type" to productDetails.productType, // -> typeAndroid
"displayName" to productDetails.name, // -> nameAndroid
"displayPrice" to displayPrice, // -> displayPriceAndroid
"oneTimePurchaseOfferDetails" to offerDetails, // -> oneTimePurchaseOfferDetails
"subscriptionOfferDetails" to subscriptions // -> subscriptionOfferDetails
)
Android Purchase Mappingโ
// Native Android Purchase mapping
val purchaseData = mapOf(
"id" to purchase.orderId, // -> id
"productId" to purchase.products.first(), // -> productId
"purchaseToken" to purchase.purchaseToken, // -> purchaseToken
"dataAndroid" to purchase.originalJson, // -> dataAndroid โจ NEW
"signatureAndroid" to purchase.signature, // -> signatureAndroid
"obfuscatedAccountId" to accountId, // -> obfuscatedAccountIdAndroid โจ NEW
"obfuscatedProfileId" to profileId // -> obfuscatedProfileIdAndroid โจ NEW
)
iOS Transaction Support (Current + Future)โ
// Current StoreKit 1 support + Future StoreKit 2 fields
let transactionData = [
"id": transaction.id, // -> id
"productId": transaction.productID, // -> productId
"quantityIOS": transaction.purchasedQuantity, // -> quantityIOS
// Future StoreKit 2 fields
"signedDateIOS": transaction.signedDate, // -> signedDateIOS
"deviceVerificationIOS": deviceVerification, // -> deviceVerificationIOS
"offerIdIOS": transaction.offerID, // -> offerIdIOS
"environmentIOS": transaction.environment // -> environmentIOS (iOS 16.0+)
]
๐ฑ Usage Examplesโ
Enhanced Android Product Usageโ
val products = kmpIapInstance.requestProducts(
ProductRequest(listOf("premium_upgrade"), ProductType.INAPP)
)
products.forEach { product ->
// Use enhanced Android fields
val displayName = product.nameAndroid ?: product.title
val displayPrice = product.displayPriceAndroid ?: product.price
// Check product type
when (product.typeAndroid) {
"inapp" -> println("One-time purchase: $displayName")
"subs" -> println("Subscription: $displayName")
}
// Access detailed pricing for one-time purchases
product.oneTimePurchaseOfferDetails?.let { offer ->
println("Price: ${offer.formattedPrice}")
println("Currency: ${offer.priceCurrencyCode}")
// Convert micros to decimal
val actualPrice = offer.priceAmountMicros.toLong() / 1_000_000.0
println("Decimal price: $actualPrice")
}
}
Enhanced Purchase Handlingโ
// Listen for purchase updates with enhanced data
kmpIapInstance.purchaseUpdatedListener.collect { purchase ->
// Validate receipt with your backend
val receiptData = PurchaseReceiptData(
purchaseToken = purchase.purchaseToken,
originalJson = purchase.dataAndroid, // โจ NEW: Full purchase data
signature = purchase.signatureAndroid,
accountId = purchase.obfuscatedAccountIdAndroid, // โจ NEW: User attribution
profileId = purchase.obfuscatedProfileIdAndroid // โจ NEW: Profile tracking
)
val isValid = validateReceiptOnServer(receiptData)
if (isValid) {
grantEntitlement(purchase.productId)
kmpIapInstance.finishTransaction(purchase, isConsumable = true)
}
}
Enhanced Error Handlingโ
kmpIapInstance.purchaseErrorListener.collect { error ->
when (error.code) {
"E_USER_CANCELLED" -> showUserCancelledMessage()
"E_PAYMENT_DECLINED" -> {
// Enhanced Android error handling
when (error.subResponseCode) {
1 -> showInsufficientFundsMessage(error.subResponseMessage)
else -> showGenericPaymentError(error.message)
}
}
else -> showGenericError(error.message)
}
}
๐ Migration Guideโ
Unified Purchase Request Structureโ
Replace old request structures with OpenIAP-compliant ones:
// โ OLD: UnifiedPurchaseRequest (deprecated)
val purchase = kmpIapInstance.requestPurchase(
UnifiedPurchaseRequest(
sku = "premium",
quantity = 1
)
)
// โ
NEW: OpenIAP-compliant RequestPurchaseProps
val purchase = kmpIapInstance.requestPurchase(
RequestPurchaseProps(
ios = RequestPurchaseIosProps(
sku = "premium",
quantity = 1
),
android = RequestPurchaseAndroidProps(
skus = listOf("premium")
)
)
)
Type Names (Automatic via Type Aliases)โ
The library provides type aliases for renamed types, so existing code continues to work:
// These imports automatically use the new names
import io.github.hyochan.kmpiap.*
// Your existing code works unchanged
val state: TransactionStateIOS = TransactionStateIOS.PURCHASED
New Optional Fieldsโ
All new fields are optional (nullable
), so no migration is required:
// Existing code works unchanged
val purchase = kmpIapInstance.requestPurchase(request)
// New fields available when needed
val accountId = purchase.obfuscatedAccountIdAndroid // null if not available
val originalData = purchase.dataAndroid // null if not available
Unified Purchase Token Accessโ
// โ
NEW: Unified purchaseToken field
val token = purchase.purchaseToken
// โ
FALLBACK: Platform-specific deprecated fields still work
val tokenFallback = (purchase as? PurchaseAndroid)?.purchaseTokenAndroid
?: (purchase as? PurchaseIOS)?.jwsRepresentationIOS
๐ฏ Why These Changes Matterโ
OpenIAP Standards Complianceโ
- Industry Standard: Follows the OpenIAP specification for cross-library compatibility
- Interoperability: Libraries can work together seamlessly
- Community Standards: Shared best practices across the ecosystem
- Innovation: Focus on features, not API design
Complete Platform Parityโ
- Android: Full Google Play Billing API coverage
- iOS: Comprehensive StoreKit field mapping (current + future StoreKit 2)
- Consistent: Unified API across platforms
Better Developer Experienceโ
- Type Safety: All fields properly typed with clear nullability
- Consistent Naming: Platform suffixes make code more readable
- Enhanced Documentation: Every field documented with usage examples
- Easier Migration: Move between OpenIAP-compliant libraries seamlessly
Improved App Qualityโ
- Better User Experience: Access to localized names and formatted prices
- Enhanced Analytics: User attribution with account/profile IDs
- Robust Error Handling: Detailed error codes for better UX
- Reduced Bugs: Standardized error codes and handling
Future-Proofโ
- StoreKit 2 Ready: Field structure prepared for StoreKit 2 migration
- Extensible: Easy to add new platform-specific fields
- Backward Compatible: Existing code continues to work
- Ready for New Platforms: Architecture supports emerging platforms
๐ Complete API Referenceโ
Product Type Fieldsโ
data class Product(
// Core fields
val id: String,
val title: String,
val description: String,
val price: String,
val priceAmount: Double,
val currency: String,
// iOS-specific fields
val displayName: String? = null,
val isFamilyShareable: Boolean = false,
val discounts: List<Discount>? = null,
// Android-specific fields โจ
val nameAndroid: String? = null, // Product display name
val typeAndroid: String? = null, // "inapp" or "subs"
val displayPriceAndroid: String? = null, // Formatted price
val oneTimePurchaseOfferDetails: OneTimePurchaseOfferDetails? = null,
val subscriptionOfferDetails: List<OfferDetail>? = null,
val platform: IapPlatform
)
Purchase Type Fieldsโ
data class Purchase(
// Core fields
val id: String,
val productId: String,
val transactionDate: Double,
val transactionReceipt: String,
val purchaseToken: String? = null,
// iOS-specific fields
val quantityIOS: Int? = null,
val originalTransactionDateIOS: Double? = null,
val originalTransactionIdIOS: String? = null,
// ... other iOS fields
// Android-specific fields โจ
val purchaseStateAndroid: Int? = null,
val signatureAndroid: String? = null,
val autoRenewingAndroid: Boolean? = null,
val acknowledgedAndroid: Boolean? = null,
val dataAndroid: String? = null, // โจ NEW
val obfuscatedAccountIdAndroid: String? = null, // โจ NEW
val obfuscatedProfileIdAndroid: String? = null, // โจ NEW
val platform: IapPlatform
)
Error Type Fieldsโ
class PurchaseError(
val code: String,
override val message: String,
val productId: String? = null,
val responseCode: Int? = null,
val debugMessage: String? = null,
val platform: IapPlatform? = null,
val subResponseCode: Int? = null, // โจ NEW: Android v8.0.0+
val subResponseMessage: String? = null // โจ NEW: Detailed error message
) : Exception(message)
๐ Get Startedโ
Update to KMP-IAP v1.0.0-beta today:
implementation("io.github.hyochan:kmp-iap:1.0.0-beta.14")
Documentation Linksโ
- OpenIAP Specification - Learn about the standard
- Complete API Documentation - Complete API reference
๐ What's Nextโ
OpenIAP Ecosystem Integrationโ
- Cross-library compatibility testing
- Shared validation utilities
- Common testing frameworks
Enhanced Standards Complianceโ
- Receipt validation standardization
- Promotional offers specification
- Subscription management patterns
Platform Extensionsโ
- StoreKit 2 Implementation: Complete iOS StoreKit 2 support with all enhanced fields
- Advanced Subscription Management: Enhanced subscription lifecycle APIs
- Promotional Offers: Comprehensive promotional offer handling for both platforms
- Web IAP Integration: Support for web platforms
- Desktop Platform Support: Native desktop IAP integration
Have questions or feedback? Join the discussion on GitHub or contribute to the project!
For new feature proposals, discuss at OpenIAP Discussions to ensure alignment with standards.
This update represents months of work to provide the most comprehensive and standards-compliant cross-platform IAP solution available. Thank you to all contributors and users who made this possible!