-
Notifications
You must be signed in to change notification settings - Fork 2
general
This is the reference guide to the general functions used by the Apple IAP Extension, along with any constants that they may use or return and examples of code that use them.
Some of the examples are extended examples that also show code from callbacks in the In-App Purchase Async Event.
It is worth noting that in some cases the function description will mention the use of a private server to verify purchases. This is not strictly required, as the extension supplies a verification method that verifies purchases locally with Apple, and purchases can be made and finalised even without server verification. However, YoYo Games and Apple both highly recommend private server verification for all IAPs. Setting up the server to deal with purchase verification is outside of the scope of this documentation and, instead, we refer you to the Apple docs here:
Apple Developer Docs: Validating receipts on the device
Apple Developer Docs: Validating receipts with the App Store
IMPORTANT
In order for the function
iap_ValidateReceipt⛔ to return atrue
response, users must download the Apple Inc. Root Certificate and include it with their project in the Included Files (using included files covers iOS/tvOS and macOS). See:
- https://www.apple.com/certificateauthority/
- https://www.apple.com/appleca/AppleIncRootCertificate.cer
Please see Validating for more information.
These are the general functions of the extension.
- iap_Init
- iap_QueryProducts
- iap_IsAuthorisedForPayment
- iap_AddProduct
- iap_PurchaseProduct
- iap_RestorePurchases
- iap_FinishTransaction
- iap_QueryPurchases
- iap_GetReceipt
- iap_RefreshReceipt
iap_ValidateReceipt⛔
This section lists the constants of this extension.
This section lists the structs of this extension.
This function initialises the Apple In-App Purchase API.
Syntax:
iap_Init()
Returns:
N/A
This function can be used to query the status of products from the Apple Store. The function will trigger an In-App Purchase Async Event with the data from the products query.
This function operates asynchronously, which means that it does not immediately return the requested result. Instead, upon completion of the task, it will trigger the In-App Purchase Async Event.
Syntax:
iap_QueryProducts()
Returns:
N/A
Triggers:
Key | Type | Description |
---|---|---|
id | iap_event_id | This will be the constant iap_product_update . |
response_json | String | This will be a JSON string object which will contain the product details. The "response_json" string can be converted into a Struct using the json_parse function. The struct will contain two variables: valid and invalid . These keys will in turn contain a Array which can then be parsed to get information about each of the individual products.The invalid array will simply be a list of strings, where each string relates to an invalid product ID (note that a product can be invalid if it is not configured or configured incorrectly on the App Store Connect console, or even if there is a connection issue between the device and App Store Connect).The valid array will contain a ProductInfo struct for each list entry, where each item corresponds to a single valid product. |
Example:
This function would normally be called straight after adding the desired products to the internal product list, as shown in the example for the function iap_AddProduct. Once called it will trigger an In-App Purchase Async Event which would be parsed with something like the following code:
/// Async IAP Event
var _event_id = async_load[? "id"];
switch(_event_id)
{
case iap_product_update:
show_debug_message("[INFO] Query Products Callback");
var _response_json = async_load[?"response_json"];
if (_response_json == "") { exit; }
var _response_data = json_parse(_response_json);
show_debug_message("Invalid SKUs");
show_debug_message("------------");
array_foreach(_response_data.invalid, show_debug_message);
var _valid = _response_data.valid;
for(var i = 0; i < array_length(_valid);i++)
{
var _product = _valid[i];
var _product_id = _product.productId;
show_debug_message($"Valid Product: {_product_id}");
}
break;
// Handle other cases here...
}
The code example above checks the event type and executes a bit of code when it's of type iap_product_update
.
It then gets the response JSON from the "response_json"
member and parses it using json_parse.
The invalid SKUs are listed in the output window. The valid SKUs' properties are looked up and output as well.
This function checks whether the user currently signed in on the device has authorised the payment process or not. The function will return true
if payment can be completed, or false
otherwise, in which case you should disable all purchase options in the game. Normally, you'd want to check this return value at Game Start.
Syntax:
iap_IsAuthorisedForPayment()
Returns:
Example:
/// Create Event - Controller object
#macro iap_consumable "yyg_iap_100gems"
#macro iap_nonconsumable "yyg_iap_noads"
#macro iap_renewablesub "yyg_iap_monthlysub"
#macro iap_nonrenewablesub "yyg_iap_yearpromosub"
iap_Init();
iap_enabled = false;
if (!iap_IsAuthorisedForPayment()) { exit; }
iap_enabled = true;
iap_AddProduct(iap_consumable);
iap_AddProduct(iap_nonconsumable);
iap_AddProduct(iap_renewablesub);
iap_AddProduct(iap_nonrenewablesub);
iap_QueryProducts();
The example code above first defines a few macros that store the product IDs. It then initialises the extension with a call to iap_Init and sets a variable iap_enabled
to false
. After that, it checks if the user is authorised for payments using iap_IsAuthorisedForPayment.
If not, the event is exited. If the user is authorised to make payments, the variable iap_enabled
is set to true
, the products are added and queried.
This function adds a product to the internal IAP product list, preparing it for purchase. The function takes a string, which is the product ID as defined in the App Store Connect console for your game. The function returns an iap_error constant to inform you of the success or failure of the addition, and you should call this at the start of your game before querying or permitting purchases.
Syntax:
iap_AddProduct(product_id)
Argument | Type | Description |
---|---|---|
product_id | String | The product ID string of the product being added |
Returns:
Example:
/// Create Event - Controller object
#macro iap_consumable "yyg_iap_100gems"
#macro iap_nonconsumable "yyg_iap_noads"
#macro iap_renewablesub "yyg_iap_monthlysub"
#macro iap_nonrenewablesub "yyg_iap_yearpromosub"
iap_Init();
iap_enabled = false;
if (!iap_IsAuthorisedForPayment()) { exit; }
iap_enabled = true;
iap_AddProduct(iap_consumable);
iap_AddProduct(iap_nonconsumable);
iap_AddProduct(iap_renewablesub);
iap_AddProduct(iap_nonrenewablesub);
iap_QueryProducts();
The example code above first defines a few macros that store the product IDs. It then initialises the extension with a call to iap_Init and sets a variable iap_enabled
to false
. After that, it checks if the user is authorised for payments using iap_IsAuthorisedForPayment.
If not, the event is exited. If the user is authorised to make payments, the variable iap_enabled
is set to true
, the products are added and queried.
This function is used to purchase a product within your game. You supply the product ID as a string (which should match the ID of the product on the App Store Connect console), and the function will immediately return one of the iap_error constants to inform you of the initial status of the purchase request, and if that is iap_no_error
then it will also trigger an In-App Purchase Async Event. In this event the async_load DS map will have an "id"
key which will be iap_payment_queue_update
.
The async_load map will also have another key "json_response"
which will contain a JSON object string with the details of the purchase. This string can be decoded into a Struct using the json_parse function, and the resulting struct will have a single variable purchases
. This in turn will be an Array in which each entry is a PurchaseInfo corresponding to a single purchase.
If the purchase state comes back as a success or a restored purchase, then you should go ahead and validate the purchase with either your own server (recommended) or with Apple, and then finalise the purchase. If the purchase has failed, then you should still finalise the purchase, but no other action needs to be taken. For more information on finalising purchases please see the function iap_FinishTransaction.
This function operates asynchronously, which means that it does not immediately return the requested result. Instead, upon completion of the task, it will trigger the In-App Purchase Async Event.
Syntax:
iap_PurchaseProduct()
Returns:
Triggers:
Key | Type | Description |
---|---|---|
id | iap_event_id | The value iap_payment_queue_update . |
response_json | String | This will be a JSON string object which will contain the details of the purchase. This string can be parsed into a Struct using the json_parse function, and the resulting struct will have a single variable purchases . This in turn will be an array in which each entry is a PurchaseInfo. |
Example:
The following code would be used to create a purchase request for the given product, and would be placed anywhere in the game (like a button object):
/// Mouse Left Released Event
iap_PurchaseProduct(iap_consumable);
Note
You should have some code in place here that checks if IAPs are enabled.
The request above will trigger an In-App Purchase Async Event which can be dealt with something like this:
/// Async IAP Event
var _event_id = async_load[? "id"];
switch (_event_id)
{
case iap_payment_queue_update:
show_debug_message("[INFO] Purchase Product Callback");
var _response_json = async_load[?"response_json"];
if (_response_json == "") { exit; }
var _response_data = json_parse(_response_json);
if(struct_exists(_response_data, "cancelled") && _response_data.cancelled)
{
// User closed the popup
show_debug_message($"Purchase Cancelled: {_response_data.product}");
return;
}
// This will only validate the receipt signature
// If it is not valid we shouldn't event bother doing the server-side check.
if (iap_ValidateReceipt()) {
var _receipt = iap_GetReceipt();
// Proceed to check validation with the server
// This is not required but is recommended
// If you don't want to validate with a server you can set flag to 'false' instead
if (true) {
var _http_request = RequestServerValidation(_receipt);
validationRequests[$ _http_request] = _response_data.purchases;
}
else
{
// Handle the purchases using your own code
HandlePurchases(_response_data.purchases);
}
}
// Refresh the receipt for a later check
else {
waiting_refresh = true;
iap_RefreshReceipt();
}
break;
// Handle other cases here
}
The code above shows the code in the In-App Purchase Async Event in the specific case of an iap_payment_queue_update
event.
The HandlePurchases
function is a custom function that handles the purchases once it's clear that they're valid. See the demo project for a example implementation.
This function can be used to restore any previous purchases.
Apple requires you to have a button in your game that calls this function so that users that have changed or refreshed their device can still access previously made purchases.
Calling this function will immediately return a iap_purchase_state constant to inform you whether the restore request has been made or not, and then a successful request may trigger an In-App Purchase Async Event with the restored purchase details. We say "may", as under the following circumstances no Async Event will be triggered:
- All transactions are unfinished.
- The user did not purchase anything that is restorable.
- You tried to restore items that are not restorable, such as a non-renewing subscription or a consumable product.
- Your app's build version does not meet the guidelines for the CFBundleVersion key.
This function operates asynchronously, which means that it does not immediately return the requested result. Instead, upon completion of the task, it will trigger the In-App Purchase Async Event.
Syntax:
iap_RestorePurchases()
Returns:
Triggers:
Key | Type | Description |
---|---|---|
id | iap_event_id | The constant iap_payment_queue_update . |
json_response | String | This is a JSON object string with the details of the purchase. It can be parsed into a struct using the json_parse function, and the resulting struct will have a single key "purchases" . This key contains an Array of PurchaseInfo structs. |
Example:
/// Async IAP Event
var _event_id = async_load[? "id"];
switch(_event_id)
{
case ios_payment_queue_update:
// Decode the returned JSON
var _json = async_load[? "response_json"];
if (_json == "") { exit; }
var _purchase_data = json_parse(_json);
var _purchases = _purchase_data.purchases;
// Loop through purchases
for(var i = 0;i < array_length(_purchases);i++)
{
var _purchase = _purchases[i];
if (_purchase.responseCode == 3)
{
// This is a restored purchased, handle it here
show_debug_message($"Purchase Restored for Product: {_purchase.productId}");
}
}
break;
}
Once a purchase request, restore request or purchase query has been sent, any products returned in the In-App Purchase Async Event for these calls should be validated and then finalised before awarding any products to the player. Finalising a product means that you are telling Apple that the transaction has been completed and the product awarded, and this function should be called on all transactions, even those that have failed (for example, cancelled by the user). Any transaction that has not been finalised will appear in the above-mentioned purchase/restore/query data and should be finalised before any further purchases of the same product are processed.
When calling this function, you need to supply the product token string (as returned in the In-App Purchase Async Event for the associated function call), and the function will return an iap_error constant that can be iap_no_error
or iap_error_unknown
.
Syntax:
iap_FinishTransaction(purchase_token)
Argument | Type | Description |
---|---|---|
purchase_token | String | The purchase token returned by the purchase request. |
Returns:
N/A
Example:
For examples of using this function, please see the HandlePurchases
function in the demo project.
This function queries the status of all unfinalised purchases.
It can be called anytime and in any place in your game code, as the purchase status details are retrieved when the API is initialised and after any change has been made (i.e., something has been purchased). However, we recommend that you initially call it after adding product IDs to the internal product list and generally it’s better to call it after having queried product details too.
Generally, you would want to call this function once at the start of the game, and then again after any purchase receipt validation so that you know which items have been purchased and need to be finalised and awarded to the user.
Important
This function will only return items that have not been finalised. So, any products that are returned by this function will need to be finalised using the function iap_FinishTransaction.
The function returns the purchases as a JSON-encoded string that you can parse using json_parse. The struct can be accessed as a PurchaseInfo struct.
Syntax:
iap_QueryPurchases()
Returns:
Example:
/// Create Event
var _purchases_json = iap_QueryPurchases();
if (_purchases_json != "") {
var _purchases_data = json_parse(_purchases_json);
var _purchases = _purchases_data.purchases;
HandlePurchases(_purchases, true);
}
The code above queries the purchases with a call to iap_QueryPurchases. The result is checked immediately on the next line of code: the JSON string is parsed into a struct using json_parse, its purchases
variable looked up and the purchases handled using a custom HandlePurchases
function.
This function can be used to retrieve the receipt string for all purchases currently in progress. This string can then be sent as part of the payload to your server (or to Apple) to verify the purchases in the receipt.
Important
The receipt string can contain multiple transaction receipts at once as Apple sends back all pending receipts in one string. For more information, including how to check the information provided in the receipt, please see the Apple Developer Documentation.
Syntax:
iap_GetReceipt()
Returns:
Example:
The following code is a very simple example of how to use the function and send a verification request off to a server you have set up. Note, however, that the actual usage will very much depend on how you've set up the server and this is not a one-size-fits-all example to be copied and used directly:
var _receipt = iap_GetReceipt();
if (_receipt != "")
{
var _map = ds_map_create();
_map[? "apple_receipt"] = _receipt;
var _body = json_encode(_map);
ds_map_clear(_map);
_map[? "Host"] = "10.36.11.105:9999";
_map[? "Content-Type"] = "application/json";
_map[? "Content-Length"] = string_length(_body);
var _url = "http://" + _map[? "Host"] + "/apple-receipt-verify";
http_request(_url, "POST", _map, _body);
ds_map_destroy(_map);
}
With this function you can request a new receipt for all purchases pertaining to a user and app. This function should only be called if a previous receipt could not be validated correctly. The function will return one of the iap_receipt_refresh_result constants immediately to inform you whether the refresh request has been successful, and if it is successful then an In-App Purchase Async Event will be triggered. In this event the async_load DS map will have an "id"
key, which will be the constant iap_receipt_refresh
, and an additional key "status"
. The status will be a member of the iap_receipt_refresh_result constants. If the refresh is successful, you can then retrieve the new receipt using the function iap_GetReceipt, but if it fails then you may want to try again at least once before deciding that something is wrong.
Note
Failing validation is a rare occurrence and is very indicative that there is something funny going on with the request. As such, you may want to consider locking down and preventing any further purchases – or at least not granting the products that were being validated – should validation fail 2 or more times. Any outstanding purchases should still be finalised at this time.
Syntax:
iap_RefreshReceipt()
Returns:
Example:
The following example assumes you have received a failed validation attempt from your server or from local validation and have called this function to request a refresh of the IAP receipt. This would then be dealt with in the In-App Purchase Async Event in the following way:
/// Async IAP Event
var _event_id = async_load[? "id"];
var _status = async_load[? "status"];
switch(_event_id)
{
case iap_receipt_refresh:
if (_status == iap_receipt_refresh_success)
{
var _receipt = iap_GetReceipt();
if (_receipt != "")
{
// Send off another validation request to your server (or locally) and try again
_receipt = iap_GetReceipt();
if (iap_ValidateReceipt())
{
switch(_product_id)
{
case iap_consumable:
global.gold += 100;
break;
case iap_nonconsumable:
global.no_ads = true;
break;
case iap_renewablesub:
global.subs = true;
break;
}
iap_FinishTransaction();
}
else
{
// Validation failed, so deal with it here
}
}
}
else if (_status == iap_receipt_refresh_failure)
{
iap_enabled = false;
// Finalise the purchase here
}
break;
// Handle other cases here...
}
⛔ This function was deprecated with version 2.0.3.
This function can be used for local receipt validation with Apple. In general, you'd want to use a private server for validation of all purchases (especially subscriptions), but if that is not possible then you can use this function, after calling the iap_GetReceipt function to validate purchases. The function will return true
if validation has been successful, or false
otherwise, in which case you should attempt to refresh and revalidate the receipt using iap_RefreshReceipt.
Note
This function was deprecated and now always returns true
.
Syntax:
iap_ValidateReceipt()
Returns:
Example:
See iap_PurchaseProduct for an example on how to use this function.
Versioning:
-
2.0.3
- This function was deprecated
This group of constants represent the possible errors.
These constants are referenced by the following functions:
Member | Description |
---|---|
iap_error_unknown |
An unknown error occurred. |
iap_no_error |
No error occurred. |
iap_error_extension_not_initialised |
The extension has not been initialised. |
iap_error_no_skus |
No SKUs were found. |
iap_error_duplicate_product |
A duplicate product was encountered. |
This group of constants represents the possible event IDs (or types) in the In-App Purchase Async Event.
These constants are referenced by the following functions:
Member | Description |
---|---|
iap_payment_queue_update |
A response to a product purchase. |
iap_product_update |
A response to a product query. |
iap_receipt_refresh |
A response to a refresh of a receipt. |
These constants are referenced by the following structs:
Member | Description |
---|---|
iap_product_period_unit_day |
Each unit represents a day. |
iap_product_period_unit_week |
Each unit represents a week. |
iap_product_period_unit_month |
Each unit represents a month. |
iap_product_period_unit_year |
Each unit represents a year. |
This constant holds the possible outcomes of the refresh of a receipt.
Member | Description |
---|---|
iap_receipt_refresh_success |
The refresh of the receipt was successful. |
iap_receipt_refresh_failure |
The refresh of the receipt wasn't successful. |
This group of constants represent the state of a purchase.
These constants are referenced by the following functions:
These constants are referenced by the following structs:
Member | Description |
---|---|
iap_purchase_success |
This value indicates that the product has been successfully purchased. |
iap_purchase_failed |
This value indicates that the product purchase has failed in some way, for example, it was cancelled by the user. |
iap_purchase_restored |
This indicates that the purchase has been restored. |
This struct stores a subscription period and the unit in which it is expressed.
This struct is referenced by the following structs:
Member | Type | Description |
---|---|---|
numberOfUnits | Real | The number of "units" that the subscription is for. |
unit | iap_product_period_unit | The unit used to calculate the duration of the subscription. |
This struct stores the details of an introductory offer or a promotional offer for an auto-renewable subscription.
This struct is referenced by the following structs:
Member | Type | Description |
---|---|---|
price | String | The discount price of the product in the local currency, represented as a string. |
priceLocale | String | The language code for the locale. Examples of language codes include "en", "es", and "zh". |
This struct stores info about a product.
Member | Type | Description |
---|---|---|
price | String | The localised price of the product as a string, for example "0.99" . |
locale_localeIdentifier | String | Examples of locale identifiers include "en_GB", "es_ES_PREEURO". |
localizedTitle | String | This holds the title of the product as a string, and localised. |
localizedDescription | String | This holds the description of the product as a string, and localised. |
productId | String | The unique ID for the product as a string, for example "mac_consumable" . |
currencyCode | String | The currency code for the locale. Example currency codes include "USD", "EUR", and "JPY". |
currencySymbol | String | The currency symbol for the locale. Example currency symbols include "$", "€", and "¥". |
locale_languageCode | String | The language code for the locale. Examples of language codes include "en", "es", and "zh". (macOS 10.12+) |
introductoryPrice | ProductDiscount | The object containing introductory price information for the product. (macOS 10.13+, iOS 11.2+, tvOS 11.2+) |
discounts | Array of ProductDiscount | This will hold an array where each list entry corresponds to a discount value. (macOS 10.14+, iOS 12.2+, tvOS 12.2+) |
subscriptionPeriod | ProductSubscriptionPeriod | The period details for products that are subscriptions. (macOS 10.13+, iOS 11.2+, tvOS 11.2+) |
subscriptionGroupIdentifier | String | The identifier of the subscription group to which the subscription belongs. (macOS 10.14+, iOS 12.0+, tvOS 12.0+) |
downloadContentVersion | String | A string that identifies which version of the content is available for download. (macOS 10.15+, iOS 6.0+, tvOS 9.0+) |
downloadContentLengths | Array of Real | The lengths of the downloadable files available for this product. (macOS 10.15+, iOS 6.0+, tvOS 9.0+) |
isDownloadable | Boolean | This will be a boolean true or false , depending on whether the App Store has downloadable content for this product. (macOS 10.15+, iOS 6.0+, tvOS 9.0+) |
This struct stores info about a purchase.
Member | Type | Description |
---|---|---|
productId | String | The ID string of the purchased product. |
responseCode | Real | This is the Apple response code, an integer value, where: * purchasing = 0 * purchased = 1 * failed = 2 * restored = 3 * deferred = 4 |
purchaseToken | String | The purchase token string. |
purchaseState | iap_purchase_state | The state of the purchase. |
receipt | String | The receipt string. This is deprecated and should not be used for anything. It is only included in this documentation as it is still part of the return payload from Apple. To get the correct receipt string, please use the function iap_GetReceipt. |
YoYoGames 2025