In-app purchases enable you to offer additional content and features to users. You can use this as a means to monetize a free application or to provide additional paid content for your application. This page provides iOS-specific information, but you must be familiar with the general in-app purchases information described in the Using In-App Purchases documentation.
Google Play Details
The information below is vague in some cases. Please check the Google Play documentation for additional details.
Querying Product Information
In-app purchase products are identified by the string-based product ID you configure for each product on the Google Play console for your application.
Subscription types in Google Play are identified by a product ID, a plan ID, and an optional offer ID. A given subscription product identified by a product ID might be configured with different plans. Different plans can define different subscription periods, auto renewal, and some other details. Each plan might define offers which can modify the renewal price under some conditions. You can find specific details for subscription management in Google Billing library official documentation.
Regarding OnlineSubsystemGooglePlay and Blueprint implementation, the product IDs used for subscriptions in any query should be prefixed with
s-. For example, when querying for product information for a subscription with product ID
test_subscription_1
in the store you should instead use s-test_subscription_1
as the product ID.
// null checks ommited for simplicity
FUniqueNetIdRepl PlayerNetId = ...;
TArray<FString>& ProductIDs = {TEXT("test_product_1"), // Product ID in the store is test_product_1.
// It is not a subscription
TEXT("s-test_subscription_1"}; // Product ID in the store is test_subscription_1
// It is a subscription
IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
IOnlineStoreV2Ptr StoreInterface = OnlineSub->GetStoreV2Interface();
Because there are several methods to purchase a subscription, the query for a product ID regarding a subscription may generate several entries describing all possible ways to purchase the product.
As an example, consider a subscription with a product ID test_subscription_id that has 2 base plans named base-plan-1 and base-plan-2, with base-plan-1 having 2 offers named
offer-1
and
offer-2. offer-1 has an initial Free trial, a discounted period and then auto renewals are done at the base-plan-1 regular price
The following images show how the configuration for that product would look like in the Google Play Console:
This example generates product information entries with the following product IDs in the response:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Entries with Product IDs in the form s-[product ID]:[plan]:[offer]:[number] are meant to contain different price points for a given subscription so they are available to show to the user if needed.
Validation
Google documentation recommends you validate your transactions using server-to-server calls before granting any benefit to the users:
Google Play documentation: Fight fraud and abuse
Google Play documentation: Integrate Google Play with your server backend
They also recommend acknowledging or consuming the transaction from your backend servers using Google Play Developer API calls.
Method: purchases.products.consume
Method: purchases.subscriptions.acknowledge
Method: purchases.products.acknowledge
If you are not using a backend server or you want your implementation to work seamlessly on both iOS and Android, remember to call IOnlinePurchase::FinalizePurchase or use the Finalize In-App Purchase Transaction Blueprint node on the completed receipt. When using backend acknowledge or consumption remember to set bDisableLocalAcknowledgeAndConsume to true.
Remember that any kind of on-device validation is not safe and subscription expiration data is not available on a device. The only way to know a subscription is still active on a device is if a receipt for it exists, and that receipt must be validated to know it is legitimate.
Product Types
The Google Play Billing library differentiates between subscriptions and in-app products. Transactions for those products should be finalized by acknowledging subscriptions and non consumable products and by consuming consumable products. Consuming implies acknowledgement. The store does not have any knowledge about products being consumable or not. Is the way you finalize them that defines what is a consumable. Subscription automatic renewal does not generate any new receipt on device.
In that sense, by default, IOnlinePurchase::FinalizePurchase from C++ and the Finalize In-App Purchase Transaction Blueprint node acknowledge subscriptions and consume in-app products locally from the device.
In case you want to support non-consumable products, you can do the finalization server-side and it is under your server control when to communicate with Google servers to consume or acknowledge an in-app product transaction. To disable local acknowledge and consume you can set the following in [ProjectName]/Config/Android/AndroidEngine.ini:
[OnlineSubsystemGooglePlay.Store]
bDisableLocalAcknowledgeAndConsume=True
Remember that non-acknowledged transactions are refunded after a period of time (some days in production; some minutes in a sandbox environment).
Receipts and Receipt Persistence
Receipts for transactions can be gathered while a product is not consumed and not expired. That means that the receipts gathered when querying for owned products will always contain receipts for active subscriptions, owned non-consumed in-app products, and non-acknowledged transactions at the moment the query was made. Be careful about not granting the same benefit twice in case of subscriptions and non-consumable products. Receipts for consumed in-app products will not appear. A non-consumed in-app product cannot be purchased again until consumed (the billing library will have an “already owned” error).
ValidationInfo contained in the receipt for each line item contains a FString containing a JSON with the Base64 encoded JSON receipt, its base64 signature, and a isSubscription field set to true in case the purchase is for a subscription product
Subscription Expiration Management
Subscription status and expiration times are not available on device. The only information regarding a possible active subscription is the presence of a receipt for its product ID. You can check subscription state and expiration times server-side by using the same Google Play Developer API endpoints used for validation:
Method: purchases.subscriptionsv2.get
Method: purchases.subscriptions.get (deprecated)
The Google billing library does not notify devices in any way for subscription expiration, cancellation, or refund. Google Play Developer API offers real-time server notifications to detect those events server side.
Take into account that enabling those notifications may incur additional charges.
Testing In-app Purchases
Google provides an environment to test in-app purchases on devices without any charge by using a license tester account. Refer to the Google Play official documentation for more information:
There is no need to sign your APK with the release key in order to test purchases if you are using a license tester.
Configuration
Set up your in-app purchase in Google Play:
Google Play requires the ID to be all lowercase, and it's best to have the ID for iOS and Android match as far as possible for ease of Blueprint setup.
Make a note of the ID you use, as well as the product type: in-app product or subscription.
Google Play does not differentiate between consumable and non-consumable products.
A product is consumable if its transaction is consumed.
A product is non-consumable if its transaction is only acknowledged.
If local consume and acknowledge is enabled, all non-subscription products are treated as consumable when invoking
FinalizePurchase.When you use the product ID for a subscription in Unreal Engine you should prefix it with
s-.
If you have not already set up your project to use online subsystems, enable it through the Plugins dialog or add the following block to your project's Build.cs file:
C++if (Target.Platform == UnrealTargetPlatform.Android) { PrivateDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "OnlineSubsystem" }); DynamicallyLoadedModuleNames.Add("OnlineSubsystemGooglePlay"); }In Project Settings > Platforms > Android, find the Advanced APKPackaging section.
Add an element to Extra Permissions called
com.android.vending.BILLING.Blueprint nodes need the
OnlineIdentityto be properly enabled.You may need to also add
android.permission.USE_CREDENTIALSto log into Google Play with existing accounts on the device.
Edit
[ProjectName]/Config/Android/AndroidEngine.ini:Config[OnlineSubsystem] DefaultPlatformService=GooglePlay [OnlineSubsystemGooglePlay.Store] bEnableGooglePlaySupport=True bSupportsInAppPurchasing=TrueIf you will implement your in-app purchases validation using a validation server then edit
[ProjectName]/Config/Android/AndroidEngine.ini. This avoids acknowledge and consume of transactions on the local device. Do those steps on the validation server after granting the purchased product through server to Google Play.Config[OnlineSubsystemGooglePlay.Store] bDisableLocalAcknowledgeAndConsume=True
If you are having difficulty configuring your in-app purchases, refer to the Troubleshoot section of the main in app purchases documentation.