Developer Console

Implement the Android IAP API

To better understand the In-App Purchasing (IAP) API, read about the classes contained in the Android IAP package, described below. To learn how to integrate the IAP API into your Android app, follow the use cases and code examples provided on this page. You can find many of these code examples in the Consumable IAP sample app, which is included in the Appstore SDK.

Watch the video tutorial to get started. For more details about implementing IAP in your app, read the sections that follow.

About the Android IAP package

The com.amazon.device.iap package provides classes and an interface that you use to implement In-App Purchasing in your Android app.

This package contains the following interface and classes:

The following table shows the request methods for PurchasingService and the associated PurchasingListener response callbacks. These are the methods, callbacks, and response objects that you will use the most frequently as you implement the IAP API:

PurchasingService method PurchasingListener callback Response object
getUserData() onUserDataResponse() UserDataResponse
getPurchaseUpdates() onPurchaseUpdatesResponse() PurchaseUpdatesResponse
getProductData() onProductDataResponse() ProductDataResponse
purchase() onPurchaseResponse() PurchaseResponse
notifyFulfillment() None None
enablePendingPurchases() None None

ResponseReceiver

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 manifest. The following code example shows how to add a ResponseReceiver to the AndroidManifest.xml file for the Appstore SDK. If your app targets Android 12 or higher, you must explicitly set android:exported to true in the MainActivity and ResponseReceiver.

 <application>
 ...
    <activity android:name="com.amazon.sample.iap.entitlement.MainActivity"
              android:label="@string/app_name" android:exported="true" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <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>

PurchasingService

Use the PurchasingService class to retrieve information, make purchases, and notify Amazon about the fulfillment of a purchase. PurchasingService implements methods below. You must implement each method for your callbacks to work:

PurchasingListener

Implement the PurchasingListener interface to process asynchronous callbacks. Because your UI thread invokes these callbacks, do not process long-running tasks in the UI thread. Your instance of PurchasingListener should implement the following required methods:

Response objects

Every call you initiate via the PurchasingService results in a corresponding response received by the PurchasingListener. Each of these responses uses a response object:

To see an example of response objects in a Kotlin app, clone or download the IAP Kotlin sample app from GitHub here:

Integrate the IAP API with your app

Now that you understand a bit more about the classes that you need to implement IAP, you can start writing the IAP code in your app.

The code snippets in this section are from the Consumable IAP sample app, included with the SDK.

1. Create placeholder methods

To organize your code, use placeholders or stubbed out code snippets to call the following methods in the following places:

Invoke the onCreate() and onResume() methods that call these APIs from your app's main activity. These four calls, which are part of the PurchasingService class, provide the foundation for executing an in-app purchase. The steps that follow go into more detail about how to implement these calls and provide sample code snippets that you can use to model your code.

2. Implement and register PurchasingListener

Implement and register PurchasingListener in the onCreate() method so that your app can listen for and process the callbacks triggered by the ResponseReceiver.

The code snippet below performs the following tasks:

  • (Required) Registers the PurchasingListener.

  • (Optional) Creates a new SampleIapManager instance to store data related to purchase receipts. This is an optional step; however, your app should store purchase receipt data somewhere where you can access it. Whether you choose to use a database or to store that data in memory is your decision.

  • (Optional) Checks if the app is running in sandbox mode. If you're using the Appstore SDK and have implemented a LicensingListener for DRM, use the getAppstoreSDKMode method from the LicensingService class. If you're using IAP v2.0 check for sandbox mode with the PurchasingService.IS_SANDBOX_MODE flag. This flag can be useful when your app is in development, and you're using the App Tester to locally test your app.

  • (Optional) Enables pending purchases. This feature allows a child within Amazon Kids to request an in-app purchase and have a parent approve or decline it. The purchase will be in a pending state while waiting for the parent's response. For instructions on how to set up pending purchases, see Implement Pending Purchases.

private SampleIapManager sampleIapManager; // Store purchase receipt data (optional)
protected void onCreate(final Bundle savedInstanceState) // Implement PurchasingListener in onCreate 
{
  super.onCreate(savedInstanceState);
  // setupApplicationSpecificOnCreate();
  
  // Registers ApplicationContext with AppstoreSDK and initiates a request to retrieve license for DRM 
  // using your implementation of LicensingListener (here named LicensingCallback)
  LicensingService.verifyLicense(this.getApplicationContext(), new LicensingCallback());
  
  setupIAPOnCreate();
}

private void setupIAPOnCreate() {
  sampleIapManager = new SampleIapManager(this);

  final SamplePurchasingListener purchasingListener = new SamplePurchasingListener(sampleIapManager);
  Log.d(TAG, "onCreate: registering PurchasingListener");

  PurchasingService.registerListener(this.getApplicationContext(), purchasingListener);
  PurchasingService.enablePendingPurchases(); // Enable pending purchases

  Log.d(TAG, "Appstore SDK Mode: " + LicensingService.getAppstoreSDKMode()); // Checks if app is in test mode
}

3. Get user information

Retrieve information (user ID and marketplace) about the current user by implementing getUserData() in onResume():

// ...
private String currentUserId =  null ;
private String currentMarketplace =  null ;

// ...

public void onUserDataResponse( final UserDataResponse response) {

 final UserDataResponse.RequestStatus status = response.getRequestStatus();

 switch (status) {
   case SUCCESSFUL:
	 currentUserId = response.getUserData().getUserId();
	 currentMarketplace = response.getUserData().getMarketplace();
	 break ;

   case FAILED:
   case NOT_SUPPORTED:
	 // Fail gracefully.
	 break ;
 }
}

Note that this example also persists the user ID and marketplace into memory for possible future use by the app.

4. Implement getPurchaseUpdates method

The getPurchaseUpdates() method retrieves all purchase transactions by a user since the last time the method was called. Call getPurchaseUpdates() in the onResume() method to ensure you are getting the latest updates.

The method takes a boolean parameter called reset. Set the value to true or false depending on how much information you want to retrieve:

  • false - Returns a paginated response of purchase history since the last call to getPurchaseUpdates(). Retrieves the receipts for the user's unfulfilled consumable, entitlement, and subscription purchases. The Appstore recommends using this approach in most cases.

  • true - Retrieves a user's entire purchase history. You need to store the data somewhere, such as in a server-side data cache or to hold everything in memory.

getPurchaseUpdates Responses

You receive a response from getPurchaseUpdates() in most scenarios. Responses are sent in the following cases:

  • Subscriptions and entitlements: You always receive a receipt for subscription and entitlement purchases.
  • Consumables: If a consumable transaction is successful, and you notify Amazon of the fulfillment (by calling notifyFulfillment()), you receive a receipt in onPurchaseResponse() but won't receive a receipt from getPurchaseUpdates(). In all other cases, you receive a receipt for consumables. The getPurchaseUpdates() method only returns fulfilled consumable purchases in rare cases, such as if an app crashes after fulfillment but before Amazon is notified, or if an issue occurs on Amazon's end after fulfillment. In these cases, you would need to remove the duplicate receipts so as not to over fulfill the item. When you deliver an item, record somewhere that you have done so, and do not deliver again even if you receive a second receipt.
  • Canceled purchases: You receive a receipt for canceled purchases of any type (subscription, entitlement, or consumable).

The response returned by getPurchaseUpdates() triggers the PurchasingListener.onPurchaseUpdatesResponse() callback.

@Override
protected void onResume() // Only call getPurchaseUpdates in onResume
{
  super.onResume();

//...

  PurchasingService.getUserData();

//...

  PurchasingService.getPurchaseUpdates(false);
}

Handle the Response of getPurchaseUpdates

Next, you need to handle the response.

When the PurchasingListener.onPurchaseUpdatesResponse() callback is triggered, check the request status returned by PurchaseUpdatesResponse.getRequestStatus(). If RequestStatus is SUCCESSFUL, process each receipt. You can use the getReceipts() 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:

public class MyPurchasingListener  implements PurchasingListener {
   boolean reset =  false ;
   //...

   public void onPurchaseUpdatesResponse( final PurchaseUpdatesResponse response) {
     //...
     // Process receipts
     switch (response.getRequestStatus()) {
       case SUCCESSFUL:
         for ( final Receipt receipt : response.getReceipts()) {
           // Process receipts
         }
         if (response.hasMore()) {
           PurchasingService.getPurchaseUpdates(reset);
         }
         break ;
       case FAILED:
         break ;
     }
   }
   //...
}

Process the receipts

Process the receipts. Call the SampleIapManager.handleReceipt() method to handle all receipts returned as part of the PurchaseUpdatesResponse.

public void onPurchaseUpdatesResponse(final PurchaseUpdatesResponse response) {
// ....
switch (status) {
case SUCCESSFUL:
	iapManager.setAmazonUserId(response.getUserData().getUserId(), response.getUserData().getMarketplace());
	for (final Receipt receipt : response.getReceipts()) {
		iapManager.handleReceipt(receipt, response.getUserData());
	}
	if (response.hasMore()) {
		PurchasingService.getPurchaseUpdates(false);
	}
	iapManager.refreshOranges();
	break;
}
// ...
}              

5. Implement getProductData method

Also in your onResume() method, call getProductData(). This method validates your SKUs so that a user's purchase does not accidentally fail due to an invalid SKU.

The following sample code validates the SKUs for an app's consumable, entitlement, and subscription items with Amazon:

protected void onResume() // Validate product SKUs in onResume only
{
   super.onResume();

   // ...

   final Set <string>productSkus =  new HashSet<string>();
   productSkus.add( "com.amazon.example.iap.consumable" );
   productSkus.add( "com.amazon.example.iap.entitlement" );
   productSkus.add( "com.amazon.example.iap.subscription" );
   PurchasingService.getProductData(productSkus); // Triggers PurchasingListener.onProductDataResponse()

   Log.v(TAG,  "Validating SKUs with Amazon" );
   }

Calling the PurchasingService.getProductData() method triggers the PurchasingListener.onProductDataResponse() callback. Check the request status returned in the ProductDataResponse.getRequestStatus(), and sell only the items or SKUs that were validated by this call.

Successful request

If the RequestStatus is SUCCESSFUL, retrieve the product data map keyed by the SKU displayed in the app. If RequestStatus is SUCCESSFUL but has unavailable SKUs, call ProductDataResponse.getUnavailableSkus() to retrieve the product data for the invalid SKUs and prevent your app's users from being able to purchase these products.

If you want to display the IAP icon within your app, you must edit your AndroidManifest.xml file to include the android.permission.INTERNET permission.

The product data map contains the following values:

Field Data Type Description
sku String Stock-keeping unit (SKU) of the product.
title String Localized title of the product.
description String Localized description of the product.
smallIconUrl String Small icon URL for the product.
productType String Type of product. Valid values: CONSUMABLE, ENTITLED, SUBSCRIPTION.
coinsRewardAmount int Number of Amazon Coins that a customer may be rewarded after purchasing this product.
freeTrialPeriod String Free trial period of the subscription term. Returned only if a free trial is configured and the customer is eligible.
subscriptionPeriod String Period of the subscription term for the SKU. Returned only for term SKUs. Valid values: Weekly, BiWeekly, Monthly, BiMonthly, Quarterly, SemiAnnual, Annual.
promotions List<Promotion> Details of the promotion the customer is eligible for. Returned only for term SKUs. See the following table for details on the Promotion object. For how to set up promotional pricing, see Set Up Promotional Pricing.
price String Localized price of the product (on the child SKU for subscription items).

The Promotion object contains the following fields:

Field Data Type Description
promotionType String Type of the promotion. Valid value: Introductory
promotionPlans List<PromotionalPlan> Details about price and billing cycles of the promotion. See the following table for details on the PromotionalPlan object.

The PromotionalPlan object has the following fields:

Field Data Type Description
promotionPrice String Price of the term SKU during the promotion period in localized format.
promotionPricePeriod String Duration of each billing cycle of the promotion. Valid values: Weekly, BiWeekly, Monthly, BiMonthly, Quarterly, SemiAnnual, Annual.
promotionPriceCycles int Number of billing cycles.

To see examples of a product data map for different subscription use cases, click the following button. The examples are in JSON format.

Failed request

If RequestStatus is FAILED, disable IAP functionality in your app as shown in the following sample code:

public class MyPurchasingListener  implements PurchasingListener {
   //  ...

   public void onProductDataResponse( final ProductDataResponse response) {
     switch (response.getRequestStatus()) {
       case SUCCESSFUL:
         for ( final String s : response.getUnavailableSkus()) {
           Log.v(TAG,  "Unavailable SKU:" + s);
         }

         final Map <string,>products = response.getProductData();
         for ( final String key : products.keySet()) {
           Product product = products.get(key);
           Log.v(TAG, String.format( "Product: %s\n Type: %s\n SKU: %s\n Price: %s\n Description: %s\n" , product.getTitle(), product.getProductType(), product.getSku(), product.getPrice(), product.getDescription()));
         }
         break ;

       case FAILED:
         Log.v(TAG,  "ProductDataRequestStatus: FAILED" );
         break ;
     }
   }

   //  ...
}

6. Implement code to make a purchase

Write the code to make a purchase. While this particular example makes the purchase of a consumable, you should be able to use similar code for subscriptions and entitlements, as well.

The following code from the consumable sample app's MainActivity class calls PurchasingService.purchase() to initialize a purchase. In the sample app, this method runs when an app user taps the Buy Orange button:

  public void onBuyOrangeClick(final View view) {
     final RequestId requestId = PurchasingService.purchase(MySku.ORANGE.getSku());
     Log.d(TAG, "onBuyOrangeClick: requestId (" + requestId + ")");
  }

Next, implement the SamplePurchasingListener.onPurchaseResponse() callback. In this snippet SampleIapManager.handleReceipt() handles the actual purchase:

public void onPurchaseResponse(final PurchaseResponse response) {
	switch (status) {
		// ...
		case SUCCESSFUL:
			final Receipt receipt = response.getReceipt();
			iapManager.setAmazonUserId(response.getUserData().getUserId(), response.getUserData().getMarketplace());
			Log.d(TAG, "onPurchaseResponse: receipt json:" + receipt.toJSON());
			iapManager.handleReceipt(receipt, response.getUserData());
			iapManager.refreshOranges();
			break;
	}
}

7. Process the purchase receipt and fulfill the purchase

You can now process the purchase receipt and, if the receipt is verified, fulfill the purchase. When designing your own app, keep in mind that you will likely implement some sort of fulfillment engine to handle these steps all in one place.

Verify the receipts from the purchase by having your back-end server verify the receiptId with Amazon's Receipt Verification Service (RVS) before fulfilling the item. Amazon provides an RVS Sandbox environment and an RVS production environment. See the Receipt Verification Service (RVS) documentation to learn how to set up both the RVS Sandbox and your server to work with RVS:

  • During development, use the RVS Sandbox environment to verify receipts generated by App Tester.
  • In production, use the RVS production endpoint.

In the following example, the handleConsumablePurchase() method in SampleIapManager checks whether the receipt is canceled.

  • If the receipt is canceled and the item was previously fulfilled, call the revokeConsumablePurchase() method to revoke the purchase.
  • If the receipt is not canceled, verify the receipt from your serve using RVS, then call grantConsumablePurchase() to fulfill the purchase.
    public void handleConsumablePurchase(final Receipt receipt, final UserData userData) {
        try {
            if (receipt.isCanceled()) {
                revokeConsumablePurchase(receipt, userData);
            } else {
                // We strongly recommend that you verify the receipt server-side
                if (!verifyReceiptFromYourService(receipt.getReceiptId(), userData)) {
                    // if the purchase cannot be verified,
                    // show relevant error message to the customer.
                    mainActivity.showMessage("Purchase cannot be verified, please retry later.");
                    return;
                }
                if (receiptAlreadyFulfilled(receipt.getReceiptId(), userData)) {
                    // if the receipt was fulfilled before, just notify Amazon
                    // Appstore it's Fulfilled again.
                    PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.FULFILLED);
                    return;
                }

                grantConsumablePurchase(receipt, userData);
            }
            return;
        } catch (final Throwable e) {
            mainActivity.showMessage("Purchase cannot be completed, please retry");
        }
    }

Guidelines for subscription purchases

If the purchasable item is a subscription, keep the following guidelines in mind with regards to the value of receiptId.

  • If the subscription is continuous and has never been canceled at any point, the app will only receive one receipt for that subscription/customer.
  • If the subscription was not continuous, for example, the customer did not auto-renew, let the subscription lapse, and then subscribed again a month later, the app will receive multiple receipts.

8. Grant the item to the user

To grant the item to the user, create a purchase record for the purchase and store that record in a persistent location.

Also validate the SKU:

private void grantConsumablePurchase(final Receipt receipt, final UserData userData) {
    try {
        // following sample code is a simple implementation, please
        // implement your own granting logic thread-safe, transactional and
        // robust

        // create the purchase information in your app/your server,
        // And grant the purchase to customer - give one orange to customer
        // in this case
        createPurchase(receipt.getReceiptId(), userData.getUserId());
        final MySku mySku = MySku.fromSku(receipt.getSku(), userIapData.getAmazonMarketplace());
        // Verify that the SKU is still applicable.
        if (mySku == null) {
            Log.w(TAG, "The SKU [" + receipt.getSku() + "] in the receipt is not valid anymore ");
            // if the sku is not applicable anymore, call
            // PurchasingService.notifyFulfillment with status "UNAVAILABLE"
            updatePurchaseStatus(receipt.getReceiptId(), null, PurchaseStatus.UNAVAILABLE);
            PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.UNAVAILABLE);
            return;
        }

        if (updatePurchaseStatus(receipt.getReceiptId(), PurchaseStatus.PAID, PurchaseStatus.FULFILLED)) {
            // Update purchase status in SQLite database success
            userIapData.setRemainingOranges(userIapData.getRemainingOranges() + 1);
            saveUserIapData();
            Log.i(TAG, "Successfuly update purchase from PAID->FULFILLED for receipt id " + receipt.getReceiptId());
            // update the status to Amazon Appstore. Once receive Fulfilled
            // status for the purchase, Amazon will not try to send the
            // purchase receipt to application any more
            PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.FULFILLED);
        } else {
            // Update purchase status in SQLite database failed - Status
            // already changed.
            // Usually means the same receipt was updated by another
            // onPurchaseResponse or onPurchaseUpdatesResponse callback.
            // simply swallow the error and log it in this sample code
            Log.w(TAG, "Failed to update purchase from PAID->FULFILLED for receipt id " + receipt.getReceiptId()
                       + ", Status already changed.");
        }

    } catch (final Throwable e) {
        // If for any reason the app is not able to fulfill the purchase,
        // add your own error handling code here.
        // Amazon will try to send the consumable purchase receipt again
        // next time you call PurchasingService.getPurchaseUpdates api
        Log.e(TAG, "Failed to grant consumable purchase, with error " + e.getMessage());
    }
}

Last updated: Dec 06, 2023