Manage third-party subscriptions
Overview
AccelByte Gaming Services (AGS) can be integrated with Apple and Google Play to manage player subscriptions. This integration enables you to synchronize subscription data for players who log into your game using their Apple or Google Play accounts.
This article walks you through how to:
- Integrate Apple and Google Play to manage player subscriptions
- Manage player subscriptions in the AGS Admin Portal
- Retrieve player subscription data via API
This integration is different from the Subscription item type that is supported in the AGS Store. For more information, see the create store items page.
Prerequisites
- In-App Purchase (IAP) Configuration: Ensure that Google Play and Apple IAP have been configured in your namespace. For more information, see Third-party store integrations.
- Apple or Google Play login methods: Ensure that the Apple or Google Play login method is enabled for your game. For more information, refer to the integration guide for Apple or Google.
- Admin Permissions: Ensure you have the required admin permissions to access the Lookup Subscribers feature in the Admin Portal.
- Unreal Engine SDK Implementation:
- Unreal Engine: Players must be logged in to the AGS OSS and the Apple or GooglePlay OSS.
- Unity SDK Implementation:
- Apple: Import the UnityPurchasing package into the project. Note that this plugin is tested using UnityPurchasing v4.8.0. For more information, refer to Unity's installation guide.
- Google Play: Import the UnityPurchasing package into the project. Ensure that you use the package with version 4.12.2 at least, in order to use Google Billing Library version 6 (deprecated). For more information, refer to Unity's installation guide.
Integrate Apple and Google Play
Unreal Engine
const IOnlineSubsystem* OnlineSubsystemABPtr = IOnlineSubsystem::Get("ACCELBYTE");
if (OnlineSubsystemABPtr == nullptr)
{
Result.ExecuteIfBound("AB subsystem nullptr");
return;
}
check(OnlineSubsystemABPtr->GetIdentityInterface())
auto UniqueNetId = (OnlineSubsystemABPtr->GetIdentityInterface()->GetUniquePlayerId(LocalUserNum).Get());
auto ABIdentityInterface = StaticCastSharedPtr<FOnlineIdentityAccelByte>(OnlineSubsystemABPtr->GetIdentityInterface());
// OPTIONAL
// For Apple: Find the configuration on your developer Apple dashboard, the URL shows subscription group ID
// For Google: Find the configuration on your developer GooglePlay dashboard, use the ProductId from your Products>Subscriptions
FString SubscriptionGroupId = "21544099";
// For Apple: Find the configuration on your developer Apple dashboard
// For Google: Find the configuration on your developer GooglePlay dashboard, use the base plan name from your Products>Subscriptions
FString SubscriptionProductId = ProductID;
// If true, it excludes the expired subscription
bool bIsActiveOnly = true;
FOnlineQuerySubscriptionRequestAccelByte QueryRequest;
QueryRequest.ActiveOnly = bIsActiveOnly;
QueryRequest.ProductId = SubscriptionProductId;
QueryRequest.GroupId = SubscriptionGroupId;
QueryRequest.QueryPaging = FPagedQuery(0, 20); // The default paging start from index 0 and the amount is 20
// OnQueryEachEntitlement is an example of delegate
auto ResponseOnSuccess = FOnQueryPlatformSubscriptionCompleteDelegate::CreateLambda([&, OnQueryEachEntitlement](int32 _LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const TArray<FAccelByteModelsThirdPartySubscriptionTransactionInfo>& QueryResult, const FOnlineError& Error)
{
if (QueryResult.Num() == 0)
{
OnQueryEachEntitlement.ExecuteIfBound(false, "EMPTY ARRAY");
}
else
{
FString SerializedResult = "EMPTY Data";
FJsonObjectConverter::UStructToJsonObjectString(QueryResult[0], SerializedResult);
OnQueryEachEntitlement.ExecuteIfBound(true, SerializedResult);
}
});
ABEntitlementInterfacePtr->AddOnQueryPlatformSubscriptionCompleteDelegate_Handle(LocalUserNum, ResponseOnSuccess);
ABEntitlementInterfacePtr->QueryPlatformSubscription(LocalUserNum, QueryRequest);
Unity
- Apple
- Google Play
Sign in with Google Play Games. For more information about this sign-in method, refer to the our Unity SDK guide for Apple.
Create a
MonoBehavior
class implementingIDetailedStoreListener
. Unity IAP will handle the purchase and trigger callbacks using this interface. Then, prepare the following variables.IStoreController storeController;
public Button BuySeasonPassButton;
private string seasonPassProductId = "item_season_pass"; // Assume that the registered subscription product ID is named item_season_pass
private ProductType seasonPassProductType = ProductType.Subscription;Prepare a script button to login and trigger the purchasing event. Using Unity Editor's inspector, attach those buttons into
public Button BuySeasonPassButton;
.Initialize
Purchasing
.void Start()
{
InitializePurchasing();
}
void InitializePurchasing()
{
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
// Add the purchasable products and indicate their types
builder.AddProduct(seasonPassProductId, seasonPassProductType);
// Assign its ApplicationUsername
if (!string.IsNullOrEmpty(AccelByteSDK.GetClientRegistry().GetApi().GetUser().Session.UserId))
{
var uid = System.Guid.Parse(AccelByteSDK.GetClientRegistry().GetApi().GetUser().Session.UserId);
appleExtensions.SetApplicationUsername(uid.ToString());
}
else
{
Debug.LogError($"Player is not logged in. Several features may not work properly");
}
}
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
Debug.Log("In-App Purchasing successfully initialized");
storeController = controller;
appleExtensions = extensions.GetExtension<IAppleExtensions>();
}Prepare a function that will trigger the purchasing event.
event
private void BuySeasonPass()
{
storeController.InitiatePurchase(seasonPassProductId);
}Assign the button.
void Start()
{
ButtonAssigning();
}
void ButtonAssigning()
{
BuySeasonPassButton.onClick.AddListener(BuySeasonPass);
}Handle
Process Purchase
. Note that it must returnPurchaseProcessingResult.Pending
because the purchased item will be synchronized with the AGS backend. See Unity's documentation on Processing Purchases. If the game client successfully purchases an item from Apple,ProcessPurchase
will be triggered, elseOnPurchaseFailed
will be triggered.
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs purchaseEvent)
{
var product = purchaseEvent.purchasedProduct;
Debug.Log($"Purchase Complete - Product: {product.definition.id}");
AGSSubscriptionEntitlementSync(product);
return PurchaseProcessingResult.Pending;
}
public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
{
Debug.LogError($"Purchase failed - Product: '{product.definition.id}', PurchaseFailureReason: {failureReason}");
}Synchronize
Purchased Product
with AGS.private void AGSSubscriptionEntitlementSync(Product purchasedSubscription)
{
// Note that sync will work after the player is logged in using AGS service
try
{ AccelByteSDK.GetClientRegistry().GetApi().GetEntitlement().SyncMobilePlatformSubscriptionApple(purchasedSubscription.appleOriginalTransactionID
, result =>
{
if (result.IsError)
{
Debug.Log($"{purchasedSubscription.definition.id} failed to sync with AB [{result.Error.Code}]:{result.Error.Message}");
return;
}
Debug.Log($"{purchasedSubscription.definition.id} is synced with AB");
FinalizePurchase(purchasedSubscription);
});
}
catch (Exception e)
{
Debug.LogError($"Failed to sync with AB {e.Message}");
}
}Finalize
Pending Purchase
.private void FinalizePurchase(Product purchasedProduct)
{
Debug.Log($"Confirm Pending Purchase for: {purchasedProduct.definition.id}");
storeController.ConfirmPendingPurchase(purchasedProduct);
}Query the subscription. The full script on the package samples is named "In App Purchase."
public void QuerySubscription()
{
PlatformStoreId storeId = new PlatformStoreId(PlatformType.Apple);
var userId = AccelByteSDK.GetClientRegistry().GetApi().GetUser().Session.UserId;
AccelByteSDK.GetClientRegistry().GetApi().GetEntitlement().QueryUserSubscription(storeId, userId, result =>
{
if (result.IsError)
{
Debug.LogWarning($"Failed to Query Subscription [{result.Error.Code}]:{result.Error.Message}");
return;
}
bool found = false;
foreach (var eInfo in result.Value.Data)
{
if (eInfo.SubscriptionGroupId == seasonPassProductId)
{
found = true;
break;
}
}
Debug.Log($"Subscribe Season Pass: {found.ToString().ToUpper()}");
});
}
Sign in with Google Play Games. For more information about this sign-in method, refer to the our Unity SDK guide for Google Play Games.
Create a
MonoBehavior
class implementingIDetailedStoreListener
. Unity IAP will handle the purchase and trigger callbacks using this interface. Then, prepare the following variables:IStoreController storeController;
public Button BuySeasonPassButton;
private string seasonPassProductId = "item_season_pass"; // Assume that the registered subscription product ID is named item_season_pass
private ProductType seasonPassProductType = ProductType.Subscription;Prepare a script button to login and trigger the purchasing event. Using Unity Editor's inspector, attach those buttons into
public Button BuySeasonPassButton;
.Initialize
Purchasing
.void Start()
{
InitializePurchasing();
}
void InitializePurchasing()
{
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
// Add the purchasable products and indicate their types
builder.AddProduct(seasonPassProductId, seasonPassProductType);
var accelByteUserId = AccelByteSDK.GetClientRegistry().GetApi().GetUser().Session.UserId;
if (!string.IsNullOrEmpty(accelByteUserId))
{
var uid = System.Guid.Parse(accelByteUserId);
builder.Configure<IGooglePlayConfiguration>().SetObfuscatedAccountId(uid.ToString());
}
else
{
Debug.LogWarning($"Player is not Logged In. Several features may not work properly");
}
UnityPurchasing.Initialize(this, builder);
}
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
Debug.Log("In-App Purchasing successfully initialized");
storeController = controller;
}
```
Prepare a function that will trigger the purchasing event.
private void BuySeasonPass()
{
storeController.InitiatePurchase(seasonPassProductId);
}Assign the button.
void Start()
{
ButtonAssigning();
}
void ButtonAssigning()
{
BuySeasonPassButton.onClick.AddListener(BuySeasonPass);
}Handle
Process Purchase
. Note that it must returnPurchaseProcessingResult.Pending
because the purchased item will be synchronized with the AGS backend. See Unity's documentation on Processing Purchases. If a client successfully purchases an item from Google Play Store, ProcessPurchase will be triggered, elseOnPurchaseFailed
will be triggered.public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs purchaseEvent)
{
var product = purchaseEvent.purchasedProduct;
Debug.Log($"Purchase Complete - Product: {product.definition.id}");
if (product.definition.type == ProductType.Subscription)
{
AGSSubscriptionEntitlementSync(product);
}
return PurchaseProcessingResult.Pending;
}
public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
{
Debug.LogError($"Purchase failed - Product: '{product.definition.id}', PurchaseFailureReason: {failureReason}");
}Synchronize
Purchased Product
with AGS.private void AGSSubscriptionEntitlementSync(Product purchasedSubscription)
{
// Please note that sync will work after the player is logged in using AGS
try
{
string receiptPayload = JObject.Parse(product.receipt)["Payload"].ToString();
var receiptJson = JObject.Parse(receiptPayload)["json"].ToString();
var receiptObject = JObject.Parse(receiptJson);
var orderId = ((string)receiptObject["orderId"]);
var packageName = ((string)receiptObject["packageName"]);
var productId = ((string)receiptObject["productId"]);
var purchaseTime = ((long)receiptObject["purchaseTime"]);
var purchaseToken = ((string)receiptObject["purchaseToken"]);
var autoAck = true;
AccelByteSDK.GetClientRegistry().GetApi().GetEntitlement().
.SyncMobilePlatformSubscriptionGoogle(
orderId
, packageName
, productId
, purchaseTime
, purchaseToken
, autoAck
, syncResult =>
{
if (syncResult.IsError)
{
UnityEngine.Debug.LogWarning(syncResult.Error.Message);
return;
}
if (syncResult.Value.NeedConsume)
{
FinalizePurchase(product);
}
});
}
}Finalize
Pending Purchase
.private void FinalizePurchase(Product purchasedProduct)
{
Debug.Log($"Confirm Pending Purchase for: {purchasedProduct.definition.id}");
storeController.ConfirmPendingPurchase(purchasedProduct);
}Query the subscription. The full script on the package samples is named "In App Purchase."
public void QuerySubscription()
{
PlatformStoreId storeId = new PlatformStoreId(PlatformType.GooglePlayGames);
var userId = AccelByteSDK.GetClientRegistry().GetApi().GetUser().Session.UserId;
AccelByteSDK.GetClientRegistry().GetApi().GetEntitlement().QueryUserSubscription(storeId, userId, result =>
{
if (result.IsError)
{
Debug.LogWarning($"Failed to Query Subscription [{result.Error.Code}]:{result.Error.Message}");
return;
}
bool found = false;
foreach (var eInfo in result.Value.Data)
{
if (eInfo.SubscriptionGroupId == seasonPassProductId)
{
found = true;
break;
}
}
Debug.Log($"Subscribe Season Pass: {found.ToString().ToUpper()}");
});
}
Manage player subscriptions in the Admin Portal
View player subscription details
On the Admin Portal sidebar, go to Live Service Utilities > Lookup Subscribers.
In the Google or Apple tab, search for a player using their player ID. You can also search for a player using their Google Product ID (for Google) or Subscription Group ID (for Apple).
A list of players with their subscription statuses will be displayed, including:
- User ID: The player's unique identifier.
- Google Product ID (for Google) or Subscription Group ID (for Apple): The identifier for the subscription.
- Status: The current subscription status, i.e., Active or Inactive.
- Subscription End Date: The date when the subscription will expire or renew.
From the results list, click View on the player with the details you want to view. The subscription details page of the player appears.
Synchronize player subscription details
To manually synchronize a player's subscription data, go to the player's subscription details page and click on the Sync button.
Retrieve player subscription data via API
Game clients can use the Public Subscription endpoint to directly interact with AGS to query subscription data. This endpoint allows game clients to query user subscriptions from either the Apple or Google Play platform. It provides real-time access to subscription data, enabling the client to stay up-to-date with the latest changes.
API details
Path | /public/namespaces/{namespace}/users/{userId}/iap/subscriptions/platforms/{platform} |
---|---|
Method | GET |
Description | Query user subscription status, sorted by updatedAt . |
Permission | NAMESPACE:{namespace}:USER:{userId}:IAP [READ] |
Path parameter | GOOGLE or APPLE |
Request parameters
Parameter | Type | Condition | Description |
---|---|---|---|
groupId | string | Optional | The identifier for the subscription group. A subscription group includes related subscription products with different offerings. For example, com.newspaper.subscription could be a group that includes various subscription options for a digital newspaper. |
productId | string | Optional | The unique identifier for a specific subscription product within a group. For instance, within the com.newspaper.subscription group, you might have com.newspaper.weekly, com.newspaper.monthly, and com.newspaper.yearly. |
activeOnly | boolean | Optional | If set to true , only active subscriptions will be returned. |
offset | integer | Optional | The pagination offset. This parameter's default value is 0. |
limit | integer | Optional | The number of results to return. This parameter's default value is 20. |
Sample request
- Google Play
- Apple
GET /public/namespaces/{namespace}/users/{userId}/iap/subscriptions/platforms/google
GET /public/namespaces/{namespace}/users/{userId}/iap/subscriptions/platforms/apple
Response information
The response for Public Subscription endpoint requests contains the following information:
Parameter | Description |
---|---|
platform | Target platform of the request: APPLE or GOOGLE . |
active | Indicates whether the subscription is active |
status | The subscription status, e.g., EXPIRED and ACTIVE . |
subscriptionGroupId | The subscription group identifier. |
subscriptionProductId | The product identifier for the subscription. |
startAt | The start date of the subscription. |
expiredAt | The expiration date of the subscription. |
lastTransactionId | The ID of the last transaction. |
createdAt | The date the subscription was created. |
updatedAt | The date for when the subscription data was last updated. |