In this article we'll be looking at how you create and test In App Purchases (IAP) in your Android apps for the Amazon Store.
The methods shown in this guide require GameMaker 2.2.4 runtimes or newer, plus the "Amazon IAPs" extension from the Marketplace - if you're using 2.2.3 runtimes (or older) you will need to update and ensure you have the correct extension in your project before you can follow this guide.
Before continuing, you should have already setup and tested the Android or Amazon Fire export and have a test project or finished game that you want to add IAPs into.
You can find out how to set up GameMaker for the Android and Amazon platforms here:
This article also requires you to have set up at least one app on the Amazon Store, so if you haven't done that already then you should. You can find out all the information about submitting apps to the store from the following link:
IMPORTANT! Amazon IAPs do not permit any form of local verification for purchases, and so you will be required to create your own server and verify all purchases through that. That process is not covered in our documentation, as it is custom to your requirements and your chosen server technology. For more information, please see this article:
Creating In App Items
Before you can include any IAPs in your game, you must first define them from the Amazon Developer Dashboard. For this, you need to go to your apps page on the dashboard and select the In App Items section then click on Add Single IAP:
You have three types of IAP to choose from, and all of them are supported by GameMaker:
- Consumable - A consumable includes any type of content that you sell within your app that is consumed within the app itself. This type of content is available only on the device it is purchased from and a user can purchase consumable items repeatedly.
- Entitlement - An entitlement includes any type of content that you sell within your app that requires access rights. This type of content is available anywhere the user is logged into the Amazon Apps mobile client. The entitlement to the content does not expire, and users can only purchase entitled content once.
- Subscription - A subscription includes any type of content that carries an entitlement, is bound by a period of time, which auto-renews at the end of the period, and is available on all eligible devices registered to the user's Amazon account. Your app can have multiple subscriptions defined for it, with each subscription having multiple periods.
In this article as an example we'll create a consumable purchase, so go ahead and select that from the list under the Add Single IAP button. This will take you to a new page where you can give the item details:
The Title is a common name for the item and the SKU is a unique identifier for the item (also called the Product ID), and will be used to identify it in your games. Once you have given those details, you can click on the Save button to continue.
The next section is where you give all the information required for your IAP item to be made available in your game. You will be required to give the following details:
- General Information: This is the title and SKU of the item, which you gave when you initially created it. You can edit these details for now, but once the item has been submitted for sale, you cannot edit the SKU (but you can edit the title).
- Availability and Pricing: This will be visible for consumable and entitlement items only, and it's here that you set the price and availability for the item.
- Subscription Periods: This will be visible for subscription items only, and it's here that you specify subscription length and free trial information for the item.
- Description: Here you can enter a display name and a short descriptive text for the item.
- Images: Here you can add images for the item.
Amazon have an excellent article on each of these items that explains in full what they are used for and how they should be formatted, so we won't go into that here:
Once you have filled in all the required details, you can click on the Submit In-App Item to add it to the list of items available for your game. Note that all submitted items must be approved first before they can be used in your game, but this usually only takes a few minutes (and the status is reflected on the IAP dashboard):
You can go ahead and create further IAP items as required for your game now before continuing.
Setting Up IAP Testing
If you want to test your IAP code in your game without making actual purchases, you will need to download and install the Amazon App Tester app, as well as download some JSON files associated with each of the products that you want to use as In App Purchases. You can find a complete guide on how to set this up from the following link:
Setting Up Your Game
Now we have our initial IAP setup in the Amazon Developer dashboard, we need to prepare our game. For that you'll need to open the project in GameMaker and then install the Amazon IAP extension which you can get from the YoYo Games Marketplace. Once you have installed this in your game, you are ready to add the code.
Coding IAPs Overview
We need to now get down to coding our IAPs in GameMaker. But before we get to the details, let's take a moment to explain exactly how the IAP system works.
NOTE: This article does not detail all the different responses to each of the functions mentioned, and instead gives an overview of the general workflow required for IAPs to work on Android. For full descriptions of all responses and callbacks for each of the functions, please see the PDF document "Amazon In-App Purchases Manual" that is included with the extension.
The way that GameMaker deals with IAPs is event-driven, and most of it will be handled by the asynchronous IAP Event. The general workflow is as follows:
- At the start of the game, add the different IAP items to the internal products list.
- After adding the products but before permitting purchases, query existing products and get the data required for them to be displayed to the user.
- Next, get the data on purchases and if there are any consumable purchases or unfulfilled entitlement/subscription purchases outstanding, then consume or fulfil them.
- Enable/disable any features based on subscriptions or entitlement purchases that have been made previously
- Permit the game to run as normal and let the user purchase/consume products as required.
Adding Products To Your Game
The following code is an example of how you would typically initiate in-app purchases, and this would normally only be done once at the start of a game, either in a dedicated startup script or object, or in the Game Start event of the first object in your game. How you do it will depend on the project you are adding IAPs to.
The first thing to do would be to add the different products that you want in your game to the internal product list, something like this:
global.hash_list = ds_list_create();
global.ProductID[0, 0] = “amz_consumable”;
global.ProductID[1, 0] = “amz_entitlement”;
global.ProductID[2, 0] = “amz_subscription”;
AmazonIAPs_AddProduct(global.ProductID[0, 0]);
AmazonIAPs_AddProduct(global.ProductID[1, 0]);
AmazonIAPs_AddProduct(global.ProductID[2, 0]);
AmazonIAPs_GetProductData();
Notice how we have also initialised some global variables to store the product IDs that we want to include for purchase, as well as a few other details (these don't need to be global if all your purchase code is in a single controller object, and the DS list we'll explain in the section on Purchasing A Product). We also send a query to retrieve the product data from the store so that we can store it in variables and then display it to the user.
Getting Product Data
When you call the function AmazonIAPs_GetProductData(), it will generate an Asynchronous IAP Event with the data from the products query. In this event, the async_load DS map will have an "id" key with the constant amz_iap_product_data_response, as well as a key "json_response” which will hold a JSON object string with the product data. This can be parsed something like this:
var _eventId = async_load[? "id"];
switch (_eventId)
{
case amz_iap_product_data_response:
var _map = json_decode(async_load[? “json_response”]);
if _map[? “status”] == “SUCCESSFUL”
{
var _num = _map[? “product_num”];
var _list = _map[? “available_skus”];
var _sz = ds_list_size(_list);
for (var i = 0; i < _sz; ++i;)
{
var _pmap = _list[| i];
switch(_pmap[? “sku”])
{
case global.ProductID[0, 0]:
global.ProductID[0, 1] = _pmap[? “price”];
global.ProductID[0, 2] = _pmap[? “description”];
global.ProductID[0, 3] = _pmap[? “title”];
break;
case global.ProductID[1, 0]:
global.ProductID[1, 1] = _pmap[? “price”];
global.ProductID[1, 2] = _pmap[? “description”];
global.ProductID[1, 3] = _pmap[? “title”];
break;
case global.ProductID[2, 0]:
global.ProductID[2, 1] = _pmap[? “price”];
global.ProductID[2, 2] = _pmap[? “description”];
global.ProductID[2, 3] = _pmap[? “title”];
break;
}
}
// Parse any invalid responses here if required
// Query purchases here and react appropriately
AmazonIAPs_GetPurchasesUpdates(true);
}
ds_map_destroy(_map);
break;
}
Once you've retrieved the product data, you can then go ahead and request data on any outstanding and unfulfilled purchases or purchases that have already been made and should award entitlements or subscription features by calling the function AmazonIAPs_GetPurchasesUpdates(), as shown in the code above.
Getting Purchase Data
Now that we have initialised and gotten the data for each of our available purchases, we can deal with the response to the purchase request that was sent. This must be done before permitting any purchases within your game. When you call this function, it will generate another Asynchronous IAP Event where the async_load "id" will be amz_iap_purchase_status, and there will be an additional key "json_repsonse" with the purchase data. This can be handled in the same "switch" statement as the product request shown above, simply by adding another "case":
case amz_iap_purchase_status:
var _map = json_decode(async_load[? “json_response”]);
if _map[“success”] == true
{
var _list = _map[? “receipts”];
for (var i = 0; i < ds_list_size(_list); ++i;)
{
var _m = _list[| i];
var _r = _m[? “id”];
if _m[? “cancelled”] == false
{
switch (_m[? “sku”])
{
case global.ProductID[0, 0]:
global.Coins += 100;
AmazonIAPs_NotifyFulfillment(_r, amz_fulfillment_fulfilled);
break;
case global.ProductID[1, 0]:
case global.ProductID[2, 0]:
// Entitlement/Subscription: Here you would send
// off a verification request to your server using
// the “id” of the receipt, which in turn should
// should trigger an HTTP Async Event where you can
// enable features and notify Amazon.
break;
}
}
else AmazonIAPs_NotifyFulfillment(_r, false);
}
}
ds_map_destroy(_map);
break;
You'll notice that in the above code, we show where to verify the purchase with a server (this is not shown for consumables, simply so you can see how the fulfilment function works, but these should be verified through a server too). The format for verifying through a server is as follows:
"http://[server address]/amazon-verify?receiptId=" + [the ID of the purchase]
For example:
var _receipt = _m[? "id"];
http_get("http://localhost:3000/amazon-verify?receiptId=" + _receipt);
And then in the HTTP Asynchronous event you would deal with the results of the verification.
Also notice that all products need to be fulfilled. We'll discuss this in more detail further on in this article.
Purchasing A Product
Once you have queried the products and consumed and/or fulfilled any outstanding purchases you can then use the purchase function in your game. This can be called from any event and would be used something like this:
var _hash = AmazonIAPs_PurchaseProduct(global.IAP_PurchaseID[0]);
if _hash != amz_error_unknown
{
ds_list_add(global.hash_list, _hash);
}
Each purchase made will generate a unique hash code which can then be stored, ready to check against the Async IAP Event callback returned by the purchase function. You would then have something like the following code in this event to deal with the purchase callback. In this event the async_load "id" key will be amz_iap_receipt:
case amz_iap_receipt:
var _map = json_decode(async_load[? “json_response”]);
if _map[“success”] == true
{
var _hash = _map[? "request_hash"];
var _pos = ds_list_find_index(global.hash_list, _hash);
if hash != -1
{
ds_list_delete(global.hash_list, _pos);
var _list = _map[? “receipts”];
var _sz = ds_list_size(_list);
for (var i = 0; i < _sz; ++i;)
{
var _m = _list[| i];
var _r = _m[? “id”];
if _m[? “cancelled”] == false
{
switch (_m[? “sku”])
{
case global.ProductID[0, 0]:
global.Coins += 100;
AmazonIAPs_NotifyFulfillment(_r, amz_fulfillment_fulfilled);
break;
case global.ProductID[1, 0]:
case global.ProductID[2, 0]:
// Non_Consumable/Subscription: Here you would send
// off a verification request to your server using
// the “id” of the receipt, which in turn should
// should trigger an HTTP Async Event where you can
// enable features and notify Amazon.
break;
}
}
else AmazonIAPs_NotifyFulfillment(_r, false);
}
ds_map_destroy(_map);
}
}
break;
Fulfilling Products
You will have seen in the different code blocks shown above that we call the function AmazonIAPs_NotifyFulfillment() at various times. This is an essential step when dealing with purchases on Amazon, as it is what will be used to tell Amazon that a purchase has been made successfully or not.
When fulfilling the product, you give the receipt ID string – which is returned as part of the Asynchronous IAP Event callback for the function AmazonIAPs_PurchaseProduct() – and also a constant to indicate whether the purchase was successful or not. In general, this would be amz_fulfillment_fulfilled, but if the IAP has not been successfully purchased – for example, it’s been cancelled – then you need to set the fulfilled argument to amz_fulfillment_unavailable. If the purchase status is unknown or in progress, then you should not be calling this function.