Implement Appstore SDK IAP
To better understand the Appstore SDK 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.
- About the Android IAP package
- Integrate the IAP API with your app
- 1. Create placeholder methods
- 2. Implement and register PurchasingListener
- 3. Get user information
- 4. Implement getPurchaseUpdates method
- 5. Implement getProductData method
- 6. Implement code to make a purchase
- 7. Process the purchase receipt and fulfill the purchase
- 8. Send fulfillment result to Amazon and grant item to the user
- 9. Cancel the purchase
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:
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 byPurchasingService
.
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 |
Manifest requirements
If you want to use the In-App Purchasing API and your app targets Android API level 30 or higher, you must define the list of packages your app needs to query in the AndroidManifest.xml file. To be able to query Amazon App Tester and Amazon Appstore, add the following code to your manifest file.
<manifest>
...
<queries>
<package android:name="com.amazon.sdktestclient" />
<package android:name="com.amazon.venezia" />
</queries>
</manifest>
Make sure to also update your manifest to receive intents from the ResponseReceiver
class. For details, see ResponseReceiver.
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:
registerListener(PurchasingListener purchasingListener)
: This method is the mechanism through which callbacks occur. CallregisterListener()
before calling other methods in thePurchasingService
class. ThePurchasingListener
object allows your app to listen for and process the callbacks triggered by theResponseReceiver
. CallregisterListener()
in theonCreate()
method in the main UI thread of your app.getUserData()
: Call this method to retrieve the app-specific ID and marketplace for the user who is currently signed in. For example, if a user switched accounts or if multiple users accessed your app on the same device, this call helps you make sure that the receipts that you retrieve are for the current user account. CallgetUserData()
in theonResume()
method.getPurchaseUpdates(boolean reset)
:getPurchaseUpdates()
retrieves all Subscription and Entitlement purchases across all devices. A consumable purchase can be retrieved only from the device where it was purchased.getPurchaseUpdates()
retrieves only unfulfilled and canceled consumable purchases. Amazon recommends that you persist the returnedPurchaseUpdatesResponse
data and query the system only for updates. The response is paginated. CallgetPurchaseUpdates()
in theonResume()
method.getProductData(java.util.Set skus)
: Call this method to retrieve item data for a set of SKUs to display in your app. CallgetProductData()
in theonResume()
method.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 theFulfillmentResult
of the specifiedreceiptId
. Possible values forFulfillmentResult
areFULFILLED
orUNAVAILABLE
.enablePendingPurchases()
: Call this method to enable the Pending Purchase feature, for kids and parents, in your app. When thePurchaseResponse.RequestStatus
value isPENDING
, the purchase is not yet approved. Do not grant the entitlement to the customer if the status is pending. If the purchase is approved, it's processed in the usual way when you callgetPurchaseUpdates()
in theonResume()
method. For more details, see Implement Pending Purchases.
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:
onUserDataResponse(UserDataResponse userDataResponse)
: Invoked after a call togetUserData()
. Determines the user ID and marketplace of the currently signed-in user. Also provides the consent status of the user for Quick Subscribe.onPurchaseUpdatesResponse(PurchaseUpdatesResponse purchaseUpdatesResponse)
: Invoked after a call togetPurchaseUpdates()
. Retrieves the purchase history. Amazon recommends that you persist the returnedPurchaseUpdatesResponse
data and query the system only for updates.onProductDataResponse(ProductDataResponse productDataResponse)
: Invoked after a call togetProductDataRequest()
. Retrieves information about SKUs you would like to sell from your app. Use the valid SKUs from theProductDataResponse
object.onPurchaseResponse(PurchaseResponse purchaseResponse)
: Invoked after a call topurchase()
. Used to determine the status of a purchase.
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:
UserDataResponse
: Provides the app-specific user ID and marketplace for the currently signed-in user. Also provides the consent status of the user for Quick Subscribe.PurchaseUpdatesResponse
: Provides a paginated list of receipts. Receipts are unordered.ProductDataResponse
: Provides item data, keyed by SKU. ThegetUnavailableSkus()
method lists any SKUs that are unavailable.PurchaseResponse
: Provides the status of a purchase initiated within your app. Note that aPurchaseResponse.RequestStatus
result ofFAILED
can simply mean that the user canceled the purchase before completion.
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:
- Call
registerListener()
in youronCreate()
method. - Call
getUserData()
in youronResume()
method. - Call
getPurchaseUpdates()
in youronResume()
method. - Call
getProductData()
in youronResume()
method.
Invoke registerListener()
in the onCreate()
method of the app's main activity. Invoke the other three methods in the onResume()
method of the app's home activity (the primary activity used by the app). 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 theLicensingService
class. If you're using IAP v2.0 check for sandbox mode with thePurchasingService.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:
// Customer isn't signed in to Amazon on the device, or
// there is a connection issue between the app and Appstore.
// Temporarily disable in-app purchases for the user on this device
// until details about user and product are successfully retrieved.
// When the getProductData method successfully retrieves ProductData,
// purchases are re-enabled.
iapManager.disableAllPurchases();
break;
case NOT_SUPPORTED:
// Device doesn't support IAP functionality.
// Disable in-app purchases for the user on this device.
iapManager.disableAllPurchases();
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.
getPurchaseUpdates()
for your app to sync purchases from the Appstore. If you don't perform this required step, your app could fail to grant customers items that they purchased. Don't restrict this call based on any business criteria, such as whether the customer is signed in or out, or what the customer's subscription status is.
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 togetPurchaseUpdates()
. Retrieves the receipts for the user's unfulfilled consumable, entitlement, and subscription purchases. The Appstore recommends using this approach in most cases.Note: If your app enables Pending Purchases, the Appstore returns all unfulfilled consumable and entitlement receipts for the user in everygetPurchaseUpdates(false)
call, until your app callsnotifyFulfillment()
. If you do not enable Pending Purchases, only unfulfilled consumable receipts will be returned in everygetPurchaseUpdates(false)
call. -
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. Usetrue
when you require a full list of a user's purchases, such as when a customer wants to restore a purchase, or you've detected a consistency issue between your app and the Appstore.
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 inonPurchaseResponse()
but won't receive a receipt fromgetPurchaseUpdates()
. In all other cases, you receive a receipt for consumables. ThegetPurchaseUpdates()
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);
}
iapManager.refresh();
break;
case FAILED:
// If user data is present, FAILED indicates an issue on the Appstore side.
// Retry after some time.
// Temporarily disable in-app purchases for this device
// until details about user and product is successfully retrieved.
// When the getProductData method successfully retrives ProductData, purchases are re-enabled.
iapManager.disableAllPurchases();
break;
case NOT_SUPPORTED:
// Device doesn't support IAP functionality.
// Disable in-app purchase for this device.
iapManager.disableAllPurchases();
}
}
//...
}
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" );
}
productSkus
for getProductData()
to validate. You need to include the child SKUs because price information is associated with each child SKU. The parent SKU does not have a price, as price varies depending on subscription duration. For additional information, see subscription item FAQs.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);
}
// SKUs aren't available for purchase by the customer.
// Disable purchase for unavailable SKUs.
iapManager.disablePurchaseForSkus(response.getUnavailableSkus());
// Enable purchase for all available SKUs
iapManager.enablePurchaseForSkus(response.getProductData());
iapManager.refresh();
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" );
// If user data is present, FAILED indicates an issue on the Appstore side.
// Retry after some time.
// Temporarily disable in-app purchases for this device
// until details about user and product is successfully retrieved.
// When the getProductData method successfully retrives ProductData, purchases are re-enabled.
iapManager.disableAllPurchases();
break;
case NOT_SUPPORTED:
// Device doesn't support IAP functionality.
// Disable in-app purchase for this device.
iapManager.disableAllPurchases();
}
}
// ...
}
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.handlePurchase(receipt, response.getUserData());
iapManager.refresh();
break;
case ALREADY_PURCHASED:
// Customer already has an active entitlement for the item.
// App and Appstore are out of sync. Re-sync all purchases from Appstore.
PurchasingService.getPurchaseUpdates(true);
break;
case INVALID_SKU:
Log.d(TAG,
"onPurchaseResponse: invalid SKU! onProductDataResponse should have disabled buy button already.");
final Set<String> unavailableSkus = new HashSet<String>();
unavailableSkus.add(response.getReceipt().getSku());
iapManager.disablePurchaseForSkus(unavailableSkus);
break;
case FAILED:
// Customer exited before completing the purchase journey
Log.d(TAG, "onPurchaseResponse: failed so remove purchase request from local storage");
iapManager.showPurchaseFailedMessage(response.getReceipt().getSku());
break;
case NOT_SUPPORTED:
Log.d(TAG, "onPurchaseResponse: failed so remove purchase request from local storage");
iapManager.showPurchaseFailedMessage(response.getReceipt().getSku());
// Device doesn't support IAP functionality.
// Disable in-app purchase for this device.
iapManager.disableAllPurchases();
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.
cancelDate
in a RVS response to prevent refund fraud. If you do not verify the cancel date, a customer could cancel a purchase and continue to receive services. See IAP Best Practices for more information on how to check the cancelDate
field.In the following example, the handlePurchase()
method checks whether the receipt is canceled.
- If the receipt is canceled and the item was previously fulfilled, call the
revokePurchase()
method to revoke the purchase. - If the customer already has access to the content because they made the purchase outside of Amazon, call
cancelPurchase()
to notify Amazon that purchase cannot be fulfilled. - If the receipt is not canceled, verify the receipt from your server using RVS, then call
grantPurchase()
to fulfill the purchase.
public void handlePurchase(final Receipt receipt, final UserData userData) {
try {
if (receipt.isCanceled()) {
revokePurchase(receipt, userData);
} else {
// Amazon strongly recommends that you verify the receipt on the server-side
if (!verifyReceiptFromYourService(receipt.getReceiptId(), userData)) {
// If the purchase can't be verified,
// show relevant error message to the customer.
mainActivity.showMessage("Purchase cannot be verified, please retry later.");
return;
}
if (itemAlreadyPurcahsed(receipt, userData)) {
// If the item is already purchased from other stores, invoke
// cancel purchase and show relevant error message to the customer.
cancelPurchase(receipt, userData);
return;
}
if (receiptAlreadyFulfilled(receipt.getReceiptId(), userData)) {
// If the receipt was previously fulfilled, notify Amazon
// Appstore it's fulfilled again.
PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.FULFILLED);
return;
}
grantPurchase(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. Send fulfillment result to Amazon and grant item to the user
When you send a fulfillment result, you ensure that Amazon can confirm whether users can access the content that they paid for. Always communicate the fulfillment result to Amazon. Use the notifyFulfillment()
method to send a FulfillmentResult
.
- If the customer has successfully created their account and can access content on your service, fulfill the item and call
notifyFulfillment()
withFulfillmentResult.FULFILLED
. After this step is complete, the Amazon Appstore no longer tries to send the purchase receipt to the app. - If the customer already has an existing account and subscription for your service, or the customer is not eligible to sign up for an account for your service, call
notifyFulfillment()
withFulfillmentResult.UNAVAILABLE
.
For a concise reference of which FulfillmentResult
to send, see Guidelines for calling notifyFulfillment.
To grant the item to the user, create a purchase record for the purchase and store that record in a persistent location. The following code shows how to send the fulfillment result to Amazon and how to grant an item to a customer.
The following code shows how to send the fulfillment result to Amazon and how to grant an item to a customer.
private void grantConsumablePurchase(final Receipt receipt, final UserData userData) {
try {
// This code shows a basic implementation. In your app, make sure your
// logic is thread-safe, transactional, and robust.
// Create the purchase information in your app and server
// and grant the purchase to customer.
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 no longer applicable, 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 succeeded.
userIapData.setRemainingOranges(userIapData.getRemainingOranges() + 1);
saveUserIapData();
Log.i(TAG, "Successfuly update purchase from PAID->FULFILLED for receipt id " + receipt.getReceiptId());
// Send the fulfillment result to Amazon Appstore. After Amazon receives
// the fulfillment result for the purchase, the Appstore stops trying to send
// the purchase receipt to the app.
PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.FULFILLED);
} else {
// Update purchase status in SQLite database failed.
// Status already changed.
// This usually indicates the same receipt was updated by another
// onPurchaseResponse or onPurchaseUpdatesResponse callback.
// This code only logs the error.
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 can't fulfill the purchase,
// add your own error handling code here.
// The next time you call PurchasingService.getPurchaseUpdates,
// Amazon will try to send the consumable purchase receipt again.
Log.e(TAG, "Failed to grant consumable purchase, with error " + e.getMessage());
}
}
Guidelines for calling notifyFulfillment
When you call notifyFulfillment()
, use these guidelines for which FulfillmentResult
to send.
Send FULFILLED
when the following conditions are met:
- The customer has successfully created their account and can access content on your service.
Send UNAVAILABLE
when either of these conditions occur:
- The customer already has an existing account and subscription for your service.
- The customer is not eligible to sign up for an account for your service.
9. Cancel the purchase
To cancel the purchase of an item, update the information to persistent storage and your back-end server, and call notifyFulfillment()
with status UNAVAILABLE
to inform Amazon that you can't complete fulfillment.
private void cancelPurchase(final Receipt receipt, final UserData userData) {
// Update the purchase information in your app and server to identify
// if there was a duplciate purchase attempt by the customer.
// Notify Amazon that item can't be fulfilled using the
// UNAVAILABLE fulfillment status
updatePurchaseStatus(receipt.getReceiptId(), null, PurchaseStatus.UNAVAILABLE);
PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.UNAVAILABLE);
return;
}
Last updated: Nov 06, 2024