What began as an R&D effort for internal use became a feature for external users of GameMaker: Studio and as such had certain features and elements that in the long term have proven undesirable.
The second iteration of IAP support within GameMaker: Studio still aims to provide a "one codebase fits all platforms" solution where possible since, in spite of their ontological differences, all the stores really do at a very simple level is provide the ability for a user to purchase a product without leaving the current application.
Products and Purchases
There is now a clear delineation between an IAP product and an IAP purchase and products themselves are now purchase state agnostic. When a purchase is attempted you will receive an identifying handle to that purchase that will remain valid for the current lifetime of the application or until iap_consume() is executed to a successful conclusion for that product. On certain platforms (e.g. Google Play) an order id is associated with a successful purchase and its value can be used to access the information about that purchase on subsequent runs of the application.
(NB: You must explicitly check the “status” field provided through iap_purchase_details() to find out whether or not a purchase was successful)
There will only ever be one purchase active/available per product. You cannot purchase a consumable product multiple times in a row without consumption: you must perform a consume operation between subsequent purchases of a specific product.
GameMaker: Studio no longer internally maintains the set of products in ds_list form and as such we no longer accept a purchase index when attempting to acquire a product and instead require you to provide us with the product id string. We also now accept a user payload that gets sent as part of the purchase bundle on Google Play and can be used later for verifying a successful purchase.
The data held by GameMaker: Studio for a product can still be accessed using a pre-generated ds_map via:
iap_product_details(id, map);
The fields you should expect to find in the map after such a call are:
- "id"
- "title"
- "description"
- "price"
- "type": either “Consumable” or “Durable”, if available (see notes).
- "verified": whether or not GameMaker: Studio has received details of this product from the store and thus believes this product to be available for purchase.
(NB: We no longer support "content_url" for products. The functionality this field previously provided is available via http_get_file() and zip_unzip())
You will be able to access the data associated with a purchase using a pre-generated ds_map via:
iap_purchase_details(index, map);
The fields you should expect to find in the map after making this call are:
- "product": the product id.
- "order": an order id, where available.
- "token": an order token, where available.
- "payload": the payload returned by the store, where available.
- "receipt": receipt data, where available.
- "status": (IMPORTANT) whether or not the purchase completed successfully. This can take one of the following GML constant values:
o iap_available (when awaiting a result)
o iap_failed (the purchase attempt failed)
o iap_purchased (the product was successfully purchased)
o iap_canceled (the user cancelled the purchase)
o iap_refunded (the purchase has been rescinded)
- "response" (a response, or error code relating to the result of the purchase attempt. This will be specific to individual stores, though in the event of purchase errors where no store response code is provided you may find we've aped the Google Play result of BILLING_RESPONSE_RESULT_ERROR (6))
Store Activation
The platform store is still loaded through the iap_activate(list) call and we still expect to receive the id of a ds_list of ds_maps indicating the set of products the user expects will be available for purchase. Within each ds_map you should at least provide one entry denoting the "id" for the product.
On Windows 8 targets, whereby a license proxy file has to be generated to test IAP in a Sandbox environment, you should also at least provide an entry for the "type" of the product, with valid values for "type" being "Consumable" and "Durable".
We have removed the proprietary server URL functionality since GameMaker: Studio provides http_get/set() functionality that allows for server communications at the GML level, thus the services provided by that server can be implemented by users in GML.
This also means that we no longer attempt to perform receipt verification via this server and will now provide receipt data (where available) for a purchase through the purchase state retrieval as detailed above.
During activation of a platform store we perform three separate operations:
1. Start communication with the store.
2. Enumerate the set of products associated with your application as known by that store. On some platforms that set of products is drive by the set of product ids you supply, but on some platforms additional products may be found. You can find out the set of product ids GameMaker: Studio has encountered via calling iap_enumerate_products(list) with a pre-generated ds_list.
3. Enumerate the set of active, unconsumed purchases.
(NB: When enumerating the set of known products on Google Play we may encounter time-outs and thus a product you supplied to iap_activate() will not have its "validated" flag set to true. When you call iap_product_details() for a product that does not have its "validated" flag set we will implicitly attempt to ask the store to verify it).
iap_is_purchased(id)
In concert with dropping the proprietary server URL feature we no longer make any attempts to verify purchases since it was simply not a secure strategy and it is up to users to verify that a purchase is legitimate, usually by ensuring receipt data is valid. As such, we no longer explicitly store whether or not a product has been successfully purchased across multiple runs of an application and therefore cannot guarantee that iap_is_purchased(id) will provide the expected result if store loading has failed and we have not been able to enumerate the set of active, unconsumed purchases.
Be aware that certain stores will cache local state on a user's device and therefore we may still be able to effectively load a store even without a valid data connection to the remote store.
Where we have been able enumerate the set of active, unconsumed purchases, iap_is_purchased(id) will simply look for a purchase relating to that product id whose state is set to iap_purchased.
Because we no longer explicitly store this data, we have provided a simple, secure way to persist purchase state across subsequent runs of your application by allowing you to save and load a ds_map structure to a file on a user's device through the following calls:
- ds_map_secure_save(map, filename)
- ds_map_secure_load(filename)
The file stored is encoded and tied to the user's device in a (minimally) secure way and should be non-transferable between devices.
However, it is recommended that users provide their own secure mechanism for persisting purchase state.
Events
The first implementation of IAPs within GameMaker: Studio had no provision for handling IAP events - the user was required to monitor the overall IAP status each frame through calls to iap_status(). A later effort was made to provide a limited event driven approach.
We have now moved to an (almost) entirely event driven approach and whilst iap_status() remains, it now simply returns a value that takes one of the following GML constant values:
-iap_status_uninitialised
-iap_status_unavailable
-iap_status_loading
-iap_status_restoring
-iap_status_available
-iap_status_processing
Multiple events may occur during one game frame as opposed to there only ever being one event per frame and a queue of events that must be interrogated (iap_event_queue() has been deprecated). Please note, it should now be safe to call iap_consume() during an IAP Event.
During an IAP event you should interrogate the built-in iap_data ds_map to find out the details of the event that has occurred. Within the iap_data ds_map there will always be a "type" entry which will take one of the following values:
- iap_ev_storeload (the store has finished its activation sequence)
- iap_ev_restore (a call to restore all purchases has completed)
- iap_ev_product (a product has been verified with the store and its details retrieved)
- iap_ev_purchase (a purchase operation has completed)
- iap_ev_consume (a consume request has completed)
iap_data will also contain the following values dependent on the type of event:
- Store load: "status", which will take a value of
o iap_storeload_ok
o iap_storeload_failed
- Restore completed: "result", which will either be 0 or 1.
- Product: "product”, which is the product id for the product for which details have been retrieved and "index", which if >= 0 can also be used with iap_product_details() to retrieve the details of the product.
- Purchase: "index", which should match up with the result of a prior call to iap_acquire() and can be used with iap_purchase_details() to find out more information about the purchase. "response", which is a store specific response code for the purchase (refer to store specific documentation for the target platform).
- Consume: "product", which confirms the id of the product in question and "consumed", which takes a value of 0 or 1 depending on whether or not consuming was successful.
Queueing up requests
If a call to iap_acquire(), iap_consume(), iap_restore() or a call to iap_product_details() is made for a non-verified product and the store is currently not in the iap_status_available state we will place the request on a queue and process it as soon as the store transitions out of the blocking state. Most times it is recommended that the user checks that the store is available before attempting a request but it is no longer necessary.
Available routines
The complete set of valid routines available for use are as follows:
- iap_activate(DS list/JSON string)
- iap_status()
- iap_enumerate_products(DS list)
- iap_restore_all()
- iap_acquire(product id string, otional per-user payload)
- iap_consume(product id string)
- iap_product_details(product id string, empty map to be used)
- iap_purchase_details(purchase id string, empty map to be used)
Deprecated routines
The following routines are no longer valid and will have no effect:
- iap_is_purchased()
- iap_store_status()
- iap_event_queue()
- iap_product_status()
- iap_is_downloaded()
- iap_product_files()
- iap_files_purchased()
Additional Notes
As part of this process we have upgraded to Google Play API v3.
Only the Mac and iOS stores currently provide restore functionality.
Windows / Windows Phone 8.0 targets do not support consumables and so you must block repeat purchase attempts in your game code. Windows / Windows Phone 8.1 does.
During testing it was found that Google Play could be quite fragile in terms of network time-outs and unsuccessful responses to acquire and consume requests frequently occurred, even though the store itself had actually processed the request. Under such circumstances the BILLING_RESPONSE_RESULT_ERROR error code is returned.
The Amazon Store (Android) only supports 100 products per title.
The Amazon Store (Android) theoretically supports multiple user accounts per device. We are currently ignoring this feature and are assuming that IAPs will be handled per device as with all other stores (we will implement multiple user support in the future on request).
Google Play (Android) can only request details for products 20 at a time which can lead to quite long load times for applications with a significant number of products. On request we will provide an option to prevent product details retrieval during store load.
Stores that provide details of whether or not a product is Consumable or Durable are:
- Amazon
- Windows Phone 8
On all other platforms the “type” field retrieved through iap_product_details() will be blank (NB: When support for consumables is implemented for Windows 8 targets this field will no longer be blank on those targets).
We now cache the Sandbox store setup on Windows 8(N), Windows 8 (JS) and Windows Phone 8 targets and therefore persist purchase state across runs of a title, assuming you do not uninstall the title at any time.
Receipt data/purchase tokens:
For the Amazon Store the purchase “token” and “receipt” entries are treated as one and the same.
For Google Play there is no “receipt” data. You are expected to verify purchases via the returned payload (should you be verifying via a payload it is recommended that you do not use hard coded payloads). We also still verify the data signature returned with a purchase by Google Play against the public key provided in the IAP tab. The token provided with a purchase is intended for use with consuming purchases but may also be used for purchase verification.
For iOS, Mac, Windows 8 (N/JS) and Windows Phone 8 targets there is receipt data available for purchases and the associated token is hand generated for internal purposes.