Introduction
Monetizing your mobile app through in-app purchases (IAP) is a powerful way to generate sustainable revenue, offer premium features, and enhance user engagement. Whether you’re unlocking new content, offering subscriptions, or selling consumable items, implementing IAP correctly on both iOS and Android ensures a seamless user experience and maximizes conversion. In this comprehensive guide, you’ll learn how to set up in-app purchases from scratch: configuring your products in App Store Connect and Google Play Console, integrating StoreKit (iOS) and BillingClient (Android), handling transactions securely, and following best practices for testing and compliance. By the end, you’ll be equipped to deploy robust IAP flows that delight users and stand up to App Store and Play Store review.

Main Body
Understanding In-App Purchase Types
Before writing any code, it’s crucial to know the four primary purchase types each platform offers:
- Consumable: Items that users buy, use, and can repurchase (e.g., coins, hints).
- Non-consumable: One-time purchases that remain available permanently (e.g., premium upgrade, ad removal).
- Auto-renewable Subscriptions: Recurring charges granting ongoing access (e.g., monthly magazine).
- Non-renewing Subscriptions: Time-limited access without automatic renewal (e.g., 6-month access).
Analogy: Think of consumables like arcade tokens you spend and need to buy again, non-consumables like unlocking the “full game,” and subscriptions like your monthly streaming service.
iOS Integration with StoreKit
Configuring Products in App Store Connect
- Log in to App Store Connect and select your app.
- Go to Features ▶️ In-App Purchases and click “+” to add a new product.
- Choose the Type (consumable, non-consumable, subscription, etc.).
- Enter the Reference Name, Product ID (e.g.,
com.yourapp.premium
), and Pricing. - Save and Submit for review.

Pro Tip: Use a consistent naming convention for Product IDs (reverse-domain style) to avoid collisions.
Implementing StoreKit (Swift)
Importing and Requesting Products
swiftCopyEditimport StoreKit
class IAPManager: NSObject {
static let shared = IAPManager()
private var products: [SKProduct] = []
func fetchProducts() {
let ids: Set<String> = ["com.yourapp.premium", "com.yourapp.coins100"]
let request = SKProductsRequest(productIdentifiers: ids)
request.delegate = self
request.start()
}
}
extension IAPManager: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
products = response.products
// Notify UI to display available products
}
}
Purchasing a Product
swiftCopyEditfunc purchase(product: SKProduct) {
guard SKPaymentQueue.canMakePayments() else { return }
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment)
}
extension IAPManager: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for tx in transactions {
switch tx.transactionState {
case .purchased:
// Unlock content
SKPaymentQueue.default().finishTransaction(tx)
case .failed:
// Handle error
SKPaymentQueue.default().finishTransaction(tx)
case .restored:
// Restore purchases
SKPaymentQueue.default().finishTransaction(tx)
default: break
}
}
}
}
Expert Insight: Always call
finishTransaction(_:)
to clear transactions and avoid duplicate callbacks.
Testing Your iOS IAP Flow

- Sandbox Tester Accounts: Create testers in App Store Connect under Users and Access ▶️ Sandbox Testers.
- Reset Transactions: In the sandbox environment, use Settings ▶️ Developer ▶️ Reset All Transactions.
- Use StoreKit Testing in Xcode: Define a local JSON file to simulate IAP responses without hitting Apple servers.
Android Integration with Google Play Billing
Configuring Products in Google Play Console
- Open Google Play Console, select your app.
- Navigate to Monetize ▶️ Products and choose In-app products or Subscriptions.
- Click Create product, select Managed product or Subscription, and fill out Product ID, Title, Description, and Price.
- Activate the product.
Pro Tip: Activate your products only when you’re ready to test or release; inactive products won’t appear in the test flow.
Implementing BillingClient (Kotlin)
Adding Dependencies
groovyCopyEditdependencies {
implementation "com.android.billingclient:billing:6.0.1"
}
Setting Up the BillingClient
kotlinCopyEditclass BillingManager(context: Context) : PurchasesUpdatedListener {
private val billingClient = BillingClient.newBuilder(context)
.setListener(this)
.enablePendingPurchases()
.build()
fun startConnection() {
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
queryAvailableProducts()
}
}
override fun onBillingServiceDisconnected() { /* Retry logic */ }
})
}
}
Querying and Purchasing Products
kotlinCopyEditfun queryAvailableProducts() {
val params = QueryProductDetailsParams.newBuilder()
.setProductList(listOf(
QueryProductDetailsParams.Product.newBuilder()
.setProductId("premium")
.setProductType(BillingClient.ProductType.INAPP)
.build()
))
.build()
billingClient.queryProductDetailsAsync(params) { billingResult, productDetailsList ->
// Display products in UI
}
}
fun purchase(activity: Activity, productDetails: ProductDetails) {
val billingFlowParams = BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.build()
billingClient.launchBillingFlow(activity, billingFlowParams)
}
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: MutableList<Purchase>?) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
for (purchase in purchases) handlePurchase(purchase)
}
}
Expert Insight: Use
enablePendingPurchases()
to comply with Google’s new billing requirements for pending transactions.
Handling and Acknowledging Purchases
kotlinCopyEditfun handlePurchase(purchase: Purchase) {
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
// Grant entitlement
val acknowledgeParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient.acknowledgePurchase(acknowledgeParams) { result ->
// Check result.responseCode
}
}
}
- Acknowledge every purchase within 3 days to avoid automatic refunds.
- Restore purchases by querying existing ones on startup:
kotlinCopyEditbillingClient.queryPurchasesAsync(
QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.INAPP).build()
) { _, purchasesList -> purchasesList.forEach(::handlePurchase) }
Best Practices & Expert Tips
- Secure Your Backend: Never rely solely on client-side verification; validate receipts/tokens on your server against Apple’s or Google’s servers.
- Graceful Fallbacks: Detect IAP availability (
SKPaymentQueue.canMakePayments()
/ BillingClient ready) and hide purchase UI if unavailable. - UX Considerations: Clearly label prices and benefits, handle failed transactions with user-friendly messages, and allow easy restoration of purchases.
- Analytics & Monitoring: Track purchase events with your analytics platform (e.g., Firebase, Amplitude) to measure conversion funnels.
- Compliance: Follow each store’s guidelines on subscriptions, free trials, and refund policies to avoid rejections.

Conclusion
Implementing in-app purchases on iOS and Android involves configuring products in your respective consoles, integrating StoreKit and BillingClient into your app, handling transactions securely, and rigorously testing the flow. By following the steps outlined—defining product types, writing purchase and restoration code, and adhering to best practices for security, UX, and analytics—you’ll deliver a polished, reliable IAP experience that drives revenue and satisfies both App Store and Play Store requirements. With this foundation, you’re ready to offer consumables, premium features, and subscriptions that keep users engaged and coming back for more.