Game > Gamebase > Unreal SDK使用ガイド > 決済

ここではUnrealでアプリ内決済機能を使用するために必要な設定方法を説明します。 Gamebaseは、1つの統合された決済APIを提供し、ゲームから簡単に多くのストアのアプリ内決済を連携できるようにサポートします。

Settings

AndroidまたはiOSでアプリ内決済機能を設定する方法は、次の文書を参照してください。

Unreal Plugin設定

[注意]

外部プラグインで決済関連処理がある場合、 Gamebase決済機能が正常に動作しない可能性があります。

  • Unrealでデフォルトで有効になっているOnline SubSystemプラグインを無効化またはストア機能を利用できないように変更する必要があります。

    • Online SubSystem GooglePlayプラグイン使用時 /Config/Android/AndroidEngine.iniファイルを編集します。

      [OnlineSubsystemGooglePlay.Store]
      bSupportsInAppPurchasing=False
      bUseGooglePlayBillingApiV2=False
      
    • Online SubSystem iOSプラグイン使用時 /Config/IOS/IOSEngine.iniファイルを編集します。

      [OnlineSubsystemIOS.Store]
      bSupportsInAppPurchasing=False
      

Purchase Flow

アイテムの購入は大きく分けて決済フロー、消費フロー、再処理フローの3つがあります。 決済フローは、次のような順序で実装してください。

purchase flow

  1. 以前の決済が正常に終了せず、再処理が動作しない場合、決済が失敗します。そのため決済前にRequestItemListOfNotConsumedを呼び出して再処理を行い、未支給のアイテムがある場合はConsume Flowを進行します。
  2. ゲームクライアントではGamebase SDKのRequestPurchaseを呼び出して決済を試行します。
  3. 決済が成功するとRequestItemListOfNotConsumedを呼び出して未消費決済履歴を確認した後、支給するアイテムが存在場合、Consume Flowを進行します。

Consume Flow

未消費決済履歴リストに値がある場合、次のような順序でConsume Flowを進行してください。

[注意]

アイテムの重複支給が発生しないように、ゲームサーバーで必ず重複支給の有無をチェックしてください。

consume flow

  1. ゲームクライアントがゲームサーバーに決済アイテムのconsume(消費)をリクエストします。
    • UserID、paymentSeq、purchaseTokenを渡します。
  2. ゲームサーバーは、ゲームDBにすでに同じpaymentSeqでアイテムを支給した履歴があるかを確認します。
    • 2-1. まだアイテムを支給していなければGamebaseサーバーのPayment Transaction APIを呼び出してpurchaseTokenが有効か、レスポンスフィールドのpaymentSeqと一致するか検証します。
    • 2-2. gamebaseProductIdはサーバーのPayment Transaction APIのレスポンスフィールドで確認できます。
      • クライアントの未消費決済履歴リストにもgamebaseProductIdが存在しますが、再処理時にはその値がない場合もありますので、サーバーのPayment Transaction APIから取得したgamebaseProductIdの値を使用してください。
    • 2-3. Payment Transaction APIの呼び出しが成功し、purchaseTokenが正常であることが確認されると、UserIDにgamebaseProductIdに該当するアイテムを支給します。
    • 2-4. アイテム支給後、ゲームDBにUserID、gamebaseProductId、paymentSeq、purchaseTokenを保存して重複支給防止または再支給ができるようにします。
  3. アイテム支給有無に関係なく、ゲームサーバーは未消費履歴が返されないようにGamebaseサーバーのconsume(消費) APIを呼び出してアイテムの支給を完了します。

Retry Transaction Flow

retry transaction flow

  • ストア決済には成功したがエラーが発生して正常に終了しなかった場合があります。
  • RequestItemListOfNotConsumedを呼び出して再処理を行い、未支給のアイテムがある場合、Consume Flowを進行してください。
  • 再処理は次の時点で呼び出すことを推奨します。
    • ログイン完了後
    • 決済前
    • ゲーム内ショップ(またはロビー)に移動した時
    • ユーザープロフィールまたはメールボックスを確認した時

Purchase Item

購入したいアイテムのitemSeqを利用して次のAPIを呼び出し、購入をリクエストします。 ゲームユーザーが購入をキャンセルする場合、PURCHASE_USER_CANCELEDエラーが返されます。

API

Supported Platforms UNREAL_IOS UNREAL_ANDROID UNREAL_WINDOWS

void RequestPurchase(const FString& gamebaseProductId, const FGamebasePurchasableReceiptDelegate& Callback);
void RequestPurchase(const FString& gamebaseProductId, const FString& payload, const FGamebasePurchasableReceiptDelegate& Callback);

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

struct FGamebasePurchasableReceipt
{   
    // 購入したアイテムの商品IDです。
    FString gamebaseProductId;

    // itemSeqで商品を購入するLegacy API用の識別子です。
    int64 itemSeq;

    // 購入した商品の価格です。
    float price;

    // 通貨コードです。
    FString currency;

    // 決済識別子です。
    // purchaseTokenと一緒に「Consume」サーバーAPIを呼び出すのに使用する重要な情報です。
    // Consume API : https://docs.toast.com/en/Game/Gamebase/en/api-guide/#purchase-iap
    // 注意:Consume APIはゲームサーバーで呼び出してください!
    FString paymentSeq;

    // 決済識別子です。
    // paymentSeqと一緒に「Consume」サーバーAPIを呼び出すのに使用する重要な情報です。
    // Consume APIでは「accessToken」という名前のパラメータで伝達する必要があります。
    // Consume API : https://docs.toast.com/en/Game/Gamebase/en/api-guide/#purchase-iap
    // 注意:Consume APIはゲームサーバーで呼び出してください!
    FString purchaseToken;

    // Google、Appleなどのストアコンソールに登録された商品IDです。
    FString marketItemId;

    // 次のような商品タイプがあります。
    // * UNKNOWN:認識できないタイプ。 Gamebase SDKをアップデートするか、Gamebaseサポートへお問い合わせください。
    // * CONSUMABLE:消費性商品。
    // * AUTO_RENEWABLE:サブスクリプション型の商品。
    // * CONSUMABLE_AUTO_RENEWABLE:サブスクリプション型の商品を購入したユーザーに、定期的に消費が可能な商品を支給したい場合に使用される「消費が可能なサブスクリプション商品」。
    FString productType;

    // 商品を購入したUser ID
    // 商品を購入していないUser IDでログインした場合、購入したアイテムを獲得できません。
    FString userId;

    // ストアの決済識別子です。
    FString paymentId;

    // サブスクリプション商品は更新するごとにpaymentIdが変更されます。
    // このフィールドは、初めてサブスクリプション商品を決済した時のpaymentIdを伝えます。
    // ストアや、決済サーバーの状態によって値が存在しない場合があるため
    // 常に有効な値を保障するわけではありません。
    FString originalPaymentId;

    // 商品を購入した時刻です。(epoch time)
    int64 purchaseTime;

    // 購読が終了する時刻です。(epoch time)
    int64 expiryTime;

    // 決済したストアコードです。
    // GamebaseStoreCodeクラスでストアコードリストを確認できます。
    FString storeCode;

    // RequestPurchase API呼び出し時、payloadに渡された値です。
    // ストアサーバー状態によって情報が流出する場合があるため、使用を推奨しません。
    FString payload;
    // プロモーション決済かどうか
    // - (Android) Gamebase決済サーバーで一時的に検証ロジックを切る場合は、常にfalseと出力されるため、有効な値が保障されません。
    bool isPromotion;
    // テスト決済かどうか
    // - (Android) Gamebase決済サーバーで一時的に検証ロジックを切る場合は、常にfalseと出力されるため、有効な値が保障されません。
    bool isTestPurchase;    
};

List Purchasable Items

アイテムリストを照会するには、次のAPIを呼び出します。 コールバックで返されるリスト内には、各アイテムの情報が含まれています。

API

Supported Platforms UNREAL_IOS UNREAL_ANDROID UNREAL_WINDOWS

void RequestItemListPurchasable(const FGamebasePurchasableItemListDelegate& Callback);

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

struct FGamebasePurchasableItem
{
    // Gamebaseコンソールに登録された商品IDです。
    // Gamebase.Purchase.requestPurchase APIで商品を購入する時に使用されます。
    FString gamebaseProductId;

    // itemSeqで商品を購入するLegacy API用の識別子です。
    int64 itemSeq;

    // 商品の価格です。
    float price;

    // 通貨コードです。
    FString currency;

    // Gamebaseコンソールに登録された商品名です。
    FString itemName;

    // Google、Appleなどのストアコンソールに登録された商品IDです。
    FString marketItemId;

    // 次のような商品タイプがあります。
    // * UNKNOWN:認識できないタイプ。 Gamebase SDKをアップデートするか、Gamebaseサポートへお問い合わせください。
    // * CONSUMABLE:消費性商品。
    // * AUTORENEWABLE:サブスクリプション型の商品。
    // * CONSUMABLE_AUTO_RENEWABLE:サブスクリプション型の商品を購入したユーザーに、定期的に消費が可能な商品を支給したい場合に使用される「消費が可能なサブスクリプション商品」。
    FString productType;

    // 通貨記号が含まれたローカライズされた価格情報です。
    FString localizedPrice;

    // ストアコンソールに登録されたローカライズされた商品名です。
    FString localizedTitle;

    // ストアコンソールに登録されたローカライズされた商品説明です。
    FString localizedDescription;

    // Gamebaseコンソールで該当商品の「使用状態」を表します。
    bool isActive;
};

Get a List of Non-Consumed Items

アイテムを購入したが、正常にアイテムが消費(配送、支給)されていない未消費決済内訳をリクエストします。 未決済内訳がある場合は、ゲームサーバー(アイテムサーバー)にリクエストして、アイテムを配送(支給)するように処理する必要があります。

FGamebasePurchasableConfiguration

API Mandatory(M) / Optional(O) Description
allStores O 同じUserIDにて他のストアで購入した未消費履歴も返します。
デフォルト値はfalseです。

API

Supported Platforms UNREAL_IOS UNREAL_ANDROID UNREAL_WINDOWS

void RequestItemListOfNotConsumed(const FGamebasePurchasableConfiguration& Configuration, const FGamebasePurchasableReceiptListDelegate& Callback);

Example

void Sample::RequestItemListOfNotConsumed(bool allStores)
{
    FGamebasePurchasableConfiguration Configuration;
    Configuration.allStores = allStores;

    IGamebase::Get().GetPurchase().RequestItemListOfNotConsumed(Configuration, 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);
        }
    }));
}

Get the List of Actived Subscriptions

現在のユーザーID基準で有効になっている購読リストを照会します。 決済が完了した購読商品(自動更新型購読、自動更新型消費性購読商品)は、有効期限が切れる前まで照会できます。 ユーザーIDが同じ場合、AndroidとiOSで購入した購読商品が照会されます。

[注意]

Androidでは現在、Google Playストアでのみサブスクリプション商品をサポートしています。

FGamebasePurchasableConfiguration

API Mandatory(M) / Optional(O) Description
allStores O 同じUserIDにて他のストアで購入した未消費履歴も返します。
デフォルト値はfalseです。

API

Supported Platforms UNREAL_IOS UNREAL_ANDROID

void RequestActivatedPurchases(const FGamebasePurchasableConfiguration& Configuration, const FGamebasePurchasableReceiptListDelegate& Callback);

Example

void Sample::RequestActivatedPurchases(bool allStores)
{
    FGamebasePurchasableConfiguration Configuration;
    Configuration.allStores = allStores;

    IGamebase::Get().GetPurchase().RequestActivatedPurchases(Configuration, 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);
        }
    }));
}

List Subscriptions Status

現在ユーザーID基準でサブスクリプション商品の状態を照会します。 コールバックで返されるリスト内にはサブスクリプション商品の情報が含まれています。

[注意]

FGamebasePurchasableConfiguration

API Mandatory(M) / Optional(O) Description
includeExpiredSubscriptions O 期限切れのサブスクリプション商品まで含めて照会します。
デフォルト値はfalseです。

API

Supported Platforms UNREAL_ANDROID

void RequestSubscriptionsStatus(const FGamebasePurchasableConfiguration& Configuration, const FGamebasePurchasableSubscriptionStatusDelegate& Callback);

Example

void Sample::RequestSubscriptionsStatus(bool includeExpiredSubscriptions)
{
    FGamebasePurchasableConfiguration Configuration;
    Configuration.allStores = allStores;
    IGamebase::Get().GetPurchase().RequestSubscriptionsStatus(Configuration, FGamebasePurchasableSubscriptionStatusDelegate::CreateLambda(
        [](const TArray<FGamebasePurchasableSubscriptionStatus>* purchasableReceiptList, const FGamebaseError* error)
    {
        if (Gamebase::IsSuccess(error))
        {
            UE_LOG(GamebaseTestResults, Display, TEXT("RequestSubscriptionsStatus succeeded."));
            for (const FGamebasePurchasableSubscriptionStatus& 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("RequestSubscriptionsStatus failed. (error: %d)"), error->code);
        }
    }));
}

VO

struct FGamebasePurchasableSubscriptionStatus
{
    // アプリがインストールされたストアについてGamebaseで内部的に定義したコードです。
    FString storeCode;

    // ストアの決済識別子です。
    FString paymentId;
    // サブスクリプション商品は更新さえる度にpaymentIdが変更されます。
    // このフィールドはサブスクリプション商品を最初に決済した時のpaymentIdを示します。
    // ストアおよび決済サーバーの状態によって値が存在しない場合があるため
    // 常に有効な値を保障するわけではありません。
    FString originalPaymentId;
    // 決済識別子です。
    // purchaseTokenと一緒にConsumeサーバーAPIを呼び出すために使用する重要な情報です。
    //    
    // 注意:Consume APIはゲームサーバーで呼び出してください! (https://docs.toast.com/en/Game/Gamebase/en/api-guide/#purchase-iap)
    FString paymentSeq;
    // 購入商品の商品IDです。
    FString marketItemId;

    // IAP Webコンソールの項目固有識別子
    int64 itemSeq;
    // 次のいずれかの値を持ちます。
    // * UNKNOWN:不明なタイプです。Gamebase SDKをアップデートするか、Gamebaseサポートにお問い合わせください。
    // * CONSUMABLE:消耗品です。
    // * AUTO_RENEWABLE:サブスクリプション商品です。
    FString productType;
    // 商品を購入したユーザーIDです。
    // 商品購入に使用していないユーザーIDでログインすると、購入した商品を受け取ることができません。
    FString userId;

    // 商品の価格です。
    float price;
    // 通貨情報です。
    FString currency;
    // Payment識別子。
    // paymentSeqで'Consume'サーバーAPIを呼び出すために使われる重要な情報です。
    // Consume APIで引数名を'accessToken'に指定する必要があります。
    // 参考: https://docs.toast.com/ko/Game/Gamebase/ko/api-guide/#purchase-iap
    FString purchaseToken;
    // 商品を購入した時間。(epoch time)
    int64 purchaseTime;

    // 購読の期限が切れる時間。(epoch time)
    int64 expiryTime;

    // RequestPurchase APIの呼び出し時にペイロードに渡される値です。
    // ストアサーバー状態によって情報が流出する場合があるため、使用を推奨しません。
    FString payload;

    // 購読状態
    // 全体ステータスコードは次の文書を参照してください。
    // - https://docs.nhncloud.com/en/TOAST/en/toast-sdk/iap-unity/#iapsubscriptionstatus
    int32 statusCode;

    // 購読状態の説明です。
    FString statusDescription;

    // Gamebaseコンソールに登録された商品IDです。
    // RequestPurchase APIで商品を購入する時に使用されます。
    FString gamebaseProductId;

    // この値はGoogleで購入する際に使用され、次の値を持つことができます。
    // ただし、GoogleサーバーのエラーによりGamebase決済サーバーで一時的に認証ロジックが無効になった場合
    // nullのみを返すため、常に有効な値を保証するわけではありません。
    // * null:正常決済
    // * テスト:テスト決済
    // * プロモーション:プロモーション決済
    FString purchaseType;
};

Error Handling

Error Error Code Description
PURCHASE_NOT_INITIALIZED 4001 Purchaseモジュールが初期化されませんでした。
gamebase-adapter-purchase-IAPモジュールをプロジェクトに追加したかを確認してください。
PURCHASE_USER_CANCELED 4002 ゲームユーザーがアイテムの購入をキャンセルしました。
PURCHASE_NOT_FINISHED_PREVIOUS_PURCHASING 4003 購入ロジックがまだ完了していない状態でAPIが呼び出されました。
PURCHASE_NOT_ENOUGH_CASH 4004 該当ストアのキャッシュが不足しているため決済できません。
PURCHASE_INACTIVE_PRODUCT_ID 4005 該当商品が有効な状態ではありません。
PURCHASE_NOT_EXIST_PRODUCT_ID 4006 存在しないGamebaseProductIDで決済をリクエストしました。
PURCHASE_LIMIT_EXCEEDED 4007 月購入限度を超過しました。
PURCHASE_NOT_SUPPORTED_MARKET 4010 サポートしないストアです。
選択できるストアはAS(App Store)、GG(Google)、ONESTORE、GALAXYです。
PURCHASE_EXTERNAL_LIBRARY_ERROR 4201 NHN Cloud IAPライブラリエラーです。
詳細エラーを確認してください。
PURCHASE_UNKNOWN_ERROR 4999 定義されていない購入エラーです。
全てのログをサポートへご送付ください。迅速に対応いたします。
* エラーコードの一覧は、次の文書を参照してください。
* エラーコード

PURCHASE_EXTERNAL_LIBRARY_ERROR

  • このエラーはNHN Cloud IAPライブラリでエラーが発生した時に返されます。
  • NHN Cloud IAPライブラリで発生したエラー情報は詳細エラーに含まれており、詳細なエラーコードおよびメッセージは次のように確認できます。
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);
    }
}
TOP