Skip to main content

1.2.0 - Billing Programs API & One-Time Product Discounts

· 5 min read
Hyo

KMP-IAP 1.2.0 brings Google Play Billing Library 8.2.0 features including the new Billing Programs API for external billing and one-time product discount support from Billing Library 7.0+.

New Features

1. Billing Programs API (Android 8.2.0+)

New methods for handling external billing programs:

  • isBillingProgramAvailable(program) - Check if a billing program is available for the user
  • createBillingProgramReportingDetails(program) - Get external transaction token for reporting
  • launchExternalLink(params) - Launch external link for billing programs

Supported Programs:

  • ExternalOffer - External offer programs
  • ExternalContentLink - External content link programs
import io.github.hyochan.kmpiap.kmpIapInstance
import io.github.hyochan.kmpiap.openiap.*

// Step 1: Check availability
val result = kmpIapInstance.isBillingProgramAvailable(BillingProgram.ExternalOffer)
if (!result.isAvailable) return

// Step 2: Launch external link
kmpIapInstance.launchExternalLink(
LaunchExternalLinkParams(
billingProgram = BillingProgram.ExternalOffer,
launchMode = ExternalLinkLaunchMode.LaunchInExternalBrowserOrApp,
linkType = ExternalLinkType.LinkToDigitalContentOffer,
linkUri = "https://your-payment-site.com"
)
)

// Step 3: Get reporting token
val details = kmpIapInstance.createBillingProgramReportingDetails(BillingProgram.ExternalOffer)
// Report token to Google Play within 24 hours
Availability Note

The Billing Programs API methods currently return FeatureNotSupported error as the underlying Google Play Billing Library 8.2.0 APIs are not yet available in the billing-ktx dependency. The types and methods are provided for future compatibility.

2. One-Time Product Discounts (Android 7.0+)

oneTimePurchaseOfferDetailsAndroid is now an array to support multiple offers with discounts.

New Fields:

  • offerId - Unique offer identifier
  • offerTags - Tags for categorizing offers
  • offerToken - Token for purchasing specific offer
  • discountDisplayInfo - Percentage and amount discount info
  • limitedQuantityInfo - Purchase quantity limits
  • validTimeWindow - Offer validity period
  • preorderDetails - Preorder release info (8.1.0+)
  • rentalDetails - Rental period info

3. Purchase Suspension Status (Android 8.1.0+)

  • Added isSuspendedAndroid field to PurchaseAndroid type
  • Indicates when a subscription is suspended due to payment issues
  • Users should be directed to fix payment method when suspended
if (purchase.isSuspendedAndroid == true) {
// Show UI to direct user to fix payment method
showPaymentIssueDialog()
}

Breaking Changes

1. oneTimePurchaseOfferDetailsAndroid Type Change

Before (1.0.0):

val price = product.oneTimePurchaseOfferDetailsAndroid?.formattedPrice

After (1.2.0):

val price = product.oneTimePurchaseOfferDetailsAndroid?.firstOrNull()?.formattedPrice

Migration: Update code that accesses this field to handle arrays:

// Access first offer (most common case)
val offers = product.oneTimePurchaseOfferDetailsAndroid
val price = offers?.firstOrNull()?.formattedPrice

// Or iterate through all offers
offers?.forEach { offer ->
println("Offer: ${offer.offerId}, Price: ${offer.formattedPrice}")
offer.discountDisplayInfo?.let { discount ->
println("Discount: ${discount.percentageDiscount}%")
}
}

2. VerifyPurchaseProps API Change

VerifyPurchaseProps now uses platform-specific nested options instead of a root sku field.

Before (1.0.0):

val result = kmpIapInstance.verifyPurchase(
VerifyPurchaseProps(
sku = "premium_upgrade",
androidOptions = VerifyPurchaseAndroidOptions(
accessToken = token,
packageName = "com.yourapp.id",
productToken = purchaseToken,
isSub = false
)
)
)

After (1.2.0):

// iOS verification
val iosResult = kmpIapInstance.verifyPurchase(
VerifyPurchaseProps(
apple = VerifyPurchaseAppleOptions(sku = "premium_upgrade")
)
)

// Android verification
val androidResult = kmpIapInstance.verifyPurchase(
VerifyPurchaseProps(
google = VerifyPurchaseGoogleOptions(
sku = "premium_upgrade",
accessToken = backendProvidedToken, // Get from your secure backend
packageName = "com.yourapp.id",
purchaseToken = purchase.purchaseToken ?: "",
isSub = false
)
)
)

// Meta Quest (Horizon) verification
val horizonResult = kmpIapInstance.verifyPurchase(
VerifyPurchaseProps(
horizon = VerifyPurchaseHorizonOptions(
sku = "premium_upgrade",
userId = userId,
accessToken = backendProvidedToken
)
)
)
Security Note

The accessToken for Google and Horizon verification must be obtained from your secure backend. Never hardcode or store API credentials in your app.

New Types

Billing Programs API Types

// Billing program type
enum class BillingProgram {
Unspecified,
ExternalContentLink,
ExternalOffer
}

// Launch mode for external links
enum class ExternalLinkLaunchMode {
Unspecified,
LaunchInExternalBrowserOrApp,
CallerWillLaunchLink
}

// Link type for external links
enum class ExternalLinkType {
Unspecified,
LinkToDigitalContentOffer,
LinkToAppDownload
}

// Parameters for launching external links
data class LaunchExternalLinkParams(
val billingProgram: BillingProgram,
val launchMode: ExternalLinkLaunchMode,
val linkType: ExternalLinkType,
val linkUri: String
)

// Result types
data class BillingProgramAvailabilityResult(
val billingProgram: BillingProgram,
val isAvailable: Boolean
)

data class BillingProgramReportingDetails(
val billingProgram: BillingProgram,
val externalTransactionToken: String
)

One-Time Product Discount Types

// Discount display information
data class DiscountDisplayInfoAndroid(
val percentageDiscount: Int? = null,
val discountAmount: DiscountAmountAndroid? = null
)

// Discount amount details
data class DiscountAmountAndroid(
val discountAmountMicros: String,
val formattedDiscountAmount: String
)

// Limited quantity information
data class LimitedQuantityInfoAndroid(
val maximumQuantity: Int,
val remainingQuantity: Int
)

// Offer validity period
data class ValidTimeWindowAndroid(
val startTimeMillis: String,
val endTimeMillis: String
)

// Pre-order details (8.1.0+)
data class PreorderDetailsAndroid(
val preorderReleaseTimeMillis: String,
val preorderPresaleEndTimeMillis: String
)

// Rental details
data class RentalDetailsAndroid(
val rentalPeriod: String,
val rentalExpirationPeriod: String? = null
)

Purchase Verification Types

// Platform-specific verification options (NEW in 1.2.0)
data class VerifyPurchaseProps(
val apple: VerifyPurchaseAppleOptions? = null,
val google: VerifyPurchaseGoogleOptions? = null,
val horizon: VerifyPurchaseHorizonOptions? = null
)

data class VerifyPurchaseAppleOptions(
val sku: String
)

data class VerifyPurchaseGoogleOptions(
val sku: String,
val accessToken: String, // Obtain from your backend
val packageName: String,
val purchaseToken: String,
val isSub: Boolean? = null
)

data class VerifyPurchaseHorizonOptions(
val sku: String,
val userId: String,
val accessToken: String // Obtain from your backend
)

// Deprecated (use VerifyPurchaseGoogleOptions instead)
@Deprecated("Use VerifyPurchaseGoogleOptions instead")
typealias VerifyPurchaseAndroidOptions = VerifyPurchaseGoogleOptions

OpenIAP Updates

Updated OpenIAP versions:

Installation

Gradle (Kotlin DSL)

// shared/build.gradle.kts
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.hyochan:kmp-iap:1.2.0")
}
}
}

Version Catalog (libs.versions.toml)

[versions]
kmp-iap = "1.2.0"

[libraries]
kmp-iap = { module = "io.github.hyochan:kmp-iap", version.ref = "kmp-iap" }

References

Questions or feedback? Reach out via GitHub Issues or join our Slack community.