This document describes setting requirements to use Unreal in-app purchase.
The unified purchase API of Gamebase supports for easy integration of in-app purchases of many stores.
Regarding how to set in-app purchases on Android or iOS, read the following documents:
[Caution]
If there is payment-related processing in an external plugin, the Gamebase purchase function may not work properly.
You must disable the Online SubSystem plugin, which is enabled by default in Unreal, or change it to not use the Store function.
If you're using Online SubSystem GooglePlay plugin, edit the /Config/Android/AndroidEngine.ini file.
[OnlineSubsystemGooglePlay.Store]
bSupportsInAppPurchasing=False
If you're using Online SubSystem Online SubSystem iOS plugin, edit the /Config/IOS/IOSEngine.ini file.
[OnlineSubsystemIOS.Store]
bSupportsInAppPurchasing=False
Purchase of an item can be divided into Purchase Flow, Consume Flow, and Reprocess Flow. It is recommended to implement the Purchase Flow in the following order:
If there's a value on the list of unconsumed purchases, proceed with the Consume Flow in the following order:
[Caution]
To prevent duplicate provision of an item, always check whether the item is being provided in duplicate in the game server.
By using itemSeq of an item to purchase, call the following API and request a purchase.
If a game user cancels purchase, the PURCHASE_USER_CANCELED error is returned.
API
Supported Platforms ■ UNREAL_IOS ■ UNREAL_ANDROID
void RequestPurchase(const FString& gamebaseProductId, const FGamebasePurchasableReceiptDelegate& onCallback);
void RequestPurchase(const FString& gamebaseProductId, const FString& payload, const FGamebasePurchasableReceiptDelegate& onCallback);
Example
void Sample::RequestPurchase(const FString& gamebaseProductId)
{
IGamebase::Get().GetPurchase().RequestPurchase(gamebaseProductId, FGamebasePurchasableReceiptDelegate::CreateLambda(
[](const FGamebasePurchasableReceipt* purchasableReceipt, const FGamebaseError* error)
{
if (Gamebase::IsSuccess(error))
{
UE_LOG(GamebaseTestResults, Display, TEXT("RequestPurchase succeeded. (gamebaseProductId= %s, price= %f, currency= %s, paymentSeq= %s, purchaseToken= %s)"),
*purchasableReceipt->gamebaseProductId, purchasableReceipt->price, *purchasableReceipt->currency,
*purchasableReceipt->paymentSeq, *purchasableReceipt->purchaseToken);
}
else
{
if (error->code == GamebaseErrorCode::PURCHASE_USER_CANCELED)
{
UE_LOG(GamebaseTestResults, Display, TEXT("User canceled purchase."));
}
else
{
// Check the error code and handle the error appropriately.
UE_LOG(GamebaseTestResults, Display, TEXT("RequestPurchase failed. (error: %d)"), error->code);
}
}
}));
}
void Sample::RequestPurchaseWithPayload(const FString& gamebaseProductId)
{
FString userPayload = TEXT("{\"description\":\"This is example\",\"channelId\":\"delta\",\"characterId\":\"abc\"}");
IGamebase::Get().GetPurchase().RequestPurchase(gamebaseProductId, userPayload, FGamebasePurchasableReceiptDelegate::CreateLambda(
[](const FGamebasePurchasableReceipt* purchasableReceipt, const FGamebaseError* error)
{
if (Gamebase::IsSuccess(error))
{
UE_LOG(GamebaseTestResults, Display, TEXT("RequestPurchase succeeded. (gamebaseProductId= %s, price= %f, currency= %s, paymentSeq= %s, purchaseToken= %s)"),
*purchasableReceipt->gamebaseProductId, purchasableReceipt->price, *purchasableReceipt->currency,
*purchasableReceipt->paymentSeq, *purchasableReceipt->purchaseToken);
FString payload = purchasableReceipt->payload;
}
else
{
if (error->code == GamebaseErrorCode::PURCHASE_USER_CANCELED)
{
UE_LOG(GamebaseTestResults, Display, TEXT("User canceled purchase."));
}
else
{
// Check the error code and handle the error appropriately.
UE_LOG(GamebaseTestResults, Display, TEXT("RequestPurchase failed. (error: %d)"), error->code);
}
}
}));
}
VO
USTRUCT()
struct FGamebasePurchasableReceipt
{
GENERATED_USTRUCT_BODY()
// The product ID of a purchased item.
UPROPERTY()
FString gamebaseProductId;
// An identifier for Legacy API that purchases products with itemSeq.
UPROPERTY()
int64 itemSeq;
// The price of purchased product.
UPROPERTY()
float price;
// Currency code.
UPROPERTY()
FString currency;
// Payment identifier.
// This is an important piece of information used to call 'Consume' server API with purchaseToken.
// Consume API : https://docs.toast.com/en/Game/Gamebase/en/api-guide/#purchase-iap
// Caution: Call Consume API from game server!
UPROPERTY()
FString paymentSeq;
// Payment identifier.
// This is an important piece of information used to call 'Consume' server API with paymentSeq.
// In Consume API, the parameter must be named 'accessToken' to be passed.
// Consume API : https://docs.toast.com/en/Game/Gamebase/en/api-guide/#purchase-iap
// Caution: Call Consume API from game server!
UPROPERTY()
FString purchaseToken;
// The product ID that is registered to store console such as Google or Apple.
UPROPERTY()
FString marketItemId;
// The product type which can have the following values:
// * UNKNOWN: An unknown type. Either update Gamebase SDK or contact Gamebase Customer Center.
// * CONSUMABLE: A consumable product.
// * AUTO_RENEWABLE: A subscription product.
// * CONSUMABLE_AUTO_RENEWABLE: This 'consumable subscription product' is used when providing a subscribed user a subscription product that can be consumed periodically.
UPROPERTY()
FString productType;
// This is a user ID with which a product is purchased.
// If a user logs in with a user ID that is not used to purchase a product, the user cannot obtain the product they purchased.
UPROPERTY()
FString userId;
// The payment identifier of a store.
UPROPERTY()
FString paymentId;
// paymentId is changed whenever product subscription is renewed.
// This field shows the paymentId that was used when a subscription product was first purchased.
// This value does not guarantee to be always valid, as it can have no value depending on the store
// the user made a purchase and the status of the payment server.
UPROPERTY()
FString originalPaymentId;
// The time when the product was purchased.(epoch time)
UPROPERTY()
int64 purchaseTime;
// The time when the subscription expires.(epoch time)
UPROPERTY()
int64 expiryTime;
// It is the value passed to payload when calling Gamebase.Purchase.requestPurchase API.
//
// This field can be used to hold a variety of additional information.
// For example, this field can be used to separately handle purchase
// and provision of the products purchased using the same user ID and sort them by game channel or character.
UPROPERTY()
FString payload;
// Whether it is promotion or not
// - (Android) If the validation logic is temporarily turned off in the Gamebase payment server, a valid value is not guaranteed because this value will be output as false only.
UPROPERTY()
bool isPromotion;
// Whether it is test purchase or not
// - (Android) If the validation logic is temporarily turned off in the Gamebase payment server, a valid value is not guaranteed because this value will be output as false only.
UPROPERTY()
bool isTestPurchase;
};
To query the list of items, call the following API: The list of callback returns includes information of each item.
API
Supported Platforms ■ UNREAL_IOS ■ UNREAL_ANDROID
void RequestItemListPurchasable(const FGamebasePurchasableItemListDelegate& onCallback);
Example
void Sample::RequestItemListPurchasable()
{
IGamebase::Get().GetPurchase().RequestItemListPurchasable(FGamebasePurchasableItemListDelegate::CreateLambda(
[](const TArray<FGamebasePurchasableItem>* purchasableItemList, const FGamebaseError* error)
{
if (Gamebase::IsSuccess(error))
{
UE_LOG(GamebaseTestResults, Display, TEXT("RequestItemListPurchasable succeeded."));
for (const FGamebasePurchasableItem& purchasableItem : *purchasableItemList)
{
UE_LOG(GamebaseTestResults, Display, TEXT(" - gamebaseProductId= %s, price= %f, itemName= %s, itemName= %s, marketItemId= %s"),
*purchasableItem.gamebaseProductId, purchasableItem.price, *purchasableItem.currency, *purchasableItem.itemName, *purchasableItem.marketItemId);
}
}
else
{
UE_LOG(GamebaseTestResults, Display, TEXT("RequestItemListPurchasable failed. (error: %d)"), error->code);
}
}));
}
VO
USTRUCT()
struct FGamebasePurchasableItem
{
GENERATED_USTRUCT_BODY()
// The product ID that is registered with the Gamebase console.
// Used when a product is purchased using Gamebase.Purchase.requestPurchase API.
UPROPERTY()
FString gamebaseProductId;
// An identifier for Legacy API that purchases products with itemSeq.
UPROPERTY()
int64 itemSeq;
// The price of a product.
UPROPERTY()
float price;
// Currency code.
UPROPERTY()
FString currency;
// The name of a product registered in the Gamebase console.
UPROPERTY()
FString itemName;
// The product ID that is registered to store console such as Google or Apple.
UPROPERTY()
FString marketItemId;
// The product type which can have the following values:
// * UNKNOWN: An unknown type. Either update Gamebase SDK or contact Gamebase Customer Center.
// * CONSUMABLE: A consumable product.
// * AUTO_RENEWABLE: A subscription product.
// * CONSUMABLE_AUTO_RENEWABLE: This 'consumable subscription product' is used when providing a subscribed user a subscription product that can be consumed periodically.
UPROPERTY()
FString productType;
// Localized price information with currency symbol.
UPROPERTY()
FString localizedPrice;
// The name of a localized product registered with the store console.
UPROPERTY()
FString localizedTitle;
// The description of a localized product registered with the store console.
UPROPERTY()
FString localizedDescription;
// Shows whether the product is 'used or not' in the Gamebase console.
UPROPERTY()
bool isActive;
};
Send a request for unconsumed purchases of which items have not been normally consumed (delivered or provided) even after purchased. When there's an unconsumed purchase, send a request to the game server (item server) so as to deliver (provide) items.
API
Supported Platforms ■ UNREAL_IOS ■ UNREAL_ANDROID
void RequestItemListOfNotConsumed(const FGamebasePurchasableReceiptListDelegate& onCallback);
Example
void Sample::RequestItemListOfNotConsumed()
{
IGamebase::Get().GetPurchase().RequestItemListOfNotConsumed(FGamebasePurchasableItemListDelegate::CreateLambda(
[](const TArray<FGamebasePurchasableItem>* purchasableItemList, const FGamebaseError* error)
{
if (Gamebase::IsSuccess(error))
{
// Should Deal With This non-consumed Items.
// Send this item list to the game(item) server for consuming item.
UE_LOG(GamebaseTestResults, Display, TEXT("RequestItemListOfNotConsumed succeeded."));
for (const FGamebasePurchasableItem& purchasableItem : *purchasableItemList)
{
UE_LOG(GamebaseTestResults, Display, TEXT(" - gamebaseProductId= %s, price= %f, itemName= %s, itemName= %s, marketItemId= %s"),
*purchasableReceipt.gamebaseProductId, purchasableItem.price, *purchasableItem.currency, *purchasableItem.itemName, *purchasableItem.marketItemId);
}
}
else
{
UE_LOG(GamebaseTestResults, Display, TEXT("RequestItemListOfNotConsumed failed. (error: %d)"), error->code);
}
}));
}
Query the list of activated subscriptions of a current user ID. Paid subscriptions (auto-renewal subscription, or auto-renewal consumable subscription products) can be queried until they are expired. Under same user ID, you can query all subscriptions purchased both on Android and iOS.
[Caution]
For Android, subscriptions are currently supported only on the Google Play Store.
API
Supported Platforms ■ UNREAL_IOS ■ UNREAL_ANDROID
void RequestActivatedPurchases(const FGamebasePurchasableReceiptListDelegate& onCallback);
Example
void Sample::RequestActivatedPurchases()
{
IGamebase::Get().GetPurchase().RequestActivatedPurchases(FGamebasePurchasableReceiptListDelegate::CreateLambda(
[](const TArray<FGamebasePurchasableReceipt>* purchasableReceiptList, const FGamebaseError* error)
{
if (Gamebase::IsSuccess(error))
{
UE_LOG(GamebaseTestResults, Display, TEXT("RequestActivatedPurchases succeeded."));
for (const FGamebasePurchasableReceipt& purchasableReceipt : *purchasableReceiptList)
{
UE_LOG(GamebaseTestResults, Display, TEXT(" - gamebaseProductId= %s, price= %f, currency= %s, paymentSeq= %s, purchaseToken= %s"),
*purchasableReceipt.gamebaseProductId, purchasableReceipt.price, *purchasableReceipt.currency, *purchasableReceipt.paymentSeq, *purchasableReceipt.purchaseToken);
}
}
else
{
UE_LOG(GamebaseTestResults, Display, TEXT("RequestActivatedPurchases failed. (error: %d)"), error->code);
}
}));
}
Error | Error Code | Description |
---|---|---|
PURCHASE_NOT_INITIALIZED | 4001 | The purchase module has not been initialized. Check if the gamebase-adapter-purchase-IAP module has been added to project. |
PURCHASE_USER_CANCELED | 4002 | Purchase has been cancelled. |
PURCHASE_NOT_FINISHED_PREVIOUS_PURCHASING | 4003 | API has been called when a purchase logic is not completed. |
PURCHASE_NOT_ENOUGH_CASH | 4004 | Cannot purchase due to shortage of cash of the store. |
PURCHASE_INACTIVE_PRODUCT_ID | 4005 | Product is not activated. |
PURCHASE_NOT_EXIST_PRODUCT_ID | 4006 | Requested for purchase with invalid GamebaseProductID. |
PURCHASE_LIMIT_EXCEEDED | 4007 | You have exceeded your monthly purchase limit. |
PURCHASE_NOT_SUPPORTED_MARKET | 4010 | The store is not supported. The stores you can select are AS (App Store), GG (Google), ONESTORE, and GALAXY. |
PURCHASE_EXTERNAL_LIBRARY_ERROR | 4201 | Error in IAP library. Check DetailCode. |
PURCHASE_UNKNOWN_ERROR | 4999 | Unknown error in purchase. Please upload the entire logs to Customer Center and we'll reply at the earliest possible moment. |
PURCHASE_EXTERNAL_LIBRARY_ERROR
GamebaseError* gamebaseError = error; // GamebaseError object via callback
if (Gamebase::IsSuccess(error))
{
// succeeded
}
else
{
UE_LOG(GamebaseTestResults, Display, TEXT("code: %d, message: %s"), error->code, *error->message);
GamebaseInnerError* moduleError = gamebaseError.error; // GamebaseError.error object from external module
if (moduleError.code != GamebaseErrorCode::SUCCESS)
{
UE_LOG(GamebaseTestResults, Display, TEXT("moduleErrorCode: %d, moduleErrorMessage: %s"), moduleError->code, *moduleError->message);
}
}