Overview
Apps integrate with IAP Vega Script library to enable the In-App Purchasing feature for customers within the app. There are functions offered via the library to retrieve information, make purchases, and notify Amazon about the fulfillment of a purchase. This post talks about how to integrate GetPurchaseUpdates into your app in order to retrieve all purchase transactions by a user since the last time the method was call
- GetPurchaseUpdates - Retrieves all Subscription and Entitlement purchases across all devices. A consumable purchase can be retrieved only from the device where it was purchased. This function retrieves only unfulfilled and canceled consumable purchases. Amazon recommends that you persist the returned receipts data and query the system only for updates. The response is paginated.
Integrate getPurchaseUpdates Function
The getPurchaseUpdates() method retrieves all purchase transactions by a user since the last time the method was called. Call getPurchaseUpdates() on app launch and when app transitions from Background to Foreground to make sure you are getting the latest updates.
Important: You must call
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. Doing so would cause unwanted friction to the customer experience.
The request takes in 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. -
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.
Note about device cache
The behavior of the
getPurchaseUpdates()
method depends on the data available on the client (device) cache. When the customer signs in from a new device or clears the cache of the current device, the firstgetPurchaseUpdates()
call returns all receipts without regard to the value ofreset
. Subsequent calls withreset
set tofalse
return only the latest receipt since the last call.Some scenarios, like a user signing in on a different device or a user resetting a device, can clear the data on the client device. These scenarios make it possible for a receipt to be returned multiple times. Your app must be able to identify new and old receipts and handle each properly.
Implementation Details
There are two ways how the getPurchaseUpdates can be invoked in JS Apps.
- Using PurchasingService.getPurchaseUpdates() async JS Function:
You can use PurchasingService.getPurchaseUpdates withreset
flagfalse
wrapped around AppState to ensure the purchase updates are received on the app during app launch and app BG → FG transitions. For other use-cases such as restore purchases, PurchasingService.getPurchaseUpdates need to be invoked withreset
flagtrue
.
//...
useEffect(() => {
triggerGetPurchaseUpdates();
// Subscribe to app state changes and call getPurchaseUpdates functon when app comes from
// background to foreground. This is needed to retrieve the purchases across devices.
appStateSubscription.current = AppState.addEventListener('change', handleAppStateChange);
return () => {
Logger.debug(`Component unmounted. Removing AppState listener`);
appStateSubscription.current?.remove();
}
}, []);
//...
const triggerGetPurchaseUpdates = () => {
IAPSDKLogger.info(`Calling getPurchaseUpdates function with reset flag = ${purchaseUpdatesRequest.reset}`);
PurchasingService.getPurchaseUpdates(purchaseUpdatesRequest)
.then((apiResponse) => {
// handle getPurchaseUpdates response
});
};
//...
const handleAppStateChange = (nextAppState) => {
// Call the getPurchaseUpdates function if App comes from background to foreground and its not already in-progress
if (nextAppState === 'active' && iapPurchaseUpdatesLoadingCurrentVal === false) {
Logger.debug('App comes from background to foreground. Triggering getPurchaseUpdates API.');
triggerGetPurchaseUpdates();
}
};
//...
- By using useIapPurchaseUpdates() hook:
useIapPurchaseUpdates hook should be called in your main screen component. This hook automatically invokes getPurchaseUpdates API and populates states during main screen launch and App state transitions from BG → FG. “iapPurchaseUpdates” state returned by this hook has unfulfilled and canceled consumable purchases. * Amazon recommends that you persist the returned PurchaseUpdates data and query the system only for updates by calling the hook with reset flag being false.
//...
export const AppMainPage = () => {
// Calling useIapPurchaseUpdates in main screen
const {iapPurchaseUpdatesLoading, iapPurchaseUpdatesError, iapPurchaseUpdates} =
useIapPurchaseUpdates({ reset: false });
// After purchaseUpdates is loaded, call handler
if (!iapPurchaseUpdatesLoading) {
if (iapPurchaseUpdatesError) {
// Handle Error based on iapPurchaseUpdates.responseCode
} else {
// Persist the iapPurchaseUpdates.receiptList and fulfill the purchases.
}
}
};
//...
Handling the response:
//...
public static handlePurchaseUpdatesResponse = async (response: PurchaseUpdatesResponse): Promise<UserData> => {
const responseCode = response.responseCode;
switch (responseCode) {
case PurchaseUpdatesResponseCode.SUCCESSFUL:
// Handle each receipt
for (const receipt of response.receiptList) {
IAPManager.handleReceipt(response.userData.userId, receipt);
}
// if there are more receipts, call the getPurchaseUpdates function again.
if (response.hasMore) {
PurchasingService.getPurchaseUpdates({ reset: false })
.then((res) => {
return this.handlePurchaseUpdatesResponse(res);
});
}
break;
case PurchaseUpdatesResponseCode.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 PurchaseUpdatesResponseCode.NOT_SUPPORTED:
// Device doesn't support IAP functionality.
// Disable in-app purchase for this device.
IAPManager.disableAllPurchases();
break;
default:
break;
}
return response.userData;
};
//...
Please refer the IAP integration document in developer portal for implementation details about other IAP functions.