Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more

Tech

Android In-App Purchases

Android_inapp_blog

Posted by Mark Alexander on 12 April 2018

In this article we'll be looking at how you create and test In App Purchases (IAP) in your Android apps for the Google Play app store. We'll be covering iOS and macOS in-app purchases in future tech blogs, but they are also explained in the relevant section of the YoYo Games Helpdesk.

Before continuing, you should have already setup and tested the Android 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 Studio 2 for the Android platform here:

Note that if you want to add Amazon Appstore IAPs (using the Game Circle API) this is a slightly different process and so there is a separate in-IDE Tutorial available here:


Set Up The IAPs On Google Play

Before you can add any IAP code and test it, you first have to set up an app listing on your Google Play Developer Console for the game and you will also have had to upload an APK to one of the available channels for testing - either Internal Test (recommended), Alpha or Beta is fine. Once that has been done, you can then go to the Store Presence section and select In App Products to take you to the IAP setup section:

IAP Setup Page

Here is where we'll be creating the initial in-app purchase details.

For the sake of this tutorial, we'll be creating two simple IAPs, one which is Consumable and one which is Durable (non-consumable). Consumable purchases are things that can be bought repeatedly, while non-consumables are things that can only be bought once. An example of a consumable would be when the player loses the game and is offered the chance to buy a continue, and if they then die a second time they can buy another continue, and so on. An example of a durable would be a game that shows ads but permits the player to pay to have them removed - the player pays once only and the ads are gone forever.

NOTE: In the Google Play console, all products that can be bought in GameMaker Studio 2 are classed as Managed Products and there is no distinction between consumable and durable at this level. That distinction will be handled later, in the code for the game.

To start with, we click the button labelled Create Managed Product. In the window that opens you will need to fill out the following details:

  • The unique IAP Product ID: This is the product ID string, which must start with a lowercase letter or a number and must be composed of only lowercase letters (a-z), numbers (0-9), underscores (_), and periods (.). Be sure to plan your product ID namespace carefully as you cannot modify an item's product ID after the item is created and you cannot reuse a product ID within an app.

    IAP Product ID

  • Title and Description: Give the IAP a title and a description to identify it easily.

    IAP Title and Description

  • Status: The status should be set to Active, otherwise it won't be available for purchase.

    IAP Status

  • Pricing: Set the base price for the purchase.

    IAP Pricing

With that done, you can click Save.

You can now go ahead and create as many further products as you require (for this tutorial you'll need to make one more for a durable product) and when you're done, go back to the in-app purchase dashboard, where we can see the products all listed together:

IAP Managed Products


Setting Up Your Game

Now we have our initial IAPs set up in the Google Play Console, we need to prepare our game. For that you'll need to get the Google Licence Key from the Console and add it into GameMaker Studio 2. You can get the key by going to the section Development Tools > Services and APIs:

IAP Licence Key

Carefully copy this whole string and then in GameMaker Studio 2 open the Android Game Options and browse to the section Packaging. Here you need to paste the licence key string into the section labelled Google Licensing Public Key:

Android Game Options

IMPORTANT! You do NOT need to check the "Enable Google Licensing" for IAPs to work. That is a separate Google product designed to be a simple DRM for your game.

We now need to install the extensions that the IAP system requires to communicate with the Google APIs. The first one we need is the Google Play Services extension. You can get this easily by going to the Game Options > Add-ons page and clicking the Download button next to the extension.

Google Addons

This will add the extension to your project, and you can close the Game Options once this is complete.

NOTE: The Google Play Services extension comes in two parts, one for Ads and one for the general Google Play Services. If you do not require ads, then that section can be removed from the extension.

We will also need to add in another extension, this time to permit the IAPs to work. For that you will need to go to the Marketplace and subscribe to the following item:

Once you have subscribed to the extension, you can then go to My Library and import it into the project via Marketplace > My Library inside GMS2 (tip: use the search box to find it quickly):

Install Extension

Once you have downloaded and imported that extension into your project, your Resource Tree should look like this:

Installed Extensions

You are now ready to start programming IAP support into your game.


Coding IAPs Overview

We need to now get down to coding our IAPs in GameMaker Studio 2. But before we get to the details, let's take a moment to explain exactly how the IAP system works.

The way that GameMaker Studio deals with IAP is event-driven, and most of it will be handled by the Asynchronous IAP Event. The general workflow is as follows:

1 You check for a secure stored purchase map and load it if found, otherwise you create a new one and save it.

2 You then activate all purchases, which will trigger an IAP Event where you can query the status of the available products.

3 In your game you have objects that connect to the store to request a product, which also triggers an IAP event where you can deal with the purchase (or failure thereof).

4 The IAP event is parsed to deal with the purchase, taking all data from a special DS map called iap_data.

5 If successful, you write the purchase to your purchase map and secure save it. Then activate the product that the user has bought (you might not do this immediately after the purchase when dealing with consumable products, but this depends entirely on what your IAP requires).

6 If the purchase is consumable, you can "consume" it at any point in the game, which frees the product up to be purchased again. This will trigger another IAP event informing you of the consumption, in which case you would update the purchase map and secure save again.


Initialise Your IAPs

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 required from you is to create a purchase map for tracking purchases between runs of the game. GameMaker Studio 2 does not perpetuate purchase information automatically, so you will have to do this yourself. Creating this map has the added benefit of permitting you to check it directly for purchases even when the target store is offline (as you will see in the code below), although it is not strictly necessary.

In our example code, the consumable will simply be "test_consumable", with each purchase adding 1000 to a value saved in the save map, and our durable will be called "test_nonconsumable" and be stored in another map value. We'll also store these strings in variables so that if we decide to change them or use the code in other games, then we only need to change them in one place.

IMPORTANT! Google Play offer a special static IAP name "android.test.purchased" which can be used instead of your own IAP names. This will work like a regular IAP but requires no setting up on the developer console, nor does it require test accounts and other permissions, making it ideal to set everything up and test it before pushing an app into alpha or beta stages with live testers. See here for more information.

So, our save map will hold one product key and one product value, where the durable product key will have an initial value of false and the consumed product value will be set to 0. The code should look something like this (note we have no need to check for consumable purchases as they are consumed the moment they are bought):

// Store the product names in variables for easy editing
product_consumable = "test_consumable";
product_durable = "test_nonconsumable";
var map_create = true; // This is to tell us if we need to create the save map
if (file_exists("iap_data.dat") // Check for the file)
{
    // The file exists so load the save map
    global.savemap= ds_map_secure_load("iap_data.dat");
    // Check the map has been loaded correctly
    if (ds_exists(global.savemap, ds_type_map))
    {
        // Check to see if the durable IAP has been used
        if (ds_map_exists(global.savemap, product_durable))
        {
            map_create = false; 
            if (ds_map_find_value(global.savemap, product_durable) == false)
            {
                // The durable IAP has not been used, so do something
                // like enable ads, or disable extra content, etc...
            }
        }
    }
}

// The save map doesn't exist, so create it and initialise the IAPs to false
if (map_create)
{
    global.savemap = ds_map_create();
    ds_map_add(global.savemap, product_durable, false);
    ds_map_add(global.savemap, "consumed", 0);
    ds_map_secure_save(global.savemap, "iap_data.dat");
}

As you can see, we first check to see if there is a file with saved purchase data, and if there is we parse it for the durable "test_nonconsumable" product and then if that has not been bought we do something. We then check to see if we have any consumables pending, and if we do we call the iap_consume function. This will trigger an Asynchronous IAP event where you can then deal with it (this is important, as before another instance of that consumable can be purchased the first must be cleared, so this check ensures that any leftover consumables from a previous run are used and the player can purchase more). This is explained further in the section below on "Acquiring A Product".

Notice that in the above code if the file is not found or the file data is corrupted in some way, then we create a new purchase map, initialise the products as not being bought, and then secure save that.

With that done, the next thing to do is to set up the purchase data itself and connect the game to the target store. All this is done using the iap_activate function, as shown below:

var durable_map = ds_map_create();
var productList = ds_list_create();
// Create durable iap
ds_map_add(durable_map , "id", product_durable);
ds_map_add(durable_map , "title", "Test Durable");
ds_map_add(durable_map , "type", "Durable");
ds_list_add(productList, durable_map);
// Create consumable IAP
var consumable_map = ds_map_create();
ds_map_add(consumable_map, "id", product_consumable);
ds_map_add(consumable_map, "title", "Test Consumable");
ds_map_add(consumable_map, "type", "Consumable");
ds_list_add(productList, consumable_map);
// Activate IAP
iap_activate(productList);
// Clean up
ds_map_destroy(durable_map);
ds_map_destroy(consumable_map);
ds_list_destroy(productList);

With that, our products will be activated and each one will trigger its own Asynchronous IAP Event of the type "iap_ev_product", where (if you wish), you can get the full details of the purchase as they are pulled from the store. To get this information you'd have something like this:

// Get the event type
var _id = iap_data[? "type"];
// Perform different code depending on the type
switch (_id)
{
    case iap_ev_storeload: // Check to see if the store has loaded or not
        if (iap_data[? "status"] == iap_storeload_ok)
        {
            show_debug_message("STORE LOADED");
        }
        else show_debug_message("STORE NOT LOADED");
        break;

    case iap_ev_product: // Get product details
        var _product = iap_data[? "index"];
        var _map = ds_map_create();
        iap_product_details(_product, _map);
        show_debug_message("PRODCT ACTIVATED - " + string(_product));
        show_debug_message("ID - " + string(_map[? "id"]));
        show_debug_message("Title - " + string(_map[? "title"]));
        show_debug_message("Description - " + string(_map[? "description"]));
        show_debug_message("Price - " + string(_map[? "price"]));
        show_debug_message("Type - " + string(_map[? "type"]));
        show_debug_message("Verified - " + string(_map[? "verified"]));
        ds_map_destroy(_map);
        break;
}

Note that we have an IAP event type for the Store being loaded. This is not triggered by any function, but will instead be triggered automatically at the start of your game and can be used to check the initial state of the Google Play store. For example, if this returns iap_storeload_failed then you can disable IAPs for that run of the game (you could then use the function iap_status() to poll the condition of the store during the game and re-enable your IAPs).

Also note that in the above example we simply output the returned data to the console, but in your projects you can use this to display information to the user, disable/enable buttons, etc...

IMPORTANT! Activating a product may also trigger the purchase IAP event, with the iapdata ID "iapev_purchase". This will happen if a product has been purchased previously. See the next section for more details.


Acquiring A Product

You'll now want to add a button instance into your game (or something similar) for the user to click to purchase a product. This is where you would call the function iap_acquire(), which will send off the purchase request to the target store. The actual code to do this is pretty simple and will look something like the following (this code, for example, would go in a Global Mouse Pressed event):

if (iap_status() == iap_status_available)
{
    // Check consumable button press
    if (point_in_rectangle(mouse_x, mouse_y, x - 264, y - 32, x - 136, y + 32))
    {
        // Try to acquire the IAP
        iap_acquire(product_consumable, "");
    }
    // Check durable button press
    if (point_in_rectangle(mouse_x, mouse_y, x + 136, y - 32, x + 264, y + 32))
    {
        if (global.saveMap[? product_durable] == false)
        {
            iap_acquire(product_durable, "");
        }
    }
}
else { show_message_async("Store is not available."); }

As you can see, we first check to see that the store is available, then we check our previously created purchase map to see if the product has been bought already. The code will trigger an IAP Event of the type "iap_ev_purchase", which will contain details of the purchase being made as well as whether it has succeeded or not.

NOTE: This event type will be triggered on game start if there is a purchase that has not been consumed, so you can use it to check durable purchases.

You already have added this async event and the switch statement as part of the last section of this FAQ, so you'd simply now expand your code to include the following case:

 case iap_ev_purchase:
    var _product = iap_data[? "index"];
    var _map = ds_map_create();
    iap_purchase_details(_product, _map);
    show_debug_message("PRODCT PURCHASED - " + string(_product));
    show_debug_message("Product - " + string(_map[? "product"]));
    show_debug_message("Order - " + string(_map[? "order"]));
    show_debug_message("Token - " + string(_map[? "token"]));
    show_debug_message("Payload - " + string(_map[? "payload"]));
    show_debug_message("Receipt - " + string(_map[? "receipt"]));
    show_debug_message("Response - " + string(_map[? "response"]));
    switch (_map[? "status"])
    {
        case iap_available:
            show_debug_message("iap_available");
            break;
        case iap_failed:
            show_debug_message("iap_failed");
            break;
        case iap_purchased:
            show_debug_message("iap_purchased");
            if _map[? "product"] == product_consumable
            {
                show_debug_message("Consumable IAP Purchased");
                iap_consume(product_consumable);
            }
            if _map[? "product"] == product_durable
            {
                show_debug_message("Durable IAP Purchased");
                global.savemap[? product_durable] = true;
                ds_map_secure_save(global.savemap, "iap_data.dat");
            }
            break;
        case iap_canceled:
            show_debug_message("iap_canceled");
            break;
        case iap_refunded:
            show_debug_message("iap_refunded");
            break;
    }
    ds_map_destroy(_map);
    break;

Here, if a durable purchase is detected then we set the save map to true and save the file out for all subsequent runs. If a consumable is purchased we immediately call the function iap_consume() to use the purchase and free it up so it can be bought again.

When you call the consume function, a further callback will be generated in the async IAP event. This time it will have the ID "iap_ev_consume" and you would parse it in the same switch as before, like this:

case iap_ev_consume:
    if iap_data[? "product"] == product_consumable
        {
        global.savemap[? "consumed"] += 1000;
        ds_map_secure_save(global.savemap, "iap_data.dat");
        }
    break;

Restoring Purchases

The code we've shown so far already somewhat covers restoring purchases, as un-consumed purchases will trigger an IAP async event of the type "iapevpurchase" when the game starts, but you can also add a dedicated "restore" button to your app if required. In this button you'd simply call the function iaprestoreall().

This function will trigger two different IAP events:

  • iap_ev_restore: The iap_data map will have an additional "result" key which will be either true or false, depending on whether the restore was successful or not.

  • iap_ev_purchase: If the restore event was a success, the Async IAP event will be triggered once for each available purchase with this event type so you can check the purchase details.

Note that for Android a "restore purchase" button is not mandatory, especially if you have added restore code into the Game Start to deal with previous purchases. However it's good practice to have the option (and we recommend it) as it is required on iOS and Mac, and it's also possible that the store couldn't be reached on startup and so this gives the user a way to restore their purchases at a later time in that game session.


Summary

Setting up IAPs is a lot simpler than it may first appear, and basically comes down to the following points:

  • Set up a store presence for the game and upload an APK
  • Set up IAPs for the game on the store app console
  • Add the required extensions to GameMaker Studio 2
  • Code your IAPs, with most of the processing being done in the dedicated IAP Async Event
  • Upload the final game APK when ready
  • Test!

It's also important to note that the code shown in this article is suitable for use cross-platform on all supported stores apart from Amazon (as mentioned at the start). You would simply change the product names as required in the Create Event.

Back to Top