Skip to main content
Version: v1.0.0-rc (Current)

Error Codes

Comprehensive error handling reference for kmp-iap. The library follows the OpenIAP specification for standardized error codes across platforms.

Error Types

PurchaseError

The main error class for all IAP-related exceptions.

data class PurchaseError(
val code: String, // Error code from ErrorCode enum
val message: String,
val productId: String? = null
) : Exception(message)

Properties:

  • code - Standardized error code string (from ErrorCode enum)
  • message - Human-readable error description
  • productId - Related product ID (optional)

OpenIAP Error Code Reference

kmp-iap implements all 27 standard OpenIAP error codes for consistent error handling across platforms.

ErrorCode Enum

enum class ErrorCode {
// General Errors
E_UNKNOWN, // Unknown error occurred
E_DEVELOPER_ERROR, // Developer configuration error

// User Action Errors
E_USER_CANCELLED, // User cancelled the purchase flow
E_USER_ERROR, // User-related error during purchase
E_DEFERRED_PAYMENT, // Payment was deferred (pending family approval, etc.)
E_INTERRUPTED, // Purchase flow was interrupted

// Product Errors
E_ITEM_UNAVAILABLE, // Product not available in store
E_PRODUCT_NOT_AVAILABLE, // Product SKU not found
E_PRODUCT_ALREADY_OWNED, // Non-consumable product already purchased
E_ALREADY_OWNED, // Item already owned by user

// Network & Service Errors
E_NETWORK_ERROR, // Network connection error
E_SERVICE_ERROR, // Store service error
E_REMOTE_ERROR, // Remote server error
E_CONNECTION_CLOSED, // Connection to store service was closed
E_IAP_NOT_AVAILABLE, // In-app purchase service not available
E_SYNC_ERROR, // Synchronization error with store

// Validation Errors
E_RECEIPT_FAILED, // Receipt validation failed
E_RECEIPT_FINISHED, // Receipt already processed/finished
E_RECEIPT_FINISHED_FAILED, // Failed to finish receipt processing
E_TRANSACTION_VALIDATION_FAILED, // Transaction validation failed

// Platform-Specific Errors
E_PENDING, // Purchase is pending approval (Android)
E_NOT_ENDED, // Transaction not finished (iOS)
E_NOT_PREPARED, // Store connection not initialized
E_ALREADY_PREPARED, // Store connection already initialized
E_BILLING_RESPONSE_JSON_PARSE_ERROR, // Failed to parse billing response (Android)
E_PURCHASE_ERROR, // General purchase error
E_ACTIVITY_UNAVAILABLE // Activity context not available (Android)
}

Error Code Details

General Errors

E_UNKNOWN

Description: Unknown error occurred
Platforms: iOS, Android
Recovery: Log and report error for debugging

when (error.code) {
ErrorCode.E_UNKNOWN.name -> {
// Log full error details
logger.error("Unknown IAP error", error)
showError("An unexpected error occurred. Please try again.")
}
}

E_DEVELOPER_ERROR

Description: Developer configuration error
Platforms: iOS, Android
Common Causes:

  • Incorrect bundle ID
  • Missing entitlements
  • Invalid signing
  • Product not configured in store
when (error.code) {
ErrorCode.E_DEVELOPER_ERROR.name -> {
if (BuildConfig.DEBUG) {
println("Developer error - check configuration:")
println("- App bundle ID matches store listing")
println("- In-app purchase entitlements enabled")
println("- Products configured in store console")
}
}
}

User Action Errors

E_USER_CANCELLED

Description: User cancelled the purchase flow
Platforms: iOS, Android
Recovery: No action needed - expected user behavior

kmpIapInstance.purchaseErrorListener.collect { error ->
when (error.code) {
ErrorCode.E_USER_CANCELLED.name -> {
// Don't show error - user intended to cancel
println("Purchase cancelled by user")
}
else -> showErrorDialog(error.message)
}
}

E_USER_ERROR

Description: User-related error during purchase
Platforms: iOS, Android
Common Causes:

  • User not signed in
  • Parental controls active
  • Payment method issues

E_DEFERRED_PAYMENT

Description: Payment was deferred (pending family approval, etc.)
Platforms: iOS (Ask to Buy), Android (Pending purchases)
Recovery: Wait for approval

when (error.code) {
ErrorCode.E_DEFERRED_PAYMENT.name -> {
showInfo("Purchase is pending approval. You'll be notified when approved.")
// Store pending purchase for later processing
savePendingPurchase(purchase)
}
}

E_INTERRUPTED

Description: Purchase flow was interrupted
Platforms: iOS, Android
Recovery: Retry the purchase

Product Errors

E_ITEM_UNAVAILABLE

Description: Product not available in store
Platforms: iOS, Android
Common Causes:

  • Product not approved
  • Regional restrictions
  • Product removed from store

E_PRODUCT_NOT_AVAILABLE

Description: Product SKU not found
Platforms: iOS, Android
Common Causes:

  • Invalid product ID
  • Typo in SKU
  • Product not yet published
try {
val products = kmpIapInstance.requestProducts(listOf("invalid_sku"))
} catch (e: PurchaseError) {
when (e.code) {
ErrorCode.E_PRODUCT_NOT_AVAILABLE.name -> {
println("Product not found. Check product ID configuration.")
}
}
}

E_PRODUCT_ALREADY_OWNED

Description: Non-consumable product already purchased
Platforms: iOS, Android
Recovery: Restore purchases

E_ALREADY_OWNED

Description: Item already owned by user
Platforms: iOS, Android
Recovery: Check purchase history or restore

when (error.code) {
ErrorCode.E_ALREADY_OWNED.name,
ErrorCode.E_PRODUCT_ALREADY_OWNED.name -> {
showInfo("You already own this product.")
// Refresh purchase state
kmpIapInstance.getAvailablePurchases()
}
}

Network & Service Errors

E_NETWORK_ERROR

Description: Network connection error
Platforms: iOS, Android
Recovery: Retry with exponential backoff

private suspend fun handleNetworkError() {
var retryCount = 0
val maxRetries = 3

while (retryCount < maxRetries) {
delay(1000L * (2.0.pow(retryCount).toLong()))
try {
kmpIapInstance.initConnection()
break // Success
} catch (e: PurchaseError) {
if (e.code != ErrorCode.E_NETWORK_ERROR.name || ++retryCount >= maxRetries) {
showError("Network error. Please check your connection.")
break
}
}
}
}

E_SERVICE_ERROR

Description: Store service error
Platforms: iOS, Android
Recovery: Retry later or check service status

E_REMOTE_ERROR

Description: Remote server error
Platforms: iOS, Android
Common Causes:

  • Backend validation server down
  • API timeout
  • Server configuration issues

E_CONNECTION_CLOSED

Description: Connection to store service was closed
Platforms: iOS, Android
Recovery: Re-initialize connection

E_IAP_NOT_AVAILABLE

Description: In-app purchase service not available
Platforms: iOS, Android
Common Causes:

  • IAP disabled on device
  • Restricted user account
  • Store app not installed (Android)

E_SYNC_ERROR

Description: Synchronization error with store
Platforms: iOS, Android
Recovery: Retry synchronization

Validation Errors

E_RECEIPT_FAILED

Description: Receipt validation failed
Platforms: iOS, Android
Common Causes:

  • Invalid receipt format
  • Signature verification failed
  • Receipt tampering detected
// Server-side validation example
suspend fun validatePurchase(purchase: Purchase): Boolean {
return try {
val response = api.validateReceipt(
receipt = purchase.transactionReceipt,
productId = purchase.productId
)
response.isValid
} catch (e: Exception) {
throw PurchaseError(
code = ErrorCode.E_RECEIPT_FAILED.name,
message = "Receipt validation failed"
)
}
}

E_RECEIPT_FINISHED

Description: Receipt already processed/finished
Platforms: iOS, Android
Recovery: Check transaction history

E_RECEIPT_FINISHED_FAILED

Description: Failed to finish receipt processing
Platforms: iOS, Android
Recovery: Retry finishing transaction

E_TRANSACTION_VALIDATION_FAILED

Description: Transaction validation failed
Platforms: iOS, Android
Common Causes:

  • Transaction data corrupted
  • Validation server error
  • Invalid transaction state

Platform-Specific Errors

E_PENDING (Android)

Description: Purchase is pending approval
Platform: Android
Recovery: Wait for purchase to be approved

when (error.code) {
ErrorCode.E_PENDING.name -> {
if (getCurrentPlatform() == Platform.ANDROID) {
showInfo("Purchase is pending. Check back later.")
}
}
}

E_NOT_ENDED (iOS)

Description: Transaction not finished
Platform: iOS
Recovery: Call finishTransaction()

when (error.code) {
ErrorCode.E_NOT_ENDED.name -> {
// Finish the pending transaction
kmpIapInstance.finishTransaction(purchase)
}
}

E_NOT_PREPARED

Description: Store connection not initialized
Platforms: iOS, Android
Recovery: Call initConnection() first

class IAPManager {
suspend fun ensureInitialized() {
if (!kmpIapInstance.isInitialized()) {
try {
kmpIapInstance.initConnection()
} catch (e: PurchaseError) {
if (e.code == ErrorCode.E_NOT_PREPARED.name) {
throw PurchaseError(
code = ErrorCode.E_NOT_PREPARED.name,
message = "Failed to initialize IAP connection"
)
}
}
}
}
}

E_ALREADY_PREPARED

Description: Store connection already initialized
Platforms: iOS, Android
Recovery: Use existing connection

E_BILLING_RESPONSE_JSON_PARSE_ERROR (Android)

Description: Failed to parse billing response
Platform: Android
Common Causes:

  • Corrupted response from Google Play
  • Library version mismatch

E_PURCHASE_ERROR

Description: General purchase error
Platforms: iOS, Android
Recovery: Check specific error details

E_ACTIVITY_UNAVAILABLE (Android)

Description: Activity context not available
Platform: Android
Common Causes:

  • App in background
  • Activity destroyed
  • No active activity
when (error.code) {
ErrorCode.E_ACTIVITY_UNAVAILABLE.name -> {
showError("Please ensure the app is in foreground and try again.")
}
}

Error Handling Best Practices

1. Centralized Error Handler

class IAPErrorHandler(
private val kmpIap: KmpIAP
) {
init {
scope.launch {
kmpIap.purchaseErrorListener.collect { error ->
handleError(error)
}
}
}

private fun handleError(error: PurchaseError) {
// Log error
logger.error("IAP Error: ${error.code}", error)

// Handle by type
when (error.code) {
ErrorCode.E_USER_CANCELLED.name -> {
// Silent - user action
}
ErrorCode.E_NETWORK_ERROR.name,
ErrorCode.E_SERVICE_ERROR.name -> {
showRetryableError(error)
}
ErrorCode.E_ALREADY_OWNED.name,
ErrorCode.E_PRODUCT_ALREADY_OWNED.name -> {
handleAlreadyOwned()
}
else -> {
showGenericError(error)
}
}
}
}

2. User-Friendly Messages

fun getErrorMessage(error: PurchaseError): String {
return ErrorCodeUtils.getErrorMessage(
ErrorCode.valueOf(error.code)
)
}

3. Error Recovery Strategies

class ErrorRecoveryManager(private val kmpIap: KmpIAP) {

suspend fun recoverFromError(error: PurchaseError) {
when (error.code) {
ErrorCode.E_CONNECTION_CLOSED.name,
ErrorCode.E_NETWORK_ERROR.name -> {
attemptReconnection()
}
ErrorCode.E_ALREADY_OWNED.name,
ErrorCode.E_PRODUCT_ALREADY_OWNED.name -> {
refreshPurchases()
}
ErrorCode.E_RECEIPT_FAILED.name,
ErrorCode.E_TRANSACTION_VALIDATION_FAILED.name -> {
revalidatePurchases()
}
ErrorCode.E_NOT_PREPARED.name -> {
kmpIap.initConnection()
}
}
}

private suspend fun attemptReconnection() {
repeat(3) { attempt ->
delay(2000L * (attempt + 1))
try {
kmpIap.initConnection()
return
} catch (e: PurchaseError) {
if (attempt == 2) throw e
}
}
}
}

4. Debug Logging

if (BuildConfig.DEBUG) {
scope.launch {
kmpIapInstance.purchaseErrorListener.collect { error ->
println("=====================================")
println("IAP ERROR DETAILS")
println("=====================================")
println("Code: ${error.code}")
println("Message: ${error.message}")
println("Product ID: ${error.productId}")
println("Platform: ${getCurrentPlatform()}")
println("Timestamp: ${System.currentTimeMillis()}")
println("=====================================")
}
}
}

Platform Error Code Mapping

The library automatically maps platform-specific error codes to OpenIAP standard codes:

iOS (StoreKit) Mapping

  • Uses integer codes from StoreKit
  • Mapped via ErrorCodeUtils.fromPlatformCode()

Android (Google Play Billing) Mapping

  • Uses string error codes directly (matching enum names)
  • Direct conversion via ErrorCode.valueOf()

Testing Error Scenarios

Unit Testing

@Test
fun testErrorHandling() = runTest {
val error = PurchaseError(
code = ErrorCode.E_NETWORK_ERROR.name,
message = "Network unavailable"
)

// Verify error code
assertEquals(ErrorCode.E_NETWORK_ERROR.name, error.code)

// Verify error message utility
val message = ErrorCodeUtils.getErrorMessage(ErrorCode.E_NETWORK_ERROR)
assertEquals("Network connection error", message)
}

Integration Testing

@Test
fun testPurchaseErrorRecovery() = runTest {
// Simulate network failure
val error = PurchaseError(
code = ErrorCode.E_NETWORK_ERROR.name,
message = "Connection failed"
)

val recoveryManager = ErrorRecoveryManager(kmpIap)
recoveryManager.recoverFromError(error)

// Verify reconnection attempted
assertTrue(kmpIap.isInitialized())
}

OpenIAP Specification Compliance

kmp-iap fully implements the OpenIAP error specification, ensuring:

  • Consistent error codes across all platforms
  • Standardized error messages for better UX
  • Compatible with expo-iap and other OpenIAP implementations
  • 27 standard error codes covering all IAP scenarios

Migration Notes

From Previous Versions

If migrating from older versions of kmp-iap, note that error codes now follow the OpenIAP standard. Update your error handling code to use the new ErrorCode enum values.

From Other Libraries

  • expo-iap: Error codes are identical (both follow OpenIAP)
  • flutter_inapp_purchase: Similar error codes with minor naming differences
  • Native SDKs: Platform codes are automatically mapped to OpenIAP codes

See Also