Skip to main content

In-App Purchase

Introduction


The In-App Purchase feature allows you to monetize your game by offering digital products and content to players. On Android, this is handled through Google Play's billing system, while on iOS, it uses Apple's In-App Purchase system (Store Kit).

Whether you're offering one-time purchases or consumable items, both platforms provide robust APIs to ensure seamless integration, giving your users a familiar and secure purchasing experience.

With Plankton, you can easily integrate in-app purchases across both Android and iOS, streamlining the process for cross-platform monetization.

Before you begin


Prerequisites

Set up your account

Android

  1. Set up your Google Play developer account
  2. Enable billing-related features in the Google Play Console
  3. Create and configure your products in the Google Play Console
Server-to-Server APIs

To get more info about server-side features of the billing library and details about how to set them up, visit this page.

iOS

  1. In your App Store Connect, accept the Paid Apps Agreement in the Business section.
  2. Create in-app purchases and fill the required fields, such as such as product name, description, price, and availability.
  3. You’ll also need to generate in-app purchase keys and set a tax category, which will allow Apple to calculate the appropriate tax on customer transactions.

Confiure Plankton settings


  1. Open plankton settings by going to Edit > Project Settings > Plankton.
  2. Select the checkbox next to the In App Purchase.

Implementation


Now it's time to write some code to make some money out of your game! Always import the Plankton package in your scripts.

using Plankton;

Initialize

Before using the billing features, you need to initialize the Billing class first. You will be notified through the callback argument when the setup process is completed.

Billing.Initialize(succeed => Debug.Log($"{Initialization result:{succeed}"));

Show available products

Now you are ready to query for your available products and display them to your users. Querying for product details is an important step before displaying your products to your users, as it returns localized product information such as price and description.

To query for in-app products, call Billing.GetSkuDetails. Inside the completion callback, it returns a list of Billing.Detail which includes the details for each product.

Here’s an example of how to get and display product details:

var productIds = new string[] {"product_sku_1", "product_sku_2"};
Billing.GetSkuDetails((succeed, productDetailList) =>
{
if (succeed)
{
Debug.Log($"Query successful. Retrieved {productDetailList.Count} product details.");

// Loop through the product details and display each one
foreach (var product in productDetailList)
{
Debug.Log($"Product SKU: {product.sku}");
Debug.Log($"Product Title: {product.title}");
Debug.Log($"Product Description: {product.description}");
Debug.Log($"Product Textual Representation of Price: {product.priceFormatted}");
Debug.Log($"Product Currency: {product.priceCurrency}");
Debug.Log($"Product Price (float): {product.priceAmount}");
}
}
else
{
Debug.Log("Failed to retrieve product details.");
}
},
productIds);
warning

On Android, if any of the product IDs in your query are incorrect or don't exist, the entire query will fail, and no product details will be returned.

However on iOS, if one of the SKUs in your query doesn't exist, the method will still return the details for the valid SKUs. Only the missing or incorrect SKU will be excluded from the results.

Make a purchase

To start a purchase request for a specific product, call Billing.StartPurchase method. Here is the signature of this method:

Billing.StartPurchase(
string sku,
Action<PurchaseStatus, string> callback,
string androidObfuscatedAccountId = "",
string androidObfuscatedProfiledId = ""
)
  • sku parameter is the product identifier.
  • callback is an Action<PurchaseStatus, string> which is called after the purchase flow has finished. The PurchaseStatus determines the result of the purchase(Purchased, Failed, Pending ...). The string value is the purchase token (in Android) or transaction ID (in iOS) if the purchase was successful, otherwise it's an empty string.
  • androidObfuscatedAccountId and androidObfuscatedProfiledId are two optional parameters for Android.

Here's an example of making a purchase:

Billing.StartPurchase("product_sku", (status, tokenOrTransactionId) =>
{
Debug.Log($"Purchase result:{status}, purchase tokenOrTransactionId:{tokenOrTransactionId}");
if (status == Billing.PurchaseStatus.Purchased)
{
// Give the product to the player and finish the purchase.
Billing.FinishPurchase(tokenOrTransactionId, (succeed, tokenOrTransactionId) => {} );
}
else
{
// Handle other purchase statuses...
}
});

Process and finish the purchase

Once a user completes a purchase, your app needs to process and finish the purchase.

Follow these steps to handle a purchase:

  1. Check the status of purchase to verify it succeeded.
  2. Deliver the purchased product to the user and finish the process.

In iOS, there’s no distinction between consumable and non-consumable products; all purchases are processed the same way. On Android, however, you need to specify whether a product is consumable or not.

Use the following method for both Android and iOS:

Billing.FinishPurchase(token, (succeed, tokenOrTransactionId) => {
Debug.Log($"Finish purchase result: {succeed}, purchaseTokenOrTransactionId: {tokenOrTransactionId}");
}, isAndroidConsumable);
  • isAndroidConsumable: A boolean value to indicate whether the product is consumable (only applicable for Android). On iOS, this parameter is ignored.

Get purchases

To retrieve a list of the user's purchases, use the GetPurchases method. This ensures that your app processes any unhandled purchases. Here's an example usage:

Billing.GetPurchases((succeed, purchaseList) => 
{
Debug.Log($"Is successful: {succeed}, Purchased items count: {purchaseList.Count}");
foreach (var item in purchaseList)
{
Debug.Log($"Purchase item --> productId={item.sku}, tokenOrTransactionId={item.token}, purchaseStatus={item.status}");
}
});
  • The callback is of the Action<Boolean, List<Billing.History>> type.
  • The boolean value indicates whether the operation was successful or not.
  • The History object contains details about each purchase, including the purchase token and its status.

Platform Differences

  • On Android, GetPurchases returns all ongoing purchases that need to be processed, as well as acknowledged purchases. Consumable purchases that have already been consumed will not appear in the response.

  • On iOS, GetPurchases only returns ongoing purchases that still need to be processed. It does not include any finished purchases, so this method cannot be used to restore non-consumable purchases. For restoring purchases on iOS, a different mechanism is required.

Restore purchases

Restoring purchases is essential for users who have switched devices or reinstalled your game. It's also possible that your app might not be aware of all the purchases a user has made. Here are some scenarios where your app could lose track or be unaware of purchases:

  • Network Issues during the purchase: The app may not receive a purchase confirmation due to network problems.

  • Multiple devices: A user buys an item on one device and expects to see the item when they switch to another device.

  • Purchases made outside your app: Some purchases, like promotion redemptions, can occur outside the app.

  • Pending transactions (Android only): Transactions that require additional steps between the moment a user initiates a purchase and when the payment method is processed.

The process for restoring purchases differs between Android and iOS.

For Android

To restore purchases on Android, use the GetPurchases method we discussed earlier. This method returns a list of the user's purchases that need to be processed. You should check the status of each purchase:

  • If the purchase has an "acked" (acknowledged) status, you should grant the product to the user.

  • If the purchase has a "purchased" status, provide the product to the user and then finish the purchase by acknowledging or consuming it.

Here’s an example:

Billing.GetPurchases((succeed, purchaseList) => 
{
if (succeed)
{
foreach (var purchase in purchaseList)
{
if (purchase.status == "acked")
{
// Provide the product to the user
}
else if (purchase.status == "purchased")
{
// Provide the product to the user, then finish the purchase
var isConsumable = true; // or False
Billing.FinishPurchase(purchase.token, (result, token) => {
Debug.Log($"Finished purchase: {result}, Token: {token}");
}, isConsumable);
}
}
}
});

For iOS

On iOS, restoring purchases is handled using the Billing.RestorePurchases method. This method triggers the restoration process, and the result is provided through the onSuccess callback. The callback returns a productId(sku) and a transactionID for each restored purchase.

Keep in mind:

  • The onSuccess callback may be called once, several times, or not at all, depending on the number of purchases that need to be restored.

  • For each restored product, you should provide the product to the user and then finish the purchase to complete the process.

Here’s an example:

Billing.RestorePurchases((productSku, transactionID) => 
{
// Provide the restored product to the user, then finish the purchase
Billing.FinishPurchase(transactionID, (result, transactionID) => {
Debug.Log($"Finished restored purchase: {result}, Transaction ID: {transactionID}");
});
});

Summary

  • Android: Use Billing.GetPurchases, check the status of each purchase, and provide the product to the user. For "purchased" status, make sure to finish the purchase after providing the product.

  • iOS: Use Billing.RestorePurchases and handle each restored purchase in the onSuccess callback. Provide the product to the user and finish the purchase afterward.

Handle pending transactions (for Android)

Google Play supports pending transactions, or transactions that require one or more additional steps between when a user initiates a purchase and when the payment method for the purchase is processed. Your app should not grant entitlement to these types of purchases until you get notified that the user's payment method was successfully charged.

For example, a user can create a Pending purchase of an in-app item by choosing cash as their form of payment. The user can then choose a physical store where they will complete the transaction and receive a code through both notification and email. When the user arrives at the physical store, they can redeem the code with the cashier and pay with cash. Google then notifies both you and the user that cash has been received. Your app can then grant entitlement to the user.

In the callback of Purchase and GetPurchases methods, detect pending purchases if the Billing.Status is equal to Status.Pending. You should design a flow in your game to check if a pending purchase's status has turned into Status.Purchased or Status.Failed. Then process the purchase again and give entitlement to the user if it was successful.

API Refrences


Defined Types

In this section, we will discuss the enumerated types, variables, and classes associated with this feature:

Enums

Billing.Store
ValuesDescriptionUsage
GooglePlayYou have to set GooglePlay as your billing provider.Billing.Store.GooglePlay
Billing.PurchaseStatus
ValuesDescriptionUsage
PurchasedThe item has been purchased. You should give it to the player and call Billing.FinishPurchase method for it.Billing.PurchaseStatus.Purchased
PendingThe item is pending for the payment to be done. Later it will either change to Purchased or Failed based on the result of the payment. (Android only)Billing.PurchaseStatus.Pending
FailedThe purchase was not successful.Billing.PurchaseStatus.Failed
AckedThe item has been purchased and acknowledged. Used for restoration of non-consumable products. (Android only)Billing.PurchaseStatus.Acked

Classes

Billing.Detail
Field NameField TypeDefault ValueField Description
sku stringstring.EmptyThe product ID
price stringstring.EmptyReturns formatted price of the item, including its currency sign.For tax-exclusive countries, the price doesn't include tax.
title stringstring.EmptyReturns the title of the product being sold.The title includes the name of the game which owns the product.Example: 100 Gold Coins (Coin selling app).
description stringstring.EmptyThe description of the product
Billing.History
Field NameField TypeDefault ValueField Description
sku stringstring.EmptyThe product Ids’
token stringstring.EmptyA token that uniquely identifies a purchase for a given item and user pair. Later it will be used to consume or acknowledge purchases
status stringstring.EmptyThe status of the purchase
payload stringstring.EmptyReturns the payload specified when the purchase was acknowledged or consumed.Google has deprecated developer payload, starting with version 2.2 of the Google Play Billing Library. Methods associated with developer payload have been deprecated in version 2.2 and were removed in version 3.0. Note that your game can continue to retrieve developer payload for purchases made using either previous versions of the library or AIDL.
obfuscated_account_id stringstring.EmptySpecifies an optional obfuscated string that is uniquely associated with the user's account in your game.If you pass this value, Google Play can use it to detect irregular activity, such as many devices making purchases on the same account in a short period of time. Do not use this field to store any Personally Identifiable Information (PII) such as emails in cleartext. Attempting to store PII in this field will result in purchases being blocked. Google Play recommends that you use either encryption or a one-way hash to generate an obfuscated identifier to send to Google Play.
obfuscated_profile_id stringstring.EmptySpecifies an optional obfuscated string that is uniquely associated with the user's profile in your game.Some games allow users to have multiple profiles within a single account. Use this method to send the user's profile identifier to Google.

Method Summaries

MethodArgumentsReturn TypeDescription
InitializeSystem.Action<bool> callbackvoidStarts up the billing setup process asynchronously. You will be notified through the callback argument when the setup process is complete.
StartPurchasestring sku,
System.Action<PurchaseStatus, string> callback,
string androidObfuscatedAccountId,
string androidObfuscatedProfiledId,
bool androidAutoAck = false,
bool androidAutoConsume = false
voidUse this method to trigger the purchase flow. androidAutoAck and androidAutoConsume are two optional parameters that you can use during the development phase to automatically finish purchase on Android. Learn more about androidObfuscatedAccountId and androidObfuscatedProfiledId in Android
FinishPurchasestring tokenOrTransactionId,
System.Action<bool, string> callback,
bool isAndroidConsumable = true
voidUse this method to finish the purchase flow. Developers are required to finish the successful purchases. In Android this method will either consume or acknowledge the purchase based on the value of isAndroidConsumable parameter. In iOS this method will finish the transaction. Failure to finish a purchase will result in that purchase being refunded.
GetSkuDetailsSystem.Action<bool, List<Detail>> callback,
params string[] skus
voidReturns a list of Billing.Detail which includes the details for each product you passed in the skus parameter.
GetPurchasesSystem.Action<bool, List<History>> callbackvoidReturns list of the user's unprocessed purchases. In Android it also includes acknowledged purchases.
RestorePurchasesSystem.Action<string, string> onSucceedvoidRestores the purchases on iOS and the onSucceed callback will be called once for each restored purchase.