Skip to main content

1.3.2 - Android Product Status Codes & Type Updates

· 3 min read
Hyo

This release syncs with OpenIAP gql v1.3.14 / google v1.3.25 / apple v1.3.13.

New Features

Android Product Status Codes (Billing 8.0+)

Products now include status codes explaining fetch results:

val products = kmpIapInstance.fetchProducts {
skus = listOf("premium_monthly")
type = ProductQueryType.Subs
}

products.forEach { product ->
when ((product as? ProductAndroid)?.productStatusAndroid) {
ProductStatusAndroid.Ok -> println("Product fetched successfully")
ProductStatusAndroid.NotFound -> println("SKU doesn't exist")
ProductStatusAndroid.NoOffersAvailable -> println("User not eligible")
ProductStatusAndroid.Unknown -> println("Unknown status")
null -> {} // iOS or no status
}
}

New Types & DSL Updates

iOS Subscription Options (Types Only)

The following new iOS subscription options are now available in Kotlin types and DSL builders for forward compatibility:

  • introductoryOfferEligibility - Override introductory offer eligibility (iOS 15+, WWDC 2025)
  • promotionalOfferJWS - JWS promotional offer (iOS 15+, WWDC 2025)
  • winBackOffer - Win-back offer for iOS 18+
// DSL syntax available
kmpIapInstance.requestPurchase {
type = ProductType.Subs
ios {
sku = "premium_monthly"
winBackOffer = WinBackOfferInputIOS(offerId = "winback_50_off")
promotionalOfferJWS = PromotionalOfferJWSInputIOS(offerId = "promo", jws = "...")
introductoryOfferEligibility = true
}
}

Note: These options require OpenIAP's ObjC bridge update to function. Currently, kmp-iap's cinterop calls the ObjC bridge requestSubscriptionWithSku(sku, offer) which doesn't accept these new parameters. The legacy withOffer (DiscountOfferInputIOS) continues to work.

Android Suspended Subscriptions Option (Billing 8.1+)

New includeSuspendedAndroid option added to PurchaseOptions for fetching suspended subscriptions:

// Fetch available purchases including suspended subscriptions
val purchases = kmpIapInstance.getAvailablePurchases(
PurchaseOptions(includeSuspendedAndroid = true)
)

purchases.forEach { purchase ->
println("Product: ${purchase.productId}, State: ${purchase.purchaseState}")
}

This option uses reflection for backward compatibility with older Billing Library versions. When the underlying library doesn't support this feature, it gracefully degrades to the default behavior.

Breaking Changes

iOS Subscription-Only Props Cleanup

Removed subscription-specific fields from RequestPurchaseIosProps. These fields now only exist in RequestSubscriptionIosProps:

  • introductoryOfferEligibility
  • promotionalOfferJWS
  • winBackOffer

Migration: If using these fields for purchases, switch to requestSubscription() API.

Bug Fixes

Android displayPrice Fix for Free Trials

Fixed an issue where displayPrice returned "Free" or "$0.00" for subscription products with free trials. Now returns the actual base/recurring price.

// Before (bug)
product.displayPrice // "Free" or "$0.00"
product.price // 0.0

// After (fixed)
product.displayPrice // "$9.99" (base recurring price)
product.price // 9.99

// Free trial info is still available in subscriptionOffers
product.subscriptionOffers[0].displayPrice // "$0.00"
product.subscriptionOffers[0].paymentMode // FreeTrial

New Types

TypePlatformDescription
WinBackOfferInputIOSiOSWin-back offer input for iOS 18+
PromotionalOfferJWSInputIOSiOSJWS promotional offer for iOS 15+
ProductStatusAndroidAndroidProduct fetch status codes
SubResponseCodeAndroidAndroidSub-response codes for errors
BillingResultAndroidAndroidExtended billing result with sub-response

OpenIAP Versions

PackageVersion
openiap-gql1.3.14
openiap-google1.3.25
openiap-apple1.3.13

Installation

implementation("io.github.hyochan:kmp-iap:1.3.2")

References