How to monetize Kotlin apps with Amazon
Tags: amazon, kotlin, mobile
Published: 2024-02-22 13:31:07

What’s the secret to _making bank_ from your Android app? Publishing on Amazon Appstore! By doing so your app is able to reach millions of Amazon customers across multiple device form factors.

Now you might be thinking: 

**Do I really need to support another Appstore?**

but I’m here to tell you don’t worry! [Amazon’s IAP API](https://developer.amazon.com/docs/in-app-purchasing/iap-overview.html?ch=soc&chlast=soc&pub=devto&publast=devto&type=org&typelast=org) allows your app to present, process, and fulfill purchases using built-in Amazon UI & modals saving you time and development effort. 

![Amazon Appstore IAP UI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1hpnkjc9rv9sh4m2b4qp.gif)

Amazon handles the user journey (see animated example above) once the customer decides to purchase an item and completing when Amazon provides your app with either a receipt for the purchase or a status code. 

Still not convinced? Let me show you in 7 steps how you can configure your app and implement the API. If you want to follow along in code, try out the boilerplate I created for this guide on GitHub: [github.com/AmazonAppDev/amazon-iap-kotlin](https://github.com/AmazonAppDev/amazon-iap-kotlin)

## Configure your app and IAP items

### 📲 Step 1: Add the Appstore SDK to your app

To get started with implementing Amazon’s IAP API you need to add the Appstore SDK to your app. With Maven you can integrate the [Amazon Appstore SDK](https://developer.amazon.com/docs/in-app-purchasing/transitioning-to-appstore-sdk.html) with one line of code:

```
implementation 'com.amazon.device:amazon-appstore-sdk:3.0.3'
```

> Tip: Make sure your project’s top-level build.gradle has the Maven Central repository defined!

Alternatively, you can visit the [SDK Download page](https://developer.amazon.com/docs/apps-and-games/sdk-downloads.html?ch=soc&chlast=soc&pub=devto&publast=devto&type=org&typelast=org#appstoresdk) and to get the Appstore SDK for Android. The download includes the IAP JAR file and API documentation.

### 🔑 Step 2: Configure your app with your public key

> Tip: You can skip this step if you are only planning on testing locally.

Next you need to configure your app with a public key. The public key is unique per app and is used to establish a secure communication channel between the Amazon Appstore and your app. To retrieve your public key, head to the [Amazon Developer Portal Web Console](https://developer.amazon.com/settings/console/home). 

* If you have an existing app, create a new version of your app 
* If you are new to Amazon Appstore, create a new app and fill in the required fields. 

Once you have completed this step, head over to the **APK files** tab to find the public key link in the upper-right area of the main page section:

![The public key on the developer portal app details page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hd76qsl7ozfb6hyao62n.png)

Clicking this link will download your `AppstoreAuthenticationKey.pem` file which includes your public key. Back in your app, create an `assets` folder within `app/src/main`  and then copy over the `AppstoreAuthenticationKey.pem` into it.

### 🛍️ Step 3: Create your In-App Items

Now it’s time to map out your [In-App Items](https://developer.amazon.com/docs/in-app-purchasing/iap-overview.html#types-of-purchases). A quick refresh for In-App item categories and examples:

| Purchase category | What it is | Examples |
| ----------- | ----------- | ----------- |
| Consumables | Purchase that is made, then consumed within the app | Extra lives, extra moves, or in-game currency
| Entitlements | One-time purchase | To unlock access to features or content within your app or game |
| Subscriptions | Offers access to a premium set of content or features for a limited period of time | Freemium model where the app itself is free but a customer can pay a $8 a month to unlock a premium tier

**Understanding SKUs**: Your app should contain unique identifiers for each purchasable item called SKUs (technically a Stock Keeping Unit). The SKU is unique to your developer account as purchasable items and SKUs have a 1:1 mapping. The SKU is how the Amazon client knows what your customer is trying to purchase, and will manage the purchase flow accordingly.  Guidance on how to create the IAP Items can be found [here](https://developer.amazon.com/docs/in-app-purchasing/iap-create-and-submit-iap-items.html) but for now you just need to decide your items SKU’s e.g. `com.amazon.example.iap.consumable`

![Guidance on how to create the IAP Items](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/67a6zpt6f6md2wqk79ii.png)

![Guidance on how to create the IAP Items](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eqbs6jji92zwmp48pp7t.png)

![Guidance on how to create the IAP Items](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0litbunx1hbsf06ui2t7.png)

## Implementing the IAP API in your app

Now that we have configured our app, we can move on to the implementation, where you will need to add each component of the IAP API to your app. But before we do that let’s look at what the IAP API looks like under the hood.

The main components of the IAP API are:

- **PurchasingService**: this is the Class that initiates requests through the Amazon Appstore.
- **ResponseReceiver**: this is the Class that receives broadcast intents from the Amazon Appstore. 
- **PurchasingListener**: this is the Interface that receives asynchronous responses to the requests initiated by PurchasingService. 

![Main components of the IAP API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2y7rk2tw9ni738e8m7s7.png)

### 📡 Step 4: Add the response receiver to the Android Manifest

Since the In-App Purchasing API performs all of its activities in an asynchronous manner, your app needs to receive broadcast intents from the Amazon Appstore via the `ResponseReceiver` class. This class is never used directly in your app, but for your app to receive intents, you must add an entry for the `ResponseReceiver` to your `AndroidManifest.xml` file:

```xml
<application>  
...
    <receiver
        android:name="com.amazon.device.iap.ResponseReceiver"
        android:exported="true"
        android:permission="com.amazon.inapp.purchasing.Permission.NOTIFY">
        <intent-filter>
             <action android:name="com.amazon.inapp.purchasing.NOTIFY" />
        </intent-filter>
    </receiver>
</application>
```

###👂 Step 5:  Initialise the PurchasingListener

Next you will need to initialise the `PurchasingListener` in the `onCreate` method so that your app can listen for and process the callbacks triggered by the `ResponseReceiver`. To do this, register the `PurchasingListener` in the `MainActivity` of your app. 

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

        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        PurchasingService.registerListener(this, purchasingListener)
    }
```

> Tip: You should call registerListener before calling other methods in the PurchasingService class. 

### 🧾 Step 6: Fetch the user and receipts data (PurchasingService)

![PurchasingService](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uh0f2k6300b4hy5u4mx1.png)

After initialising the `PurcahsingListener` you can use the methods in the `PurchasingService` to retrieve information on user and product data, make purchases, and notify Amazon about the fulfilment of a purchase.

In the `onResume` method you can call 3 methods:

- `getUserData`  which is method used to retrieve the app-specific ID for the user who is currently logged on. For example, if a user switched accounts or if multiple users accessed your app on the same device, this call will help you make sure that the receipts that you retrieve are for the current user account.
- `getPurchaseUpdates`  which is a method that retrieves all purchase transactions by a user since the last time the method was called that is purchases across all devices. You need to call `getPurchaseUpdates` in the `onResume` method to ensure you are getting latest updates. 

> Tip: This method takes a boolean parameter called [reset](https://developer.amazon.com/docs/in-app-purchasing/iap-implement-iap.html#responsereceiver). Set the value to true or false depending on how much information you want to retrieve:
> **True** - retrieves a user's entire purchase history. You need to store this data somewhere, such as in server-side data cache or hold everything in memory.
> **False**- returns a paginated response of purchase history from the last call to `getPurchaseUpdates()`. This is the Amazon recommended approach.

- `getProductData`: which is a method used to retrieve item data for your SKUs, to display in your app.

```kotlin
const val parentSKU = "com.amazon.sample.iap.subscription.mymagazine"

override fun onResume() {
        super.onResume()
        
        PurchasingService.getUserData()
        PurchasingService.getPurchaseUpdates(true)

        val productSkus = hashSetOf(parentSKU)
        PurchasingService.getProductData(productSkus)
    }
```

### 👂Step 7: Implement the Purchasing Listener


![Purchasing Listener](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z41r3879ipjsryx57cod.png)

Next you need to implement the `PurchasingListener` interface to process the asynchronous callbacks made in the `PurchasingService`. Every call initiated results in a corresponding response received by the `PurchasingListener`. e.g. `onUserDataResponse()` is invoked after a call to `getUserData()`.

```kotlin
private var purchasingListener: PurchasingListener = object : PurchasingListener {
       
    override fun onUserDataResponse(response: UserDataResponse) {...}
    override fun onProductDataResponse(productDataResponse: ProductDataResponse) {...}
    override fun onPurchaseResponse(purchaseResponse: PurchaseResponse) {...}  
    override fun onPurchaseUpdatesResponse(purchaseUpdatesResponse: PurchaseUpdatesResponse){...} 
 };     
```
Lets look into whats returned by each of these callbacks in detail:

**`UserDataResponse`**

The `UserDataResponse` provides the app-specific userId and marketplace for the currently logged on user.

```kotlin
private lateinit var currentUserId: String
private lateinit var currentMarketplace: String

override fun onUserDataResponse(response: UserDataResponse) {
            when (response.requestStatus) {
                UserDataResponse.RequestStatus.SUCCESSFUL -> {
                    currentUserId = response.userData.userId
                    currentMarketplace = response.userData.marketplace
                }
                UserDataResponse.RequestStatus.FAILED, UserDataResponse.RequestStatus.NOT_SUPPORTED, null -> {
                    Log.e("Request", "Request error")
                }
            }
        }
```

> Tip: Persist the User Id and marketplace into memory for possible future use by the app.

**`ProductDataResponse`**

The `ProductDataResponse`, retrieves item data keyed by SKUs about the items you would like to sell from your app. 
This method also validates your SKUs so that a user's purchase does not accidentally fail due to an invalid SKU.
If the `RequestStatus` is SUCCESSFUL, retrieve the product data keyed by the SKU to be displayed in the app.
Additionally, if `RequestStatus` is SUCCESSFUL but has unavailable SKUs, you can call `PurchaseUpdatesResponse.getUnavailableSkus()` to retrieve the product data for the invalid SKUs and prevent your app's users from being able to purchase these products.

```kotlin
override fun onProductDataResponse(productDataResponse: ProductDataResponse) {
            when (productDataResponse.requestStatus) {
                ProductDataResponse.RequestStatus.SUCCESSFUL -> {
                    val products = productDataResponse.productData
                    for (key in products.keys) {
                        val product = products[key]
                        Log.v(
                            "Product:",
                            "Product: ${product!!.title} \n Type: ${product.productType}\n SKU: ${product.sku}\n Price: ${product.price}\n Description: ${product.description}\n"
                        )
                    }
                    for (s in productDataResponse.unavailableSkus) {
                        Log.v("Unavailable SKU:$s", "Unavailable SKU:$s")
                    }
                }
                ProductDataResponse.RequestStatus.FAILED -> Log.v("FAILED", "FAILED")

                else -> {
                    Log.e("Product", "Not supported")
                }
            }
        }
```
**`PurchaseResponse`**

![PurchaseResponse](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8lcadwrctbx76uxvvza3.png)

Now let’s look into what happens if a user goes to make a purchase. Let’s say a purchase is initiated when a user taps a button and this action triggers the `PurchasingService.purchase()` method to run and initialise a purchase.

```kotlin
binding.subscriptionButton.setOnClickListener { PurchasingService.purchase(parentSKU) }
```

Once the purchase is initiated the `onPurchaseResponse` method is used to determine the status of a purchase initiated within your app. If the request is successful, you can call the `notifyFulfillment` method to send the `FulfillmentResult `of the specified receiptId. 

```kotlin
override fun onPurchaseResponse(purchaseResponse: PurchaseResponse) {
            when (purchaseResponse.requestStatus) {
                PurchaseResponse.RequestStatus.SUCCESSFUL -> PurchasingService.notifyFulfillment(
                    purchaseResponse.receipt.receiptId,
                    FulfillmentResult.FULFILLED
                )
                PurchaseResponse.RequestStatus.FAILED -> {
                }
                else -> {
                    Log.e("Product", "Not supported")
                }
            }
        }
```

> Tip: If the PurchaseResponse.RequestStatus returns a result of FAILED, this could simply mean that the user canceled the purchase before completion.

**`PurchaseUpdatesResponse`**

Finally if you want to get the most up to date information about the user purchases you can use the response returned by `getPurchaseUpdates()`. Triggering this function, initiates the `PurchasingListener.onPurchaseUpdatesResponse()` callback. When the `PurchasingListener.onPurchaseUpdatesResponse()` callback is triggered, it retrieves the purchase history. 

Check the request status returned by `PurchaseUpdatesResponse.getPurchaseUpdatesRequestStatus()`. If requestStatus is SUCCESSFUL, process each receipt. 

You can use the `getReceiptStatus` method to retrieve details about the receipts. To handle pagination, get the value for `PurchaseUpdatesResponse.hasMore()`. If `PurchaseUpdatesResponse.hasMore()` returns true, make a recursive call to `getPurchaseUpdates()`, as shown in the following sample code.

> Tip: Persist the returned PurchaseUpdatesResponse data and query the system only for updates.

```kotlin
   override fun onPurchaseUpdatesResponse(response: PurchaseUpdatesResponse) {
       when (response.requestStatus) {
           PurchaseUpdatesResponse.RequestStatus.SUCCESSFUL -> {
               for (receipt in response.receipts) {
                   if (!receipt.isCanceled) {
                       binding.textView.apply {
                           text = "SUBSCRIBED"
                           setTextColor(Color.RED)
                       }
                   }
               }
               if (response.hasMore()) {
                   PurchasingService.getPurchaseUpdates(true)
               }
           }
           PurchaseUpdatesResponse.RequestStatus.FAILED -> Log.d("FAILED", "FAILED")
           else -> {
               Log.e("Product", "Not supported")
           }
       }
   }
```

And thats it, you should now have successfully implemented the IAP API in your app! The following is a demo of the boilerplate sample app I created for this guide:

{% link https://dev.to/amazonappdev/in-app-purchasing-demo-2nfe %}

So what next?

### 🧪 Next: Testing!

Now that you have implemented the IAP API you can go ahead and test your implementation. You can use [Amazon App Tester](https://developer.amazon.com/docs/in-app-purchasing/iap-install-and-configure-app-tester.html?ch=soc&chlast=soc&pub=devto&publast=devto&type=org&typelast=org) to test your IAP API code before you submit your app to the Amazon Appstore.  

> Tip: If when tested your purchases don’t fulfil, make sure you are using the latest version of the App Tester from the Amazon Appstore, this usually fixes this.

You can also checkout a [livestream](https://www.youtube.com/watch?v=mTCe3yaUrDw) we did with AWS Amplify where we show you how you can build a brand new android app using AWS Amplify, monetize it with the IAP API and then finally test it with the App Tester in under an hour! Or, you can get started right away by downloading my [boilerplate](https://github.com/AmazonAppDev/amazon-iap-kotlin) code.

Finally, you can stay up to date with Amazon Appstore developer updates on the following platforms:

📣 [Follow @AmazonAppDev on Twitter](https://twitter.com/AmazonAppDev)
📺 [Subscribe to our Youtube channel](https://www.youtube.com/amazonappstoredevelopers)
📧 [Sign up for the Developer Newsletter](https://m.amazonappservices.com/devto-newsletter-subscribe)