Home > Services & APIs > Earn > In-App Purchasing

Implementing In-App Purchasing (IAP)

Introduction

Introduction

This page explains how to implement the IAP API in your app by using a task-based approach, providing use cases and code snippets to complement the IAP v2.0 API Reference documentation. Note that many (but not all) of the code snippets in this document can be found in the Consumable IAP sample app.

This topic has two main parts:

  • The first part gives a high level overview to the interface and classes found in the com.amazon.device.iap package.
  • The second part discusses common IAP tasks for most apps and gives code snippet examples for each task.

About package com.amazon.device.iap

The com.amazon.device.iap package provides classes and an interface that are required to use the In-App Purchasing API with the Amazon Appstore.

This package contains the following interface and classes:

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

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

For more information about these classes and methods, see the IAP v2.0 API Reference documentation.

ResponseReceiver

In-App Purchasing API performs all of its activities in an asynchronous manner. Your app needs to receive broadcast intents from the 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. The following code snippet shows how to add a ResponseReceiver to the AndroidManifest.xml file for IAP v2.0:

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

PurchasingService

Use the PurchasingService class to retrieve various types of information, execute purchases, and notify Amazon about the fulfillment of a purchase. PurchasingService implements the following methods:

  • registerListener(PurchasingListener purchasingListener): Call this method before calling other methods in the PurchasingService class.
  • getUserData(): Call this method to retrieve the app-specific ID and marketplace 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(boolean reset): Call this method to retrieve previous purchases and revoked purchases from across multiple devices. Amazon recommends that you persist the returned PurchaseUpdatesResponse data and query the system only for updates. The response is paginated.
  • getProductData(java.util.Set skus): Call this method to retrieve item data for a set of SKUs to display in your app.
  • purchase(java.lang.String sku): Call this method to initiate a purchase of a particular SKU.
  • notifyFulfillment(java.lang.String receiptId, FulfillmentResult fulfillmentResult): Call this method to send the FulfillmentResult of the specified receiptId. Possible values for FulfillmentResult are FULFILLED or UNAVAILABLE.

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

  • onUserDataResponse(UserDataResponse userDataResponse): Invoked after a call to getUserData(). Determines the UserId and marketplace of the currently logged on user.
  • onPurchaseUpdatesResponse(PurchaseUpdatesResponse purchaseUpdatesResponse): Invoked after a call to getPurchaseUpdates(boolean reset). Retrieves the purchase history. Amazon recommends that you persist the returned PurchaseUpdatesResponse data and query the system only for updates.
  • onProductDataResponse(ProductDataResponse productDataResponse): Invoked after a call to getProductDataRequest(java.util.Set skus). Retrieves information about SKUs you would like to sell from your app. Use the valid SKUs in onPurchaseResponse().
  • onPurchaseResponse(PurchaseResponse purchaseResponse): Invoked after a call to purchase(String sku). Used to determine the status of a purchase.

ResponseObjects

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

  • UserDataResponse: Provides the app-specific UserId and <span> </span>marketplace for the currently logged on user.
  • PurchaseUpdatesResponse: Provides a paginated list of receipts. Receipts are unordered.
  • ProductDataResponse: Provides item data, keyed by SKU. The getUnavailableSkus() method lists any SKUs that are unavailable.
  • PurchaseResponse: Provides the status of a purchase initiated within your app. Note that a PurchaseResponse.RequestStatus result of FAILED can simply mean that the user canceled the purchase before completion.

Integrating the IAP API with your app

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

The code snippets in this procedure can all be found in the Consumable IAP sample app included with the SDK.

  1. Organize your code. Using placeholders or stubbed out code snippets, set up your app to call the following methods in the following places:
    • Call registerListener() in your onCreate() method.
    • Call getUserData() in your onResume() method.
    • Call getPurchaseUpdates() in your onResume() method.
    • Call getProductData() in your onResume() method.

      These four calls, which are part of the PurchasingService class, provide the foundation for executing an in-app purchase. The steps that follow will go into more detail about how to implement these calls and provide sample code snippets that you can use to model your code.

  2. In your code, implement and register the PurchasingListener so that your app can listen for and process the callbacks triggered by the ResponseReceiver. This is the call that you set up in the previous step:

    private SampleIapManager sampleIapManager;
    protected void onCreate(final Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setupApplicationSpecificOnCreate();
      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);
    
      Log.d(TAG, "IS_SANDBOX_MODE:" + PurchasingService.IS_SANDBOX_MODE);
    }
    

    In addition to registering the PurchasingListener, this sample code performs the following two optional tasks:

    • Create 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.
    • Check if the app is running under sandbox (test) mode by checking PurchasingService.IS_SANDBOX_MODE. This flag can be useful when your app is in development, and you are using the App Tester to locally test your app.
  3. Determine who the current user is (User Id and marketplace) 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. In youronResume() call, implement the getPurchaseUpdates() call, which you can call to retrieve all purchase transactions by a user since the last time this method was called.
    • Set reset to false to return a paginated response of purchase history since the last call to getPurchaseUpdates(). This combination retrieves the receipts for the user’s pending consumable, entitlement, and subscription purchases. (Amazon recommends using this approach in nearly all cases.)
    • Only set reset to true if you want to retrieve a user’s entire purchase history and store it somewhere, such as in a server-side data cache or to hold everything in memory.

      Regardless of the value of the reset flag, note the following behaviors for getPurchaseUpdates():

      • The getPurchaseUpdates() call always returns all unfulfilled consumable purchases.
      • The getPurchaseUpdates() call returns fulfilled purchases only in rare circumstances. For example, getPurchaseUpdates() returns fulfilled consumable purchases 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 overfulfill 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.

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

        @Override
        protected void onResume() {
          super.onResume();
        
        //...
        
          PurchasingService.getUserData();
        
        //...
        
          PurchasingService.getPurchaseUpdates(false);
        }
        

        Now, handle the response:

        1. When the PurchasingListener.onPurchaseUpdatesResponse() callback is triggered, check the request status returned by PurchaseUpdatesResponse.getPurchaseUpdatesRequestStatus(). If requestStatus is SUCCESSFUL then process each receipt
        2. 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.getPurchaseUpdatesRequestStatus()) {
                 case SUCCESSFUL:
                   for ( final Receipt receipt : response.getReceipts()) {
                     // Process receipts
                   }
          
                   if (response.hasMore()) {
                     PurchasingService.getPurchaseUpdates(reset);
                   }
                   break ;
                 case FAILED:
                   break ;
               }
             }
          
             //...
          }
          
  5. Also in your onResume() method, call getProductData() to validate 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() {
       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);
    
       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.

    • If the requestStatus is SUCCESSFUL, retrieve the product data map keyed by the SKU displayed in the app. The product data map contains the following values:
      • Product type
      • Icon URL
      • Localized price
      • Title
      • Description
      • SKU

        Note that if you want to display the IAP icon within your app, you will need to edit your AndroidManifest.xml file to include the android.permission.INTERNET permission.

        Additionally, if requestStatus is SUCCESSFUL but has unavailable SKUs, 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.

    • 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. Write the code to execute a purchase. While this particular example executes the purchase of a consumable, you should be able to use similar code for subscriptions and entitlements, as well.

    The following code snippet from MainActivity calls PurchasingService.purchase() to initialize a purchase. In the consumable sample app, this method is executed 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. Fulfill the purchase and process the purchase receipt. 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.
    • If the purchaseable 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.

    • 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 both an RVS Sandbox environment and a production environment for RVS. See the [Receipt Verification Service (RVS)][iap-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 to verify receipts generated by App Tester.
      • In production, use the RVS production endpoint.
    • In this example, in SampleIapManager, the handleConsumablePurchase() method 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 server, 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");
            }
        }
        
  8. Grant the item to the user by performing the following tasks:

    1. Create a purchase record for the purchase and store that record somewhere.
    2. Validate the SKU:

      • If the SKU is available, fulfill the item and call notifyFulfillment with status FULFILLED. Once this step is complete, the Amazon Appstore will not try to send the purchase receipt to the app anymore.
      • If the SKU is not available, call notifyFulfillment with status UNAVAILABLE.

        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());
            }
        }
        
  9. Process the receipts. Note that this step is a result of getPurchaseUpdates() not as a result of a call to purchase(SKU). 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;
    // ...