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 allows your app to present, process, and fulfill purchases using built-in Amazon UI & modals saving you time and development effort.
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
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 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 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.
- 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:
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. 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 but for now you just need to decide your items SKU’s e.g. com.amazon.example.iap.consumable
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.
📡 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:
<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.
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)
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 callgetPurchaseUpdates
in theonResume
method to ensure you are getting latest updates.
Tip: This method takes a boolean parameter called reset. 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 togetPurchaseUpdates()
. This is the Amazon recommended approach.
-
getProductData
: which is a method used to retrieve item data for your SKUs, to display in your app.
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
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()
.
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.
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.
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
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.
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.
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.
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:
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 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 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 code.
Finally, you can stay up to date with Amazon Appstore developer updates on the following platforms:
📣 Follow @AmazonAppDev on Twitter
📺 Subscribe to our Youtube channel
📧 Sign up for the Developer Newsletter