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

Implementing In-App Purchasing (IAP v1.0)

1. Incorporate the API into Your Project

  1. In Eclipse, create or navigate to your Android project root folder, right click, and select the Properties view.
  2. Click Java Build Path on the left
  3. Click on the Libraries tab at the top
  4. Click Add External Jars on the right
  5. Navigate to the where you extracted the Apps-SDK.zip file, locate the /In-App-Purchasing/lib folder, and select the In-App-Purchasing jar file
  6. Click Open
  7. On the Order and Export tab, make sure that the checkbox beside the jar is selected.
  8. Click OK

Fig. 1: Screenshot of adding In-App Purchasing API jar to your Android Project

Optional: Adding In-App Purchasing API Reference to your Eclipse environment

Many developers find it useful to add In-App Purchasing API Reference to their project as well. This allows you to view the reference documentation within Eclipse, similar to Java and the Android SDK.

  1. In Eclipse, navigate to your Android project root folder, right click, and select the Properties view.
  2. Click Java Build Path on the left
  3. Click on the Libraries tab at the top
  4. Expand the In-App Purchasing Jar
  5. Select Javadoc location
  6. Click Edit
  7. Click Browse
  8. Navigate to the directory you extracted the SDK.zip file, then to the docs/API_Reference directory
  9. Click OK
  10. Click OK

Fig. 2: Screenshot of adding In-App Purchasing API javadocs to your Eclipse environment

The In-App Purchasing API reference is now integrated into Eclipse and you should see windows like the below screenshot directly from Eclipse.


Fig. 3: Example of javadocs integrated into Eclipse environment

2. Update the Android Manifest

Your app needs to receive broadcast intents from the Amazon Client via the ResponseReceiver class. You never use this class directly in your app. Instead, you simply include the following lines directly in the <application> tag in the AndroidManifest.xml file for your app.

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

3. Implement the Purchasing Observer

Once you have setup the ResponseReceiver to listen for broadcast intents from the Amazon Client, you need the ability to process the callbacks triggered from the ResponseReceiver. This is done with the PurchasingObserver. Your app must implement the PurchasingObserver interface by subclassing the abstract BasePurchasingObserver.

		public class MyPurchasingObserver extends BasePurchasingObserver {
		   private static final String TAG = "IAPPurchasingObserver";

		   public MyPurchasingObserver(Activity iapActivity) {
			  super(iapActivity);
		   }
	 
		   public void onSdkAvailable(final boolean isSandboxMode) {}
		   public void onGetUserIdResponse(final GetUserIdResponse response) {}
		   public void onItemDataResponse(final ItemDataResponse response) {}
		   public void onPurchaseResponse(final PurchaseResponse response) {}
		   public void onPurchaseUpdatesResponse(final PurchaseUpdatesResponse response) {}
		}

	

4. Register the Purchasing Observer

Next, register your PurchasingObserver with the PurchasingManager so that you can begin calling the In-App Purchasing API and receiving callbacks. This must be done in the onCreate(...) method of your Main Activity.

	public class IAPActivity extends Activity {
    ...
		 protected void onCreate(Bundle savedInstanceState) {
			...
			PurchasingManager.registerObserver(new MyPurchasingObserver(this));
		 }
	 ...
	 }
	

The Purchasing Observer callback matching the register call is onSDKAvailable(...). The callback returns a boolean that you can check to see whether the app is running in test mode against SDKtester or in the live production environment.

 

While potentially useful while testing, this check is not strictly required since your app doesn’t need to know whether it if running in a live or test environment. Using SdkTester, in test mode, the app will function the same way it does in a live environment. The main use for this boolean flag is to allow you to toggle between telling your server to point to the live RVS Server and the RVS Sandbox. Because of this you must persist this boolean value.

		public class MyPurchasingObserver extends BasePurchasingObserver {
		...
			private boolean rvsProductionMode = false;
			
			public void onSDKAvailable(boolean isSandboxMode) {
				// Switch RVS URL from test to production
				rvsProductionMode = !isSandboxMode;
			}
	    ...
	    }
	

5. Sync Access Rights for the Current User

During your Main Activity’s onResume() method, retrieve the user Id of the customer currently logged into the Amazon Appstore by calling PurchasingManager.initiateGetUserIdRequest().

		public class IAPActivity extends Activity {
		...
			protected void onResume() {
				super.onResume();
				PurchasingManager.initiateGetUserIdRequest();
			}
		...
		}
	

The PurchasingObserver.onGetUserIdResponse callback will be triggered in response to the PurchasingManager.initiateGetUserIdRequest() call.

The first thing to do in the PurchasingObserver.onGetUserIdResponse callback is to persist the user Id returned in the GetUserIdResponse object in memory. This is the user Id of the customer currently logged into the Amazon Appstore. As such, the user Id is unique to both the user and the app.

		public class MyPurchasingObserver extends BasePurchasingObserver {
		...

		   private String currentUserID = null;

			public void onGetUserIdResponse(final GetUserIdResponse response)
			{
				if (response.getUserIdRequestStatus() ==
					GetUserIdResponse.GetUserIdRequestStatus.SUCCESSFUL) {
					currentUserID = response.getUserId();
				}
				else {
					// Fail gracefully.
				}
			}
	    ...
	    }
	

Next, with the user returned, within the PurchasingObserver.onGetUserIdResponse(), call PurchasingManager.initiatePurchaseUpdatesRequest() with an offset value to retrieve the current state of receipts for your app’s entitlements and subscriptions. You must use this method to sync purchases made from other devices onto this device, and to sync revoked entitlements across all instances of your app.

Although the corresponding PurchasingService.onPurchaseUpdatesResponse() callback will not return any consumable information, you must make a call to PurchasingManager.initiatePurchaseUpdatesRequest() even if your app only contains consumable items. In this case, it is onPurchaseResponse() which will be returned to your app if there are any pending consumable purchases when PurchasingManager.initiatePurchaseUpdatesRequest() is called.

The offset represents a position in a set of paginated results. You can use the offset to get the next set of results. You can also pass in Offset.BEGINNING to get the entire list of results. Offset values are base64 encoded values and not human readable.

It is best practice to only query the system for updates ie. to only retrieve new receipts generated since the last call to PurchasingManager.initiatePurchaseUpdatesRequest(). Use the user Id returned above in from the onGetUserId Response to retrieve the persisted offset and pass this in into the PurchasingManager.initiatePurchaseUpdatesRequest(). You can use Offset.BEGINNING when making a call to PurchasingManager.initiatePurchaseUpdatesRequest() in the consumable case.

	public class MyPurchasingObserver extends BasePurchasingObserver {
	...

	   private String currentUserID = null;

		public void onGetUserIdResponse(final GetUserIdResponse response)
		{
			if (response.getUserIdRequestStatus() ==
				GetUserIdResponse.GetUserIdRequestStatus.SUCCESSFUL) {
				currentUserID = response.getUserId();
				PurchasingManager.initiatePurchaseUpdatesRequest(getPersistedOffset());
			}
			else {
				// Fail gracefully.
			}
		}

		private Offset getPersistedOffset() {
			// Retrieve the offset you have previously persisted.
			// If no offset exists or the app is dealing exclusively with consumables
			// use Offset.BEGINNING.
		}
	...
	}
	

Handling the PurchaseUpdatesResponse with a successful response status when the PurchasingObserver.onPurchaseUpdatesResponse() callback is triggered differs depending on whether the your app offers, consumable, entitlements or subscriptions for purchase. If the PurchaseUpdatesResponse response status is failed, use state of the receipts already persisted to enable access to entitlements and subscriptions for your user.

Consumables

No code needs to be added to the PurchasingObserver.onGetPurchaseUpdatesResponse() callback if your app deals solely with consumables.

		public class MyPurchasingObserver extends BasePurchasingObserver {
		...

		   public void onPurchaseUpdatesResponse(final PurchaseUpdatesResponse response) {
			    // No implementation required when dealing solely with consumables.
		   }
	    ...
	    }
	

Entitlements

When the PurchasingObserver.onGetPurchaseUpdatesResponse() callback is triggered, process receipts returned by fulfilling and enabling all entitlements returned and process any revoked skus. First, check the request status returned in the PurchaseUpdatesResponse. getPurchaseUpdatesRequestStatus()

 

If the requestStatus is successful:

  • Retrieve all receipts and then fulfill/entitle them accordingly.
  • Only entitlements can be revoked. Call PurchaseUpdatesResponse.getRevokedSkus() and remove entitlement to any skus returned in this call.
		public class MyPurchasingObserver extends BasePurchasingObserver {
		...
		   public void onPurchaseUpdatesResponse(final PurchaseUpdatesResponse response) {
			...				
				switch (response.getPurchaseUpdatesRequestStatus()) {
					case SUCCESSFUL:
						// Check for revoked SKUs
						for (final String sku : response.getRevokedSkus()) {
							Log.v(TAG, "Revoked Sku:" + sku);
						}

						// Process receipts
						for (final Receipt receipt : response.getReceipts()) {
							switch (receipt.getItemType()) {
								case ENTITLED: // Re-entitle the customer
								break;
							}
						}
						break;
						
				   case FAILED:
						// Provide the user access to any previously persisted entitlements.
						break;
                }
			}
	    ...
	    }
	

Subscriptions

When the PurchasingObserver.onGetPurchaseUpdatesResponse() callback is triggered, process receipts returned by fulfilling and enabling all subscriptions returned. First, check the request status returned in the PurchaseUpdatesResponse. getPurchaseUpdatesRequestStatus()

 

If the requestStatus is successful:

  • Retrieve all receipts and then fulfill/entitle them accordingly. Active subscriptions are denoted by a null end date in the receipt. Expired subscriptions are determined by a non-null end date on the receipt. It is your app's responsibility to manage expired subscriptions.
		public class MyPurchasingObserver extends BasePurchasingObserver {
		...

		   public void onPurchaseUpdatesResponse(final PurchaseUpdatesResponse response) {
			...
				// Process receipts
				switch (response.getPurchaseUpdatesRequestStatus()) {
					case SUCCESSFUL:
						for (final Receipt receipt : response.getReceipts()) {
							switch (receipt.getItemType()) {
								case SUBSCRIPTION:// Check for receipt with the latest start date and a null end date.
								break;
							}
						}
						break;
				   case FAILED:
						// Provide the user access to any previously persisted subscriptions.
						break;
                }
			}
	    ...
	    }
	

Handling the Offset

In both the entitlement and subscription case, when persisting the offset and returned receipts, they should be associated with the current user Id. This is to handle the case that multiple customers share the same physical device. The user Id returned in getPurchaseUpdatesResponse() can be used to for storing the receipt data and offset.

If PurchaseUpdatesResponse.isMore() returns true, make a recursive call to PurchasingManager.initiatePurchaseUpdatesRequest() with the value returned in PurchaseUpdatesResponse.getOffset().

 
		public class MyPurchasingObserver extends BasePurchasingObserver {
		...

		   public void onPurchaseUpdatesResponse(final PurchaseUpdatesResponse response) {
			...
			    // Check for revoked SKUs
				for (final String sku : response.getRevokedSkus()) {
					Log.v(TAG, "Revoked Sku:" + sku);
				}
				
				// Process receipts
				switch (response.getPurchaseUpdatesRequestStatus()) {
					case SUCCESSFUL:
						for (final Receipt receipt : response.getReceipts()) {
							switch (receipt.getItemType()) {
								case ENTITLED: // Re-entitle the customer
								break;
								case SUBSCRIPTION:// Check eceipt with the latest start date.
								break;
							}
						}
				
						final Offset newOffset = response.getOffset();
						if (response.isMore()) {
							Log.v(TAG, "Initiating Another Purchase Updates with offset: "
						  + newOffset.toString());
							PurchasingManager.initiatePurchaseUpdatesRequest(newOffset);
						}
						break;
				   case FAILED:
						// Provide the user access to any persisted entitlements and subscriptions or fail gracefully.
						break;
                }
			}
	    ...
	    }
	

6. Validate SKUs to be Used in Your App

Calling PurchasingManager.initiateItemDataRequest() allows you to retrieve the most up-to-date and localized price, title and description information for each of your SKUs. This must be done for all of the SKUs you are offering for sale within your app. Proper SKU validation will also prevent an invalid SKU status from being returned in the PurchasingObserver.onPurchaseResponse().

public class IAPActivity extends Activity {
    //  ...
    protected void onResume() {
        // ...
        Set<String> skuSet = new HashSet<String>();
        skuSet.add("com.amazon.example.iap.consumable");
        skuSet.add("com.amazon.example.iap.entitlement");
        skuSet.add("com.amazon.example.iap.subscription");

        PurchasingManager.initiateItemDataRequest(skuSet);
        Log.v(TAG, "Validating SKUs with Amazon");
    }
    // ...
}

The PurchasingObserver.onItemDataResponse() callback is triggered after the PurchasingManager.intiateItemDataRequest() method has been called. You should check the request status returned in the PurchaseUpdatesResponse.getItemDataRequestStatus() and only offer for sale purchasable items or SKUs validated by this call.

 

If the requestStatus is successful, retrieve the item data map (item type, icon url, localized price, title and description) keyed on sku for display in the app.

 

If the requestStatus is successful with unavailable SKUs, this indicates that the request was successful but item data for one or more of the provided skus was not available. Developers should retrieve the item data for each available sku for display in the app. Access to purchase should be degraded gracefully for any skus returned in PurchaseUpdatesResponse.getUnavailableSkus().

 

If the requestStatus is failed, you should disable IAP functionality in your app.

public class MyPurchasingObserver extends BasePurchasingObserver {
    //  ...
    public void onItemDataResponse(final ItemDataResponse response) {
        switch (response.getItemDataRequestStatus()) {
            case SUCCESSFUL_WITH_UNAVAILABLE_SKUS:
                for (final String s : response.getUnavailableSkus()) {
                    Log.v(TAG, "Unavailable SKU:" + s);
                }

            case SUCCESSFUL:
                final Map<String, Item> items = response.getItemData();
                for (final String key : items.keySet()) {
                    Item i = items.get(key);
                    Log.v(TAG, String.format("Item: %s\n Type: %s\n SKU: %s\n Price: %s\n Description: %s\n", i.getTitle(), i.getItemType(), i.getSku(), i.getPrice(), getDescription()));
                }
                break;

            case FAILED: // Fail gracefully on failed responses.
                Log.v(TAG, "ItemDataRequestStatus: FAILED");
                break;
            }
    }
    //  ...
}

7. Initiate an In-App Purchase

You can now make a call to the In-App Purchasing API to initiate a purchase for a specific SKU.

If an item is purchased in your app, record the requestId from the PurchaseResponse object. From onPurchaseResponse() , save the requestId found in the PurchaseResponse object to ensure that your app does not grant an item multiple times. Save the requestId to a server or the device’s local storage.

If the item corresponding to the requestId has already been granted, the app does not need to take any action. Allowing the SDK to continue processing will remove the Receipt so that the same Receipt and requestId will not be sent to the app again.

Before your app grants an item to the customer, your app must verify that the item corresponding to the requestId has not already been granted.

Note: The SKU used directly in the code here for simplicity but it is a best practice to store SKUs in resource file (e.g strings.xml) or to pull them from a server.

public class IAPActivity extends Activity {
    //  ...
    private void initiatePurchase() {
        String requestId = PurchasingManager.initiatePurchaseRequest("com.amazon.example.iap.consumable");
    }
    //  ...
}

The matching onPurchaseResponse callback contains all the information you need to verify the purchase. It is important that you make sure your code can handle the onPurchaseResponse callback at any time while your app is running.

Purchase request status is has four possible values:

  1. SUCCESSFUL - Indicating that the purchase was successfully completed.
  2. FAILED - Indicating that the purchase failed.
  3. INVALID_SKU - Indicating that the SKU originally provided to the PurchasingManager.initiatePurchaseRequest(String) method is not valid.
  4. ALREADY_ENTITLED - Indicating that the customer already owns the provided SKU.

If the PurchaseResponse.PurchaseRequestStatus.SUCCESSFUL then a receipt will be returned in the PurchaseResponse

Every receipt will contain the item type, sku, subscription period, if the item type is a subscription and a PurchaseToken that can be used to validate a purchase via the Receipt Verification Service. The receipt is secure, and you can safely rely solely on it to authorize access to content or functionality within your app.

The PurchaseToken is dynamically generated each time receipt data is returned and is not a unique order identifier. They are unique values for out-of-app verification of the receipt with RVS.

Receipt data is returned in the PurchaseRsponse object and will contain a purchase token. The PurchaseToken of a Receipt returned in a PurchaseResponse will be different than the PurchaseToken of a Receipt for the same user and sku returned in a PurchaseUpdatesResponse.Regardless of the difference, each PurchaseToken will successfully validate against the Receipt Verification Service at any given time.

Receipt processing in the successful onPurchaseResponse(...) case differs between consumables, entitlements and subscriptions.

Consumables

In the consumable case, this is the only time you will see a receipt. You therefore must persist the relevant receipt data to allow you to provide the user with access to the consumable that they have purchased. Remember, consumable purchasable items are only valid for the user on the device that it was purchased on.

		public class MyPurchasingObserver extends BasePurchasingObserver {
		...
			public void onPurchaseResponse(PurchaseResponse response) {
				final PurchaseRequestStatus status = response.getPurchaseRequestStatus();
				
				if (status == PurchaseResponse.PurchaseRequestStatus.SUCCESSFUL) {
				    Receipt receipt = response.getReceipt();
				    ItemType itemType = receipt.getItemType();
				    String sku = receipt.getSku();
				    String purchaseToken = receipt.getPurchaseToken();
				
				    // Store receipt and enable access to consumable
				}
			}
	    ...
	    }
	

Entitlements

For entitlements, the receipt parsing code is the same as for consumables.

		public class MyPurchasingObserver extends BasePurchasingObserver {
		...
			public void onPurchaseResponse(PurchaseResponse response) {
				final PurchaseRequestStatus status = response.getPurchaseRequestStatus();
				
				if (status == PurchaseResponse.PurchaseRequestStatus.SUCCESSFUL) {
				    Receipt receipt = response.getReceipt();
				    ItemType itemType = receipt.getItemType();
				    String sku = receipt.getSku();
				    String purchaseToken = receipt.getPurchaseToken();
				
				    // Store receipt and enable access to entitlement
				}
			}
	    ...
	    }
	

Subscriptions

For subscriptions, an additional paramater will be returned inside the Receipt object - the SubscriptionPeriod. The SubscriptionPeriod object contains a start date and an end date representing the period of time during which a subscription is valid. If a subscription is valid, it will have a null end date. If the subscription has expired, the end date will contain the date that the subscription is no longer valid.

public class MyPurchasingObserver extends BasePurchasingObserver {
    //  ...
    public void onPurchaseResponse(PurchaseResponse response) {				
        final PurchaseRequestStatus status = response.getPurchaseRequestStatus();

        if (status == PurchaseResponse.PurchaseRequestStatus.SUCCESSFUL) {
            Receipt receipt = response.getReceipt();
            ItemType itemType = receipt.getItemType();
            String sku = receipt.getSku();

            String purchaseToken = receipt.getPurchaseToken();
            SubscriptionPeriod subscriptionPeriod = receipt.getSubscriptionPeriod();

            Date subscriptionStart = subscriptionPeriod.getStartDate();
            Date subscriptionEnd = subscriptionPeriod.getEndDate();

            Date today = new Date();

            boolean subscriptionHasStarted = subscriptionStart.before(today) || subscriptionStart.equals(today);
            boolean subscriptionHasEnded = subscriptionEnd != null && today.after(subscriptionEnd);

            if (subscriptionHasStarted && !subscriptionHasEnded) {
                // Entitle subscription
            }
        }
    }
    //  ...
}

8. Test Your App with SDKTester

Testing your app with the SDKTester app requires a JSON file listing all of the SKUs you wish to purchase. You can either download a JSON formatted version of your submitted IAP items or else build one yourself.

You can find information on testing your app with the SDKTester app here.

9. Send Receipt Data to Your Backend Server for Receipt Verification

		public class MyPurchasingObserver extends BasePurchasingObserver {
		...
			private boolean rvsProductionMode = false;
			
			public void onSDKAvailable(boolean isSandboxMode) {
				// Switch RVS URL from test to production
				rvsProductionMode = !isSandboxMode;
			}

		   public void onPurchaseUpdatesResponse(final PurchaseUpdatesResponse response) {
			...
				
				// Process receipts
				switch (response.getPurchaseUpdatesRequestStatus()) {
					case SUCCESSFUL:
						for (final Receipt receipt : response.getReceipts()) {
							...
							String purchaseToken = receipt.getPurchaseToken();
							String userId =  = receipt.getUserId();
							
							sendReceiptDataToYourBackendServer(purchaseToken, userId);
						}
				  ...
                }
			}

			public void onPurchaseResponse(PurchaseResponse response) {
				final PurchaseRequestStatus status = response.getPurchaseRequestStatus();
				
				if (status == PurchaseResponse.PurchaseRequestStatus.SUCCESSFUL) {
				    Receipt receipt = response.getReceipt();
				    String purchaseToken = receipt.getPurchaseToken();
					String userId =  = receipt.getUserId();
				    // Store receipt and enable access
				    							
				    sendReceiptDataToYourBackendServer(purchaseToken, userId);
				}
			}
	    ...
	    }
	

You can find information on implementing RVS and the testing with the RVS Sandbox here.

10. Submit Your App and In-App Items to Amazon

You are now ready to submit your app to the Amazon Appstore through the Amazon Apps & Games Developer Portal. You can find information on submitting your app and in-app items to Amazon here.

IAP v1.0 Deprecation Notice

In July 2014, Amazon released the In-App Purchasing (IAP) v2.0 API, which includes several important updates. As of April 30, 2016, Amazon will deprecate IAP v1.0 for new and updated app submissions. While this deprecation will not affect apps that are currently live in the Amazon Appstore, any apps submitted as either a new or updated app after April 30, 2016 will need to use IAP v2.0.