Skip to main content

Comprehensive API Enhancements & OpenIAP Compliance in KMP-IAP v1.0.0-beta

ยท 11 min read
KMP-IAP Team

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") from productDetails.productType
  • nameAndroid: String? - Product display name from productDetails.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 quantity
  • originalTransactionDateIOS: Original purchase date
  • originalTransactionIdIOS: Original transaction identifier
  • appBundleIdIOS: App bundle identifier
  • productTypeIOS: 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 date
  • deviceVerificationIOS: Device verification data
  • deviceVerificationNonceIOS: Device verification nonce
  • offerIdIOS: Promotional offer identifier
  • offerTypeIOS: Offer type (introductory, promotional, code)
  • subscriptionPeriodIOS: Subscription period unit
  • environmentIOS: 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 purchase
  • obfuscatedAccountIdAndroid: String? - Account identifier for purchase attribution
  • obfuscatedProfileIdAndroid: String? - Profile identifier for user segmentation

Enhanced Error Handlingโ€‹

  • subResponseCode: Int? - Android billing v8.0.0+ sub-response code for detailed error info
  • subResponseMessage: 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 NameNew Name
IosTransactionStateTransactionStateIOS
IosSubscriptionPeriodUnitSubscriptionPeriodUnitIOS
IosDiscountPaymentModeDiscountPaymentModeIOS
IosDiscountTypeDiscountTypeIOS
SubscriptionIosPeriodSubscriptionPeriodIOS
IapPlatformIapPlatform

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")

๐ŸŽ‰ 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!