Skip to content

hyodotdev/openiap-google

Repository files navigation

OpenIAP Android

OpenIAP Google Logo

Android implementation of the OpenIAP specification using Google Play Billing.


Maven Central API Publish to Maven Central CI License

Modern Android Kotlin library for in-app purchases using Google Play Billing Library v8.

🌐 Learn More

Visit openiap.dev for complete documentation, guides, and the full OpenIAP specification.

🎯 Overview

OpenIAP GMS is a modern, type-safe Kotlin library that simplifies Google Play in-app billing integration. It provides a clean, coroutine-based API that handles all the complexity of Google Play Billing while offering robust error handling and real-time purchase tracking.

✨ Features

  • πŸ” Google Play Billing v8 - Latest billing library with enhanced security
  • ⚑ Kotlin Coroutines - Modern async/await API
  • 🎯 Type Safe - Full Kotlin type safety with sealed classes
  • πŸ”„ Real-time Events - Purchase update and error listeners
  • 🧡 Thread Safe - Concurrent operations with proper synchronization
  • πŸ“± Easy Integration - Simple singleton pattern with context management
  • πŸ›‘οΈ Robust Error Handling - Comprehensive error types with detailed messages
  • πŸš€ Production Ready - Used in production apps

πŸ“‹ Requirements

  • Minimum SDK: 21 (Android 5.0)
  • Compile SDK: 34+
  • Google Play Billing: v8.0.0
  • Kotlin: 1.9.20+

πŸ“¦ Installation

Add to your module's build.gradle.kts:

dependencies {
    implementation("io.github.hyochan.openiap:openiap-google:1.2.9")
}

Or build.gradle:

dependencies {
    implementation 'io.github.hyochan.openiap:openiap-google:1.2.9'
}

πŸš€ Quick Start

1. Initialize in Application

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        OpenIAP.initialize(this)
    }
}

2. Basic Usage

class MainActivity : AppCompatActivity() {
    private lateinit var openIAP: OpenIAP

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        openIAP = OpenIAP.getInstance()

        // Set up listeners
        openIAP.addPurchaseUpdateListener { purchase ->
            handlePurchaseUpdate(purchase)
        }

        openIAP.addPurchaseErrorListener { error ->
            handlePurchaseError(error)
        }

        // Initialize connection
        lifecycleScope.launch {
            try {
                val connected = openIAP.initConnection()
                if (connected) {
                    loadProducts()
                }
            } catch (e: OpenIapError) {
                // Handle connection error
            }
        }
    }

    private suspend fun loadProducts() {
        try {
            val products = openIAP.fetchProducts(listOf("premium_upgrade", "remove_ads"))
            // Display products in UI
        } catch (e: OpenIapError) {
            // Handle error
        }
    }

    private suspend fun purchaseProduct(productId: String) {
        try {
            openIAP.requestPurchase(
                activity = this,
                sku = productId
            )
        } catch (e: OpenIapError) {
            // Handle purchase error
        }
    }

    private fun handlePurchaseUpdate(purchase: OpenIapPurchase) {
        when (purchase.purchaseState) {
            PurchaseState.Purchased -> {
                // Acknowledge or consume the purchase
                lifecycleScope.launch {
                    try {
                        purchase.purchaseToken?.let { token ->
                            openIAP.acknowledgePurchase(token)
                            // Or for consumables: openIAP.consumePurchase(token)
                        }
                    } catch (e: OpenIapError) {
                        // Handle error
                    }
                }
            }
            PurchaseState.Pending -> {
                // Purchase is pending (e.g., awaiting payment)
            }
            // Handle other states...
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        openIAP.clearListeners()
        openIAP.endConnection()
    }
}

πŸ“š API Reference

Core Methods

Connection Management

suspend fun initConnection(): Boolean
fun endConnection()
fun isReady(): Boolean

Product Management

suspend fun fetchProducts(skus: List<String>): List<OpenIapProduct>
suspend fun fetchProducts(type: String, skus: List<String>): List<OpenIapProduct>
fun getCachedProduct(sku: String): ProductDetails?
fun getAllCachedProducts(): Map<String, ProductDetails>

Purchase Operations

suspend fun requestPurchase(
    activity: Activity,
    sku: String,
    offerToken: String? = null,
    obfuscatedAccountId: String? = null,
    obfuscatedProfileId: String? = null
)

suspend fun requestPurchase(params: Map<String, Any?>, activity: Activity)
suspend fun finishTransaction(purchase: OpenIapPurchase, isConsumable: Boolean? = null)
suspend fun getAvailablePurchases(): List<OpenIapPurchase>
suspend fun getAvailablePurchases(options: Map<String, Any?>?): List<OpenIapPurchase> // options ignored on Android
suspend fun getAvailableItemsByType(type: String): List<OpenIapPurchase>
suspend fun acknowledgePurchase(purchaseToken: String): Boolean
suspend fun consumePurchase(purchaseToken: String): Boolean

Note: Use "in-app" for in-app product types. The legacy alias "inapp" remains available for compatibility but will be removed in version 1.2.0.

Store Information

suspend fun getStorefront(): String

Subscription Management

suspend fun getActiveSubscriptions(subscriptionIds: List<String>? = null): List<OpenIapActiveSubscription>
suspend fun hasActiveSubscriptions(subscriptionIds: List<String>? = null): Boolean
fun deepLinkToSubscriptions(): Boolean

Event Listeners

fun addPurchaseUpdateListener(listener: OpenIapPurchaseUpdateListener)
fun removePurchaseUpdateListener(listener: OpenIapPurchaseUpdateListener)
fun addPurchaseErrorListener(listener: OpenIapPurchaseErrorListener)
fun removePurchaseErrorListener(listener: OpenIapPurchaseErrorListener)

// Convenience methods
fun addListener(listener: OpenIapListener)
fun removeListener(listener: OpenIapListener)
fun clearListeners()

Data Models

OpenIapProduct

data class OpenIapProduct(
    val id: String,
    val title: String,
    val description: String,
    val price: Double?,
    val displayPrice: String,
    val currency: String,
    val type: ProductType,
    val platform: String = "android",
    val displayName: String?,
    val debugDescription: String?,
    val nameAndroid: String?,
    val oneTimePurchaseOfferDetails: OneTimePurchaseOfferDetails?,
    val subscriptionOfferDetails: List<SubscriptionOfferDetails>?
)

OpenIapPurchase

data class OpenIapPurchase(
    val id: String,                  // transactionId
    val productId: String,
    val ids: List<String>?,          // alias of productIds
    val transactionDate: Double,
    val transactionReceipt: String,
    val purchaseToken: String?,
    val platform: String = "android",
    val quantity: Int = 1,
    val transactionId: String?,
    val purchaseTime: Long,
    val purchaseState: PurchaseState,
    val isAutoRenewing: Boolean,
    // ... Android-specific fields
    val isAcknowledgedAndroid: Boolean?,
    val autoRenewingAndroid: Boolean?,
    // ... many more fields
)

Error Handling

sealed class OpenIapError : Exception {
    object UserCancelled : OpenIapError()
    object ItemAlreadyOwned : OpenIapError()
    object ItemNotOwned : OpenIapError()
    data class ProductNotFound(val productId: String) : OpenIapError()
    data class PurchaseFailed(override val message: String) : OpenIapError()
    // ... many more error types
}

πŸ”„ Purchase Flow

  1. Initialize: Call initConnection()
  2. Fetch Products: Use fetchProducts() to load available items
  3. Request Purchase: Call requestPurchase() with the product SKU
  4. Handle Events: Listen for purchase updates via listeners
  5. Process Purchase: Acknowledge non-consumables or consume consumables
  6. Server Verification: Always verify purchases on your backend

πŸ›‘οΈ Security Best Practices

  • Server-Side Verification: Always verify purchases on your backend server
  • Acknowledge Promptly: Acknowledge non-consumable purchases within 3 days
  • Consume Consumables: Consume consumable purchases after granting content
  • Handle All States: Implement proper handling for all purchase states
  • Error Handling: Implement comprehensive error handling

πŸ§ͺ Testing

The library includes a comprehensive sample app demonstrating all features:

git clone https://github.com/hyodotdev/openiap-google.git
cd openiap-google
./gradlew :sample:installDebug

Test Products

For development, use Google Play's test SKUs:

  • android.test.purchased - Always succeeds
  • android.test.canceled - Always cancels
  • android.test.item_unavailable - Always fails

For production testing, configure products in Google Play Console and use internal testing.

πŸ“± Sample App

The included sample app demonstrates:

  • βœ… Connection management with retry logic
  • βœ… Product listing and purchase flow
  • βœ… Real-time purchase event handling
  • βœ… Purchase history and management
  • βœ… Error handling and user feedback
  • βœ… Android-specific billing features

πŸ”§ Advanced Usage

Custom Error Handling

try {
    openIAP.requestPurchase(this, "premium_upgrade")
} catch (e: OpenIapError) {
    when (e) {
        OpenIapError.UserCancelled -> {
            // User cancelled, no action needed
        }
        OpenIapError.ItemAlreadyOwned -> {
            // Item already purchased
            showMessage("You already own this item!")
        }
        is OpenIapError.ProductNotFound -> {
            // Product not available
            showError("Product ${e.productId} not found")
        }
        // Handle other error types...
        else -> {
            showError("Purchase failed: ${e.message}")
        }
    }
}

Subscription Offers

// Get subscription offers
val product = openIAP.getCachedProduct("monthly_subscription")
val offers = product?.subscriptionOfferDetails

// Purchase with specific offer
val offerToken = offers?.firstOrNull()?.offerToken
openIAP.requestPurchase(
    activity = this,
    sku = "monthly_subscription",
    offerToken = offerToken
)

⚠️ Important Notes

  • This library requires Google Play Billing Library v8
  • Test with real Google Play Console products for production
  • Always verify purchases server-side for security
  • Handle all purchase states properly
  • Clean up listeners and connections in onDestroy()

πŸ”§ Troubleshooting

Common Issues

  1. Product not found

    • Ensure products are configured in Google Play Console
    • App must be uploaded to Google Play Console (even as draft)
    • Wait up to 24 hours for products to become available
  2. Billing unavailable

    • Verify Google Play Services are installed and updated
    • Check that app is signed with release key for testing
    • Ensure billing permissions are in AndroidManifest.xml
  3. Purchase not triggering

    • Use real device with Google Play Store
    • Avoid emulators without Google Play Services
    • Check that test account has payment method

Debug Mode

Enable verbose logging to see detailed billing operations:

// In development builds
if (BuildConfig.DEBUG) {
    Log.d("OpenIAP", "Debug mode enabled")
}

πŸ“„ License

MIT License

Copyright (c) 2025 hyo.dev

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

🀝 Contributing

Contributions are welcome! Please read our contributing guidelines and submit pull requests.

πŸ“ž Support


Built with ❀️ for the OpenIAP community

About

In-App Purchase in Google mobile platform that confirms OpenIAP

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •