Game > Gamebase > Unity Developer's Guide > Purchase

This page describes how to set In-App Purchase (IAP). Gamebase provides an integrated purchase API to easily link IAP of many stores in a game.

[Caution]

If there is payment-related processing in an external package, the Gamebase payment function may not work properly.

Settings

For Android and iOS IAP setting, refer to the below documents.

Purchase Flow

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:

purchase flow

  1. If the previous purchase was not completed normally, the purchase will fail if the reprocessing is not performed. Therefore, call RequestItemListOfNotConsumed before the purchase to run reprocessing, so that the Consume Flow is executed if there are any unprovided items.
  2. The game client attempts to make a purchase by calling RequestPurchase of the Gamebase SDK.
  3. If the purchase is successful, call RequestItemListOfNotConsumed to check the unconsumed purchase details, and if there is an item to provide, proceed with the Consume Flow.

Consume Flow

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.

consume flow

  1. The game client makes a request to the game server to consume the purchase item.
    • Passes UserID, paymentSeq, and purchaseToken.
  2. The game server checks the game DB to see if there is a history of already providing an item with the same paymentSeq.
    • 2-1. If the item has not been provided yet, call the Gamebase server's Payment Transaction API to verify that the paymentSeq value is matched with the response field and purchaseToken value is valid.
    • 2-2. gamebaseProductId can be found in the response field of the server's Payment Transaction API
      • gamebaseProductId also exists in the client's used payment history list, but the value may not be available at the time of reprocessing, so please use the gamebaseProductId value obtained from the server's Payment Transaction API
    • 2-3. If the Payment Transaction API call succeeds and the purchaseToken is confirmed to be valid, issues the item corresponding to the gamebaseProductId to the UserID
    • 2-4. After issuing the item, saves the UserID, gamebaseProductId, paymentSeq, and purchaseToken in the game DB to prevent duplicate payments or re-payments
  3. Regardless of whether the item has been provided, the game server completes the item provision by calling the Gamebase server's consume API.

Retry Transaction Flow

retry transaction flow

  • There are cases where the store purchase has been made successfully but the process was not properly completed due to errors.
  • Call RequestItemListOfNotConsumed to run reprocessing and proceed with the Consume Flow for any unprovided items.
  • It is recommended to call reprocessing at the following times:
    • After login is completed
    • Before making a purchase
    • When entering the store (or lobby) in a game
    • When checking the user profile or mailbox

Purchase Item

Request a purchase by using the gamebaseProductId of the item to purchase.
The gamebaseProductId is generally the same as the ID of item registered at the store, but it can be changed in the Gamebase console. Additional information entered in the payload field is maintained at the PurchasableReceipt.payload field after a successful payment, so it can be used for many purposes.

[Caution]

The AMAZON store does not support the payload field.

When a game user cancels purchase, the PURCHASE_USER_CANCELED error is returned. Please process cancellation.

API

Supported Platforms UNITY_IOS UNITY_ANDROID

static void RequestPurchase(string gamebaseProductId, GamebaseCallback.GamebaseDelegate<GamebaseResponse.Purchase.PurchasableReceipt> callback)
static void RequestPurchase(string gamebaseProductId, string payload, GamebaseCallback.GamebaseDelegate<GamebaseResponse.Purchase.PurchasableReceipt> callback)

Example

public void RequestPurchase(string gamebaseProductId)
{
    Gamebase.Purchase.RequestPurchase(gamebaseProductId, (purchasableReceipt, error) =>
    {
        if (Gamebase.IsSuccess(error))
        {
            Debug.Log("Purchase succeeded.");
        }
        else
        {
            if (error.code == (int)GamebaseErrorCode.PURCHASE_USER_CANCELED)
            {
                Debug.Log("User canceled purchase.");
            }
            else
            {
                Debug.Log(string.Format("Purchase failed. error is {0}", error));
            }
        }
    });
}


public void RequestPurchase(string gamebaseProductId)
{
    string userPayload = "{\"description\":\"This is example\",\"channelId\":\"delta\",\"characterId\":\"abc\"}";
    Gamebase.Purchase.RequestPurchase(gamebaseProductId, userPayload, (purchasableReceipt, error) =>
    {
        if (Gamebase.IsSuccess(error))
        {
            Debug.Log("Purchase succeeded.");
            // userPayload value entered when calling API
            string payload = purchasableReceipt.payload
        }
        else
        {
            if (error.code == (int)GamebaseErrorCode.PURCHASE_USER_CANCELED)
            {
                Debug.Log("User canceled purchase.");
            }
            else
            {
                Debug.Log(string.Format("Purchase failed. error is {0}", error));
            }
        }
    });
}  

VO

public class PurchasableReceipt
{
    /// <summary>
    /// The product ID of a purchased item.
    /// </summary>
    public string gamebaseProductId;

    /// <summary>
    /// An identifier for Legacy API that purchases products with itemSeq.
    /// </summary>
    public long itemSeq;

    /// <summary>
    /// The price of purchased product.
    /// </summary>
    public float price;

    /// <summary>
    /// Currency code.
    /// </summary>
    public string currency;

    /// <summary>
    /// Payment identifier.
    /// This is an important piece of information used to call 'Consume' server API with purchaseToken.
    ///    
    /// Caution: Call Consume API from game server!
    /// <para/><see href="https://docs.toast.com/en/Game/Gamebase/en/api-guide/#purchase-iap">Consume API</see>
    /// </summary>
    public string paymentSeq;

    /// <summary>
    /// 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.
    ///    
    /// Caution: Call Consume API from game server!
    /// <para/><see href="https://docs.toast.com/en/Game/Gamebase/en/api-guide/#purchase-iap">Consume API</see>
    /// </summary>
    public string purchaseToken;

    /// <summary>
    /// The product ID that is registered to store console such as Google or Apple.
    /// </summary>
    public string marketItemId;

    /// <summary>
    /// 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.
    /// <para/><see cref="GamebasePurchase.ProductType"/>
    /// </summary>
    public string productType;

    /// <summary>
    /// 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.
    /// </summary>
    public string userId;

    /// <summary>
    /// The payment identifier of a store.
    /// </summary>
    public string paymentId;

    /// <summary>
    /// 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.
    /// </summary>
    public string originalPaymentId;

    /// <summary>
    /// The time when the product was purchased.(epoch time)
    /// </summary>
    public long purchaseTime;

    /// <summary>
    /// The time when the subscription expires.(epoch time)
    /// </summary>
    public long expiryTime;


     /// <summary>
    /// The store code where the purchase is made.
    /// You can check the store code list from GamebaseStoreCode class.
    /// </summary>
    public string storeCode;

    /// <summary>
    /// It is the value passed to payload when calling Gamebase.Purchase.requestPurchase API.
    /// Not recommended to use due to the possible loss of information depending on the store server status.
    /// </summary>
    public string payload;

    /// <summary>
    /// Whether it is promotion payment
    /// - (Android) If the validation logic is temporarily turned off in the Gamebase payment server, the value is output as false only, so it is not always guaranteed to be a valid value.
    /// </summary>
    public bool isPromotion;

    /// <summary>
    /// Whether it is test purchase
    /// - (Android) If the validation logic is temporarily turned off in the Gamebase payment server, the value is output as false only, so it is not always guaranteed to be a valid value.
    /// </summary>
    public bool isTestPurchase;
}

List Purchasable Items

To retrieve the list of items, call the following API. Information of each item is included in the array of callback return.

API

Supported Platforms UNITY_IOS UNITY_ANDROID

static void RequestItemListPurchasable(GamebaseCallback.GamebaseDelegate<List<GamebaseResponse.Purchase.PurchasableItem>> callback)

Example

public void RequestItemListPurchasable()
{
    Gamebase.Purchase.RequestItemListPurchasable((purchasableItemList, error) =>
    {
        if (Gamebase.IsSuccess(error))
        {
            Debug.Log("Get list succeeded.");
        }
        else
        {
            Debug.Log(string.Format("Get list failed. error is {0}", error));
        }
    });
}

VO

public class PurchasableItem
{
    /// <summary>
    /// The product ID that is registered with the Gamebase console.
    /// Used when a product is purchased using Gamebase.Purchase.requestPurchase API.
    /// </summary>
    public string gamebaseProductId;

    /// <summary>
    /// An identifier for Legacy API that purchases products with itemSeq.
    /// </summary>
    public long itemSeq;

    /// <summary>
    /// The price of a product.
    /// </summary>
    public float price;

    /// <summary>
    /// Currency code.
    /// </summary>
    public string currency;

    /// <summary>
    /// The name of a product registered in the Gamebase console.
    /// </summary>
    public string itemName;

    /// <summary>
    /// The product ID that is registered to store console such as Google or Apple.
    /// </summary>
    public string marketItemId;

    /// <summary>
    /// 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.
    /// * AUTORENEWABLE: 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.
    /// <para/><see cref="GamebasePurchase.ProductType"/>
    /// </summary>
    public string productType;

    /// <summary>
    /// Localized price information with currency symbol.
    /// </summary>
    public string localizedPrice;

    /// <summary>
    /// The name of a localized product registered with the store console.
    /// </summary>
    public string localizedTitle;

    /// <summary>
    /// The description of a localized product registered with the store console.
    /// </summary>
    public string localizedDescription;

    /// <summary>
    /// Shows whether the product is 'used or not' in the Gamebase console.
    /// </summary>
    public bool isActive;
}

List Non-Consumed Items

Request for the list of non-consumed purchases, in which items were not properly consumed (delivered or provided) after purchase.
In case there is any non-purchased item, request the game server (item server) to proceed with item delivery (provision). If the purchase was not completed normally, this API also serves the reprocessing function, so call it in the following situations:

  • Check if there's any unprovided items for a game user
    • After login is completed
    • When entering the store (or lobby) of a game
    • When checking the user profile or mailbox
  • Check if there's any item in need of reprocessing
    • Before making a purchase
    • After a purchase fails

GamebaseRequest.Purchase.PurchasableConfiguration

API Mandatory(M) / Optional(O) Description
allStores O Return the unconsumed list purchased from a different store with the same UserID.
Default value is false.

API

Supported Platforms UNITY_IOS UNITY_ANDROID

static void RequestItemListOfNotConsumed(GamebaseRequest.Purchase.PurchasableConfiguration configuration, GamebaseCallback.GamebaseDelegate<List<GamebaseResponse.Purchase.PurchasableReceipt>> callback)

Example

public void RequestItemListOfNotConsumedSample(bool allStores)
{
    var configuration = new GamebaseRequest.Purchase.PurchasableConfiguration
    {
        allStores = allStores
    };
    Gamebase.Purchase.RequestItemListOfNotConsumed(configuration, (purchasableReceiptList, error) =>
    {
        if (Gamebase.IsSuccess(error))
        {
            Debug.Log("Get list succeeded.");

            // Should Deal With This non-consumed Items.
            // Send this item list to the game(item) server for consuming item.
        }
        else
        {
            Debug.Log(string.Format("Get list failed. error is {0}", error));
        }
    });
}

List Activated Subscriptions

List activated subscriptions for the current user ID. Subscriptions that are paid up (e.g. auto-renewable subscription, auto-renewed consumable subscription) can be listed before they are expired.

[Caution]

For Android, subscriptions are currently supported only on the Google Play Store.

GamebaseRequest.Purchase.PurchasableConfiguration

API Mandatory(M) / Optional(O) Description
allStores O Return the unconsumed list purchased from a different store with the same UserID.
Default value is false.

API

Supported Platforms UNITY_IOS UNITY_ANDROID

static void RequestActivatedPurchases(GamebaseRequest.Purchase.PurchasableConfiguration configuration, GamebaseCallback.GamebaseDelegate<List<GamebaseResponse.Purchase.PurchasableReceipt>> callback)

Example

public void RequestActivatedPurchasesSample(bool allStores)
{
    var configuration = new GamebaseRequest.Purchase.PurchasableConfiguration
    {
        allStores = allStores
    };

    Gamebase.Purchase.RequestActivatedPurchases(configuration, (purchasableReceiptList, error) =>
    {
        if (Gamebase.IsSuccess(error) == true)
        {
            Debug.Log("RequestItemListPurchasable succeeded");

            foreach (GamebaseResponse.Purchase.PurchasableReceipt purchasableReceipt in purchasableReceiptList)
            {
                var message = new StringBuilder();
                message.AppendLine(string.Format("gamebaseProductId:{0}", purchasableReceipt.gamebaseProductId));
                message.AppendLine(string.Format("price:{0}", purchasableReceipt.price));
                message.AppendLine(string.Format("currency:{0}", purchasableReceipt.currency));

                // You will need paymentSeq and purchaseToken when calling the Consume API.
                // Refer to the following document for the Consume API.
                // https://docs.toast.com/en/Game/Gamebase/en/api-guide/#purchaseiap
                message.AppendLine(string.Format("paymentSeq:{0}", purchasableReceipt.paymentSeq));
                message.AppendLine(string.Format("purchaseToken:{0}", purchasableReceipt.purchaseToken));
                message.AppendLine(string.Format("marketItemId:{0}", purchasableReceipt.marketItemId));
                Debug.Log(message);
            }
        }
        else
        {
            // Check the error code and handle the error appropriately.
            Debug.Log(string.Format("RequestItemListPurchasable failed. error is {0}", error));
        }
    });
}

List Subscriptions Status

Retrieve the status of subscription products based on the current user ID. The list returned in the callback contains information about the subscription products.

[Caution]

  • The subscription status code is only returned correctly if you follow the guide below to set up the subscription event.
    • Go to [Game > Gamebase > Store Console Guide > Google Console Guide and set up event propagation of real-time subscription information within Google's system
    • The status code for a subscription product purchased without setting up events always returns 0 (PURCHASED).
  • Subscription products currently only supports Google Play Store.

GamebaseRequest.Purchase.PurchasableConfiguration

API Mandatory(M) / Optional(O) Description
includeExpiredSubscriptions O Retrieves including expired subscription products.
Default value is false.

API

Supported Platforms UNITY_ANDROID

static void RequestSubscriptionsStatus(GamebaseRequest.Purchase.PurchasableConfiguration configuration, GamebaseCallback.GamebaseDelegate<List<GamebaseResponse.Purchase.PurchasableReceipt>> callback)

Example

public void RequestSubscriptionsStatusSample(bool includeExpiredSubscriptions)
{
    var configuration = new GamebaseRequest.Purchase.PurchasableConfiguration
    {
        includeExpiredSubscriptions = includeExpiredSubscriptions
    };
    Gamebase.Purchase.RequestSubscriptionsStatus(configuration, (subscriptionStatusList, error) =>
    {
        if (Gamebase.IsSuccess(error) == true)
        {
            Debug.Log("RequestSubscriptionsStatus succeeded");
            foreach (GamebaseResponse.Purchase.PurchasableSubscriptionStatus subscriptionStatus in subscriptionStatusList)
            {
                var message = new StringBuilder();
                message.AppendLine(string.Format("storeCode:{0}", subscriptionStatus.storeCode));
                message.AppendLine(string.Format("itemSeq:{0}", subscriptionStatus.itemSeq));
                message.AppendLine(string.Format("price:{0}", subscriptionStatus.price));
                // Subscription status
                // Refer to the following document for the entire status code.
                // https://docs.nhncloud.com/en/TOAST/en/toast-sdk/iap-unity/#iapsubscriptionstatusstatus
                message.AppendLine(string.Format("statusCode:{0}", subscriptionStatus.statusCode));
                message.AppendLine(string.Format("gamebaseProductId:{0}", subscriptionStatus.gamebaseProductId));
                Debug.Log(message);
            }
        }
        else
        {
            // Check the error code and handle the error appropriately.
            Debug.Log(string.Format("RequestSubscriptionsStatus failed. error is {0}", error));
        }
    });
}

VO

public class PurchasableSubscriptionStatus
{
    /// <summary>
    /// This is code internally defined by Gamebase for the store where the app is installed.
    /// </summary>
    public string storeCode;
    /// <summary>
    /// Payment identifier of stores.
    /// </summary>
    public string paymentId;
    /// <summary>
    /// PaymentId is changed whenever the subscription product is renewed.
    /// This field shows the paymentId used when the subscription product was first paid for.
    /// This value does not guarantee to be always valid, as it can have no value
    // depending on the store and payment server status.
    /// </summary>
    public string originalPaymentId;
    /// Payment identifier.
    /// This is an important piece of information used to call 'Consume' server API with purchaseToken.
    ///    
    /// Caution: Call Consume API from game server!
    /// <para/><see href="https://docs.toast.com/en/Game/Gamebase/en/api-guide/#purchase-iap">Consume API</see>
    public string paymentSeq;
    /// <summary>
    /// Product ID of purchased item.
    /// </summary>
    public string marketItemId;
    /// <summary>
    /// Item unique identifier in the IAP web console
    /// </summary>
    public long itemSeq;
    /// <summary>
    /// One of 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.
    /// </summary>
    public string productType;
    /// <summary>
    // This is a user ID that purchased a product.
    // 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.
    /// </summary>
    public string userId;
    /// <summary>
    /// Product price/
    /// </summary>
    public float price;
    /// <summary>
    /// Currency information.
    /// </summary>
    public string currency;
    /// <summary>
    /// 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.
    ///
    /// <para/><see href="https://docs.toast.com/ko/Game/Gamebase/ko/api-guide/#purchase-iap">Purchase IAP</see>
    /// </summary>
    public string purchaseToken;
    /// <summary>
  // This value is used when making a purchase on Google, which can have the following values.
    // However, if the verification logic is temporarily disabled by Gamebase payment server due to error on Google server,
    // it returns only null, so please remember that it does not guarantee a valid return value at all times.
    // * null: Normal payment
    // * Test: Test payment
    // * Promotion: Promotion payment
    /// </summary>
    public string purchaseType;
    /// <summary>
    /// Time when the product is purchased.(epoch time)
    /// </summary>
    public long purchaseTime;
    /// <summary>
    /// Time when subscription expires.(epoch time)
    /// </summary>
    public long expiryTime;
    /// <summary>
    /// It is the value passed to payload when calling Gamebase.Purchase.requestPurchase API.
    /// Not recommended to use due to possible loss of information depending on the store server status.
    /// </summary>
    public string payload;
    /// <summary>
    /// Subscription status
    /// For all status cods, refer to the document below.
    /// <para/><see href="https://docs.nhncloud.com/en/TOAST/en/toast-sdk/iap-unity/#iapsubscriptionstatus">IAP Subscription Status</see>
    /// </summary>
    public int statusCode;
    /// <summary>
    /// Description for subscription status.
    /// </summary>
    public string statusDescription;
    /// <summary>
    /// Product ID registered in Gamebase console.
    /// It is used when purchasing products with the Gamebase.Purchase.requestPurchase API.
    /// </summary>
    public string gamebaseProductId;
}

Event by Promotion

When a promotional purchase is completed, get an event from GamebaseEventHandler to be processed. See the guide on how to process a promotional purchase event via GamebaseEventHandler. Game > Gamebase > User Guide for Unity SDK > ETC > Gamebase Event Handler

Supported Platforms UNITY_IOS UNITY_ANDROID

[Caution]

To execute an iOS promotion purchase, make sure to follow the guide for a setup. Game > Gamebase > User Guide for iOS SDK > Purchase > Event by Promotion

Error Handling

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, GALAXY, AMAZON, HUAWEI, and MYCARD.
PURCHASE_EXTERNAL_LIBRARY_ERROR 4201 Error in IAP library.
Check the code details.
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

  • The error is returned when an error occurs in NHN Cloud IAP library.
  • The information on the error in NHN Cloud IAP library is included in the error details, and you can find detailed error code and message as follows.
GamebaseError gamebaseError = error; // GamebaseError object via callback

if (Gamebase.IsSuccess(gamebaseError))
{
    // succeeded
}
else
{
    Debug.Log(string.Format("code:{0}, message:{1}", gamebaseError.code, gamebaseError.message));

    GamebaseError moduleError = gamebaseError.error; // GamebaseError.error object from external module
    if (null != moduleError)
    {
        int moduleErrorCode = moduleError.code;
        string moduleErrorMessage = moduleError.message;

        Debug.Log(string.Format("moduleErrorCode:{0}, moduleErrorMessage:{1}", moduleErrorCode, moduleErrorMessage));
    }
}
TOP