In-App Purchase (IAP) API

The VIVEPORT In-App Purchase API provides an easy way for your content to provide IAP functionalities. These APIs may be time consuming so IAP SDK provide a callback mechanism to let you know if API call is done without blocking the main thread.

How to use In-App Purchase (IAP) API

Note

  1. Remember that you should call Top Level API - Api.Init() before you access any other API functions, waiting for the Init() callback function is returned successfully , then you could call In-App Purchase (IAP) - IAPurchase.IsReady(). After checking the IsReady() callback is returned successfully , then you could start to use the IAPurchase APIs.
  2. If user has setup PIN code in VIVEPORT, after you call Purchase() or Subscribe(), there will be a PIN code dialog for the user to enter the PIN code. In your content, you need to implement controller which acts as Steam’s default controller to let user have the ability to interact with PIN code dialog.
  3. Remember that you should call Top Level API - Api.Shutdown() to teardown the library runtime after you finish using VIVEPORT SDK.

Implement listener to receive callback of APIs:

  • IAPurchaseListener
    • OnSuccess(string pchCurrencyName): callback when IsReady() is successful. Return the name of user’s currency setting.
    • OnRequestSuccess(string pchPurchaseId): callback when Request() is successful. Return the purchase Id which is used for purchase later.
    • OnPurchaseSuccess(string pchPurchaseId): callback when Purchase() is successful. Return the purchase Id which can be used for query later.
    • OnQuerySuccess(QueryResponse response): callback when Query() is successful.
    • OnQuerySuccess(QueryListResponse response): callback when Query() full list is successful. ( New )
    • OnBalanceSuccess(string pchBalance): callback when GetBalance() is successful. Return the balance of user’s wallet.
    • OnRequestSubscriptionSuccess(string pchSubscriptionId): callback when RequestSubscription() is successful. Return the subscription Id which is used for subscription later.
    • OnRequestSubscriptionWithPlanIDSuccess(string pchSubscriptionId): callback when RequestSubscriptionWithPlanID() is successful. Return the subscription Id which is used for subscription later.
    • OnSubscribeSuccess(string pchSubscriptionId): callback when Subscribe() is successful. Return the subscription Id which can be used for query or cancel subscription later.
    • OnQuerySubscriptionSuccess(Subscription[] subscriptionlist): callback when QuerySubscription() is successful. Return the subscriptions of the user.
    • OnCancelSubscriptionSuccess(bool bCanceled): callback when CancelSubscription() is successful. Return the status of cancellation.
    • OnFailure(int nCode, string pchMessage): callback when API call fails. Return the error code and message.

Call this function to initialize In-App Purchase:

_images/IAP_ApiKey.png

Call this function to get a purchase Id:

Note

You can refer Currency Mapping Table for setting price.

Call this function to make a purchase:

Call this function to query one specific transaction:

Call this function to request certain end user’s purchased item and get the full list. ( New )

Call this function to get the balance:

Call this function to get a subscription Id. This function is used for the case that you don’t register the subscription plan on developer console.

Call this function to get a subscription Id. This function is used for the case that you have registered the subscription plan on developer console.

Call this function to make a subscription.

Call this function to query a subscription.

Call this function to cancel a subscription.

Webhook Setting flow and usage

  • Register the Webhook on the developer console.
_images/Viveport_API_developer_console_webhook.png

In-App Purchase work flow

_images/Viveport_API_IAP_working_flow.png

In-App Purchase Subscription work flow without the game server

_images/Viveport_API_IAP_Subscription_workflow_client.png

In-App Purchase Subscription work flow with the game server

_images/Viveport_API_IAP_Subscription_workflow_server.png

Currency Mapping Table

Location Currency Name Mininum price Min Unit
AE AED 3.67 0.01
AT EUR 0.84 0.01
AU AUD 1.31 0.01
BE EUR 0.84 0.01
CA CAD 1.24 0.01
CH CHF 0.97 0.01
CN CNY 6 1
CZ CZK 21.67 0.01
DE EUR 0.84 0.01
DK DKK 6.26 0.01
ES EUR 0.84 0.01
FI EUR 0.84 0.01
FR EUR 0.84 0.01
GB GBP 0.75 0.01
HK HKD 7.7 0.1
IE EUR 0.84 0.01
IN INR 64.13 0.01
IS ISK 104.44 1
IT EUR 0.84 0.01
JP JPY 111 1
KR KRW 1120 1
MO HKD 7.7 0.1
NL EUR 0.84 0.01
NO NOK 7.83 0.01
NZ NZD 1.38 0.01
PL PLN 3.56 0.01
RU RUB 56.7 0.01
SA SAR 3.71 0.01
SE SEK 8.03 0.01
SG SGD 1.34 0.01
TW TWD 30 1
US USD 0.99 0.01
VN VND 22488 1
LA LAK 8227.65 0.01
KH KHR 4006.99 0.01
MM MMK 1351.19 0.01
PH PHP 50.58 0.01
ID IDR 13355.57 0.01
BN BND 1.34 0.01
TL USD 0.99 0.01
GU USD 0.99 0.01
PE PEN 3.21 0.01
BR BRL 3.14 0.01
AR ARS 17.15 0.01
UY UYU 29.13 0.01
BG BGN 1.64 0.01
GR EUR 0.84 0.01
LU EUR 0.84 0.01
SK EUR 0.84 0.01
MT EUR 0.84 0.01
HR HRK 6.31 0.01
CY EUR 0.84 0.01
EE EUR 0.84 0.01
HU HUF 258.71 0.01
LV EUR 0.84 0.01
LT EUR 0.84 0.01
PT EUR 0.84 0.01
RO RON 3.85 0.01
SI EUR 0.84 0.01
MY MYR 4.18 0.01
TH THB 32.76 0.01

Detailed Example in Unity

using Viveport;

public class ViveportDemo_IAP : MonoBehaviour
{
#if UNITY_ANDROID
    private int nWidth = 240, nHeight = 120;
#else
    private int nWidth = 80, nHeight = 40;
#endif
    private int nXStart = 10, nYStart = 35;

    static string VIVEPORT_ID = "app_VIVEPORT_ID" ; //replace it with your VIVEPORT ID
    static string API_KEY = "app_API_Key" ; // replace it with your API KEP on IAP
    private Result mListener;
    private static bool bIsDuplicatedSubscription = false;

    // Use this for initialization
    void Start()
    {
        mListener = new Result();
        Api.Init(InitStatusHandler, VIVEPORT_ID);
#if !UNITY_ANDROID
        Viveport.Core.Logger.Log("Version: " + Api.Version());
        Viveport.Core.Logger.Log("UserId: " + User.GetUserId());
#endif
    }

    // Update is called once per frame
    void Update()
    {

    }

    void OnGUI()
    {
#if UNITY_ANDROID
        GUIStyle customButton = new GUIStyle("button");
        customButton.fontSize = 24;
        if (GUI.Button(new Rect(nXStart, nYStart, nWidth, nHeight), "IsReady", customButton))
        {
            Viveport.Core.Logger.Log("IsReady");
            IAPurchase.IsReady(mListener, API_KEY);
        }

        if (GUI.Button(new Rect(nXStart, nYStart + 1 * nWidth + 10, nWidth, nHeight), "Request",
                                customButton))
        {
            Viveport.Core.Logger.Log("Request");
            //add virtual items into cache
            mListener.mItem.items = new string[3];
            mListener.mItem.items[0] = "sword";
            mListener.mItem.items[1] = "knife";
            mListener.mItem.items[2] = "medicine";
            IAPurchase.Request(mListener, "1");
        }

        if (GUI.Button(new Rect(nXStart, nYStart + 2 * nWidth + 20, nWidth, nHeight), "Purchase",
                       customButton))
        {
            Viveport.Core.Logger.Log("Purchase mListener.mItem.ticket=" + mListener.mItem.ticket);
            IAPurchase.Purchase(mListener, mListener.mItem.ticket);
        }

        if (GUI.Button(new Rect(nXStart, nYStart + 3 * nWidth + 30, nWidth, nHeight), "Query",
                       customButton))
        {
            Viveport.Core.Logger.Log("Query");
            IAPurchase.Query(mListener, mListener.mItem.ticket);
        }

        if (GUI.Button(new Rect(nXStart, nYStart + 4 * nWidth + 40, nWidth, nHeight), "GetBalance",
                       customButton))
        {
            Viveport.Core.Logger.Log("GetBalance");
            IAPurchase.GetBalance(mListener);
        }

        if (GUI.Button(new Rect(nXStart + nWidth + 10, nYStart + 1 * nWidth + 10, nWidth + 70, nHeight),
                       "RequestSubscription", customButton))
        {
            Viveport.Core.Logger.Log("RequestSubscription");
            IAPurchase.RequestSubscription(mListener, "1", "month", 1, "day", 2, 3, "pID");
        }

        if (GUI.Button(new Rect(nXStart + nWidth + 10, nYStart + 2 * nWidth + 20, nWidth + 120,
                       nHeight), "RequestSubscriptionWithPlanID", customButton))
        {
            Viveport.Core.Logger.Log("RequestSubscriptionWithPlanID");
            IAPurchase.RequestSubscriptionWithPlanID(mListener, "pID");
        }

        if (GUI.Button(new Rect(nXStart + nWidth + 10, nYStart + 3 * nWidth + 30, nWidth, nHeight),
                       "Subscribe", customButton))
        {
            Viveport.Core.Logger.Log("Subscribe bIsDuplicatedSubscription=" +
                                     bIsDuplicatedSubscription);
            IAPurchase.Subscribe(mListener, mListener.mItem.subscription_ticket);
        }

        if (GUI.Button(new Rect(nXStart + nWidth + 10, nYStart + 4 * nWidth + 40, nWidth + 50,
                       nHeight), "QuerySubscription", customButton))
        {
            Viveport.Core.Logger.Log("QuerySubscription");
            bIsDuplicatedSubscription = false;
            IAPurchase.QuerySubscription(mListener, mListener.mItem.subscription_ticket);
        }

        if (GUI.Button(new Rect(nXStart + nWidth + 10, nYStart + 5 * nWidth + 50, nWidth + 50,
                       nHeight), "CancelSubscription", customButton))
        {
            Viveport.Core.Logger.Log("CancelSubscription");
            IAPurchase.CancelSubscription(mListener, mListener.mItem.subscription_ticket);
        }

#else

        if (GUI.Button(new Rect(nXStart, nYStart, nWidth, nHeight), "IsReady"))
        {
            Viveport.Core.Logger.Log("IsReady");
            IAPurchase.IsReady(mListener, IAP_APP_TEST_KEY);
        }

        if (GUI.Button(new Rect(nXStart, nYStart + 1 * nWidth + 10 , nWidth + 120 , nHeight),
                       "Request"))
        {
            Viveport.Core.Logger.Log("Request");
            //add virtual items into cache
            mListener.mItem.items = new string[3];
            mListener.mItem.items[0] = "sword";
            mListener.mItem.items[1] = "knife";
            mListener.mItem.items[2] = "medicine";
            IAPurchase.Request(mListener, "1");
        }

        if (GUI.Button(new Rect(nXStart, nYStart + 2 * nWidth - 10, nWidth + 120, nHeight),
                       "RequestWithUserData"))
        {
            Viveport.Core.Logger.Log("Request");
            IAPurchase.Request(mListener, "1", "Knife");
        }

        if (GUI.Button(new Rect(nXStart + 2*nWidth + 65, nYStart + 1 * nWidth + 40, nWidth, nHeight),
                       "Purchase"))
        {
            Viveport.Core.Logger.Log("Purchase mListener.mItem.ticket=" + mListener.mItem.ticket);
            IAPurchase.Purchase(mListener, mListener.mItem.ticket);
        }

        if (GUI.Button(new Rect(nXStart + 4*nWidth +20 , nYStart + 1 * nWidth + 10, nWidth + 50 ,
                       nHeight), "Query"))
        {
            Viveport.Core.Logger.Log("Query");
            IAPurchase.Query(mListener, mListener.mItem.ticket);
        }

        if (GUI.Button(new Rect(nXStart + 4 * nWidth + 20, nYStart + 2 * nWidth - 10, nWidth + 50,
                       nHeight), "QueryPurchaseList"))
        {
            Viveport.Core.Logger.Log("QueryPurchaseList");
            IAPurchase.Query(mListener);
        }

        if (GUI.Button(new Rect(nXStart + 6 * nWidth + 20, nYStart + 1 * nWidth + 40, nWidth,
                       nHeight), "GetBalance"))
        {
            Viveport.Core.Logger.Log("GetBalance");
            IAPurchase.GetBalance(mListener);
        }

        if (GUI.Button(new Rect(nXStart, nYStart + 3 * nWidth + 20, nWidth + 120, nHeight),
                       "RequestSubscription"))
        {
            Viveport.Core.Logger.Log("RequestSubscription");
            IAPurchase.RequestSubscription(mListener, "1", "month", 1, "day", 2, 3, "pID");
        }

        if (GUI.Button(new Rect(nXStart, nYStart + 4 * nWidth  , nWidth + 120, nHeight),
                       "RequestSubscriptionWithPlanID"))
        {
            Viveport.Core.Logger.Log("RequestSubscriptionWithPlanID");
            IAPurchase.RequestSubscriptionWithPlanID(mListener, "pID");
        }

        if (GUI.Button(new Rect(nXStart + 3* nWidth -10 , nYStart + 4 * nWidth - 35, nWidth,
                       nHeight), "Subscribe"))
        {
            Viveport.Core.Logger.Log("Subscribe");
            IAPurchase.Subscribe(mListener, mListener.mItem.subscription_ticket);
        }

        if (GUI.Button(new Rect(nXStart + 4*nWidth + 20, nYStart + 4 * nWidth - 35, nWidth + 50,
                       nHeight), "QuerySubscription"))
        {
            Viveport.Core.Logger.Log("QuerySubscription");
            IAPurchase.QuerySubscription(mListener, mListener.mItem.subscription_ticket);
        }

        if (GUI.Button(new Rect(nXStart + 6*nWidth + 20, nYStart + 4 * nWidth - 35, nWidth + 50,
                       nHeight), "CancelSubscription"))
        {
            Viveport.Core.Logger.Log("CancelSubscription");
            IAPurchase.CancelSubscription(mListener, mListener.mItem.subscription_ticket);
        }

#endif

    }

    private static void InitStatusHandler(int nResult)
    {
        Viveport.Core.Logger.Log("InitStatusHandler: " + nResult);
    }
    //a sample class which store purchase id and puchased items
    public class Item
    {
        public string ticket = "test_id";
        public string[] items;
        public string subscription_ticket = "unity_test_subscriptionId";
    }
    class Result : IAPurchase.IAPurchaseListener
    {
        public Item mItem = new Item();
        public override void OnSuccess(string pchCurrencyName)
        {
            Viveport.Core.Logger.Log("[OnSuccess] pchCurrencyName=" + pchCurrencyName);
        }

        public override void OnRequestSuccess(string pchPurchaseId)
        {
            mItem.ticket = pchPurchaseId;
            Viveport.Core.Logger.Log("[OnRequestSuccess] pchPurchaseId=" + pchPurchaseId +
                                      ",mItem.ticket=" + mItem.ticket);
        }

        public override void OnPurchaseSuccess(string pchPurchaseId)
        {
            Viveport.Core.Logger.Log("[OnPurchaseSuccess] pchPurchaseId=" + pchPurchaseId);
            //if stored id equals the purchase id which is returned by OnPurchaseSuccess(),
            //give the virtual items to user
            if (mItem.ticket == pchPurchaseId)
            {
                Viveport.Core.Logger.Log("[OnPurchaseSuccess] give items to user");
                //to implement: give virtual items to user
            }
        }

        public override void OnQuerySuccess(IAPurchase.QueryResponse response)
        {
            //when status equals "success", then this purchase is valid, you can deliver
            //virtual items to user
            Viveport.Core.Logger.Log("[OnQuerySuccess] purchaseId=" + response.purchase_id
                                      + ",status=" + response.status);
        }

        public override void OnQuerySuccess(IAPurchase.QueryListResponse response)
        {
            //return all purchases which status equal to "success"
            Viveport.Core.Logger.Log("total=" + response.total + ",from=" + response.from
                                      + ",to=" + response.to);
            foreach (IAPurchase.QueryResponse2 qr in response.purchaseList)
            {
                Viveport.Core.Logger.Log("purchase_id=" + qr.purchase_id + ", user_data="
                                          + qr.user_data + ",price="+ qr.price + ",
                                          currency=" + qr.currency + ", paid_timestamp="
                                          + qr.paid_timestamp);
            }
        }

        public override void OnBalanceSuccess(string pchBalance)
        {
            Viveport.Core.Logger.Log("[OnBalanceSuccess] pchBalance=" + pchBalance);
        }

        public override void OnRequestSubscriptionSuccess(string pchSubscriptionId)
        {
            mItem.subscription_ticket = pchSubscriptionId;
            Viveport.Core.Logger.Log("[OnRequestSubscriptionSuccess] pchSubscriptionId="
                                      + pchSubscriptionId + ",mItem.subscription_ticket="
                                      + mItem.subscription_ticket);
        }

        public override void OnRequestSubscriptionWithPlanIDSuccess(string pchSubscriptionId)
        {
            mItem.subscription_ticket = pchSubscriptionId;
            Viveport.Core.Logger.Log("[OnRequestSubscriptionWithPlanIDSuccess] pchSubscriptionId="
                                      + pchSubscriptionId + ",mItem.subscription_ticket="
                                      + mItem.subscription_ticket);
        }

        public override void OnSubscribeSuccess(string pchSubscriptionId)
        {
            Viveport.Core.Logger.Log("[OnSubscribeSuccess] pchSubscriptionId=" + pchSubscriptionId);
            if (mItem.subscription_ticket == pchSubscriptionId)
            {
                Viveport.Core.Logger.Log("[OnSubscribeSuccess] give virtual items to user");
                //to implement: give virtual items to user
            }
        }

        public override void OnQuerySubscriptionSuccess(IAPurchase.Subscription[] subscriptionlist)
        {
            int size = subscriptionlist.Length;
            Viveport.Core.Logger.Log("[OnQuerySubscriptionSuccess] subscriptionlist size =" + size);
            if (size > 0)
            {
                for (int i = 0; i < size; i++)
                {
                    //when status equals "ACTIVE", then this subscription is valid, you can
                    //deliver virtual items to user
                    Viveport.Core.Logger.Log("[OnQuerySubscriptionSuccess]
                                  + subscriptionlist[" + i + "].status ="
                                  + subscriptionlist[i].status
                                  + ", subscriptionlist[" + i + "].plan_id = "
                                  + subscriptionlist[i].plan_id);
                    if (subscriptionlist[i].plan_id == "pID"
                        && subscriptionlist[i].status == "ACTIVE")
                    {
                        bIsDuplicatedSubscription = true;
                    }
                }
            }
        }

        public override void OnCancelSubscriptionSuccess(bool bCanceled)
        {
            Viveport.Core.Logger.Log("[OnCancelSubscriptionSuccess] bCanceled=" + bCanceled);
        }

        public override void OnFailure(int nCode, string pchMessage)
        {
            Viveport.Core.Logger.Log("[OnFailed] " + nCode + ", " + pchMessage);
        }
    }
}

Detailed Example in Unreal

ViveportIAPurchaseDemo.h

#include "Components/ActorComponent.h"
#include "ViveportType.h"
#include "ViveportIAPurchaseType.h"

#include "ViveportIAPurchaseDemo.generated.h"

UCLASS(ClassGroup = (Viveport), meta = (BlueprintSpawnableComponent))
class VIVEPORTSDK_API UViveportIAPurchaseDemo : public UActorComponent
{
    GENERATED_BODY()

public:
    // Called when the game starts
    void BeginPlay() override;

    void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

    /** The APP ID and Key for IAP testing*/
    FString VIVEPORT_ID = "app_test_id";
    FString API_KEY = "app_test_key";

private:
    /** Callback objects */
    class MyViveportApiStatus : public ViveportApiStatus
    {
    protected:
        UViveportIAPurchaseDemo* mDemo;
    public:
        void SetDemoPtr(UViveportIAPurchaseDemo* p) { mDemo = p; }
    };

    class MyViveportIAPurchaseListener : public IAPurchaseListener
    {
    protected:
        UViveportIAPurchaseDemo* mDemo;
    public:
        void SetDemoPtr(UViveportIAPurchaseDemo* p) { mDemo = p; }
    };

    class MyInitCallback : public MyViveportApiStatus
    {
    public:
        void OnSuccess(
        ) override;
        void OnFailure(
            int nErrorCode
        ) override;
    };

    MyInitCallback myInitCallback;

    class MyShutdownCallback : public MyViveportApiStatus
    {
    public:
        void OnSuccess(
        ) override;
        void OnFailure(
            int nErrorCode
        ) override;
    };

    MyShutdownCallback myShutdownCallback;

    class MyIsReadyCallback : public MyViveportIAPurchaseListener
    {
    public:
        void OnSuccess(
            const FString& pchCurrencyName
        ) override;
        void OnFailure(
            int nCode,
            const FString& pchMessage
        ) override;
    };

    MyIsReadyCallback myIsReadyCallback;

    class MyGetBalanceCallback : public MyViveportIAPurchaseListener
    {
    public:
        void OnBalanceSuccess(
            const FString& pchBalance
        ) override;
        void OnFailure(
            int nCode,
            const FString& pchMessage
        ) override;
    };

    MyGetBalanceCallback myGetBalanceCallback;

    class MyRequestCallback : public MyViveportIAPurchaseListener
    {
    public:
        void OnRequestSuccess(
            const FString& pchPurchaseId
        ) override;
        void OnFailure(
            int nCode,
            const FString& pchMessage
        ) override;
    };

    MyRequestCallback myRequestCallback;

    class MyPurchaseCallback : public MyViveportIAPurchaseListener
    {
    public:
        void OnPurchaseSuccess(
            const FString& pchPurchaseId
        ) override;
        void OnFailure(
            int nCode,
            const FString& pchMessage
        ) override;
    };

    MyPurchaseCallback myPurchaseCallback;

    class MyQueryCallback : public MyViveportIAPurchaseListener
    {
    public:
        void OnQuerySuccess(
            const FQueryResponse& response
        ) override;
        void OnFailure(
            int nCode,
            const FString& pchMessage
        ) override;
    };

    MyQueryCallback myQueryCallback;

public:
    ViveportApiStatus* GetInitCallback() { return &myInitCallback; }
    ViveportApiStatus* GetShutdownCallback() { return &myShutdownCallback; }
    IAPurchaseListener* GetIsReadyCallback() { return &myIsReadyCallback; }
    IAPurchaseListener* GetBalanceCallback() { return &myGetBalanceCallback; }
    IAPurchaseListener* GetRequestCallback() { return &myRequestCallback; }
    IAPurchaseListener* GetPurchaseCallback() { return &myPurchaseCallback; }
    IAPurchaseListener* GetQueryCallback() { return &myQueryCallback; }
};

ViveportIAPurchaseDemo.cpp

#include "ViveportSDKPrivatePCH.h"
#include "ViveportIAPurchaseDemo.h"
#include "ViveportApi.h"
#include "ViveportIAPurchase.h"

void UViveportIAPurchaseDemo::BeginPlay()
{
    Super::BeginPlay();

    myInitCallback.SetDemoPtr(this);
    myShutdownCallback.SetDemoPtr(this);
    myIsReadyCallback.SetDemoPtr(this);
    myGetBalanceCallback.SetDemoPtr(this);
    myRequestCallback.SetDemoPtr(this);
    myPurchaseCallback.SetDemoPtr(this);
    myQueryCallback.SetDemoPtr(this);

    // Call ViveportApi::Init()
    UViveportApi::Init(&myInitCallback, VIVEPORT_ID);
}

void UViveportIAPurchaseDemo::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    Super::EndPlay(EndPlayReason);

    // Call ViveportApi::Shutdown()
    UViveportApi::Shutdown(&myShutdownCallback);
}

/***************************************************************
*                    MyInitCallback
***************************************************************/

void UViveportIAPurchaseDemo::MyInitCallback::OnSuccess()
{
    UE_LOG(ViveportSDK, Log, TEXT("[UViveportIAPurchaseDemo][MyInitCallback] Init success."));
    FString fstring("Init success.");
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);

    UViveportIAPurchase::IsReady(mDemo->GetIsReadyCallback(), mDemo->API_KEY);
}

void UViveportIAPurchaseDemo::MyInitCallback::OnFailure(int nErrorCode)
{
    UE_LOG(ViveportSDK, Log, TEXT("[UViveportIAPurchaseDemo][MyInitCallback] Init failure.
           Error = %d"), nErrorCode);
    FString fstring = FString::Printf(TEXT("Init failure. Error = %d"), nErrorCode);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);
}

/***************************************************************
*                    MyShutdownCallback
***************************************************************/

void UViveportIAPurchaseDemo::MyShutdownCallback::OnSuccess()
{
    UE_LOG(ViveportSDK, Log, TEXT("[UViveportIAPurchaseDemo][MyShutdownCallback] Shutdown success."));
    FString fstring("Shutdown success.");
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);
}

void UViveportIAPurchaseDemo::MyShutdownCallback::OnFailure(int nErrorCode)
{
    UE_LOG(ViveportSDK, Error, TEXT("[UViveportIAPurchaseDemo][MyShutdownCallback] Shutdown
           failure. Error = %d"), nErrorCode);
    FString fstring = FString::Printf(TEXT("Shutdown failure. error=%d"), nErrorCode);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);
}

/***************************************************************
*                    MyIsReadyCallback
***************************************************************/

void UViveportIAPurchaseDemo::MyIsReadyCallback::OnSuccess(const FString& pchCurrencyName)
{
    UE_LOG(ViveportSDK, Log, TEXT("[UViveportIAPurchaseDemo][MyIsReadyCallback] IsReady success.
           Currency name = %s"), *pchCurrencyName);
    FString fstring = FString::Printf(TEXT("Is ready success, currency name = %s"), *pchCurrencyName);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);

    // Get balance
    UViveportIAPurchase::GetBalance(mDemo->GetBalanceCallback());
}

void UViveportIAPurchaseDemo::MyIsReadyCallback::OnFailure(int nCode, const FString& pchMessage)
{
    UE_LOG(ViveportSDK, Error, TEXT("[UViveportIAPurchaseDemo][MyIsReadyCallback] IsReady failure.
           Code = %d, message = %s"), nCode, *pchMessage);
    FString fstring = FString::Printf(TEXT("IsReady failure. Code = %d, message = %s"), nCode,
                                      *pchMessage);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);
}

/***************************************************************
*                    MyGetBalanceCallback
***************************************************************/

void UViveportIAPurchaseDemo::MyGetBalanceCallback::OnBalanceSuccess(const FString& pchBalance)
{
    UE_LOG(ViveportSDK, Log, TEXT("[UViveportIAPurchaseDemo][MyGetBalanceCallback] GetBalance
           success. Balance = %s"), *pchBalance);
    FString fstring = FString::Printf(TEXT("GetBalance success. Balance = %s"), *pchBalance);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);

    int32 balance = FCString::Atoi(*pchBalance);
    if (balance > 0)
    {
        // You can choose one of the below Request APIs you need.
        // 1. Request purchase (if you don't need to keep the item name by Query API)
        UViveportIAPurchase::Request(mDemo->GetRequestCallback(), "1");

        // 2. Request purchase (if you need to keep the item name by Query API)
        // UViveportIAPurchase::Request(mDemo->GetRequestCallback(), "1", "Knife");
    }
}

void UViveportIAPurchaseDemo::MyGetBalanceCallback::OnFailure(int nCode, const FString& pchMessage)
{
    UE_LOG(ViveportSDK, Error, TEXT("[UViveportIAPurchaseDemo][MyGetBalanceCallback] GetBalance
           failure. Code = %d, message = %s"), nCode, *pchMessage);
    FString fstring = FString::Printf(TEXT("GetBalance failure. code = %d, message = %s"), nCode,
                                           *pchMessage);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);
}

/***************************************************************
*                    MyRequestCallback
***************************************************************/

void UViveportIAPurchaseDemo::MyRequestCallback::OnRequestSuccess(const FString& pchPurchaseId)
{
    UE_LOG(ViveportSDK, Log, TEXT("[UViveportIAPurchaseDemo][MyRequestCallback] Request success.
           Purchase id = %s"), *pchPurchaseId);
    FString fstring = FString::Printf(TEXT("Request success. Purchase id = %s"), *pchPurchaseId);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);

    // Query purchase
    UViveportIAPurchase::Purchase(mDemo->GetPurchaseCallback(), pchPurchaseId);
    //UViveportIAPurchase::Query(mDemo->GetQueryCallback(), pchPurchaseId);
}

void UViveportIAPurchaseDemo::MyRequestCallback::OnFailure(int nCode, const FString& pchMessage)
{
    UE_LOG(ViveportSDK, Error, TEXT("[UViveportIAPurchaseDemo][MyRequestCallback] Request failure.
           Code = %d, message = %s"), nCode, *pchMessage);
    FString fstring = FString::Printf(TEXT("Request failure. code = %d, message = %s"), nCode,
                                           *pchMessage);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);
}

/***************************************************************
*                    MyPurchaseCallback
***************************************************************/

void UViveportIAPurchaseDemo::MyPurchaseCallback::OnPurchaseSuccess(const FString& pchPurchaseId)
{
    UE_LOG(ViveportSDK, Log, TEXT("[UViveportIAPurchaseDemo][MyPurchaseCallback] Purchase success.
           Purchase id = %s"), *pchPurchaseId);
    FString fstring = FString::Printf(TEXT("Purchase success. Purchase id = %s"), *pchPurchaseId);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);

    // Query purchase
    // You can choose one of the below Query APIs you need.
    // 1. Query (if you only need to query specific purchase id and know the purchase status)
    UViveportIAPurchase::Query(mDemo->GetQueryCallback(), pchPurchaseId);

    // 2. Query (if you only need to query specific purchase id and know the purchase status)
    //    And you can refer the detailed sample in ViveportIAPQueryPurchaseListDemo.h and
    //    ViveportIAPQueryPurchaseListDemo.cpp.
    // UViveportIAPurchase::Query(mDemo->GetQueryCallback());
}

void UViveportIAPurchaseDemo::MyPurchaseCallback::OnFailure(int nCode, const FString& pchMessage)
{
    UE_LOG(ViveportSDK, Error, TEXT("[UViveportIAPurchaseDemo][MyPurchaseCallback] Purchase failure.
           Code = %d, message = %s"), nCode, *pchMessage);
    FString fstring = FString::Printf(TEXT("Purchase failure. Code = %d, message = %s"), nCode,
                                      *pchMessage);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);
}

/***************************************************************
*                    MyQueryCallback
***************************************************************/

void UViveportIAPurchaseDemo::MyQueryCallback::OnQuerySuccess(const FQueryResponse& response)
{
    UE_LOG(ViveportSDK, Log, TEXT("[UViveportIAPurchaseDemo][MyQueryCallback] Query success.
           Purchase id = %s"), *(response.purchase_id));
    FString fstring = FString::Printf(TEXT("Query success. Purchase id = %s"),
                                      *(response.purchase_id));
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);
}

void UViveportIAPurchaseDemo::MyQueryCallback::OnFailure(int nCode, const FString& pchMessage)
{
    UE_LOG(ViveportSDK, Error, TEXT("[UViveportIAPurchaseDemo][MyQueryCallback] Query failure.
                                     Code = %d, message = %s"), nCode, *pchMessage);
    FString fstring = FString::Printf(TEXT("Query failure. Code = %d, message = %s"), nCode,
                                           *pchMessage);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);
}

ViveportIAPSubscriptionDemo.h

#include "Components/ActorComponent.h"
#include "ViveportType.h"
#include "ViveportIAPurchaseType.h"

#include "ViveportIAPSubscriptionDemo.generated.h"

UCLASS(ClassGroup = (Viveport), meta = (BlueprintSpawnableComponent))
class VIVEPORTSDK_API UViveportIAPSubscriptionDemo : public UActorComponent
{
    GENERATED_BODY()

public:
    // Called when the game starts
    void BeginPlay() override;

    void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

    /** The VIVEPORT_ID and API_KEY for IAP testing*/
    FString VIVEPORT_ID = "app_test_id";
    FString API_KEY = "app_test_key";

    /** The Plan ID of Subscription*/
    FString PLAN_ID = "PlanID";

private:
    /** Callback objects */
    class MyViveportApiStatus : public ViveportApiStatus
    {
    protected:
        UViveportIAPSubscriptionDemo* mDemo;
    public:
        void SetDemoPtr(UViveportIAPSubscriptionDemo* p) { mDemo = p; }
    };

    class MyViveportIAPurchaseListener : public IAPurchaseListener
    {
    protected:
        UViveportIAPSubscriptionDemo* mDemo;
    public:
        void SetDemoPtr(UViveportIAPSubscriptionDemo* p) { mDemo = p; }
    };

    class MyInitCallback : public MyViveportApiStatus
    {
    public:
        void OnSuccess(
        ) override;
        void OnFailure(
            int nErrorCode
        ) override;
    };

    MyInitCallback myInitCallback;

    class MyShutdownCallback : public MyViveportApiStatus
    {
    public:
        void OnSuccess(
        ) override;
        void OnFailure(
            int nErrorCode
        ) override;
    };

    MyShutdownCallback myShutdownCallback;

    class MyIsReadyCallback : public MyViveportIAPurchaseListener
    {
    public:
        void OnSuccess(
            const FString& pchCurrencyName
        ) override;
        void OnFailure(
            int nCode,
            const FString& pchMessage
        ) override;
    };

    MyIsReadyCallback myIsReadyCallback;

    class MyGetBalanceCallback : public MyViveportIAPurchaseListener
    {
    public:
        void OnBalanceSuccess(
            const FString& pchBalance
        ) override;
        void OnFailure(
            int nCode,
            const FString& pchMessage
        ) override;
    };

    MyGetBalanceCallback myGetBalanceCallback;

    class MyRequestSubscriptionPlanIDCallback : public MyViveportIAPurchaseListener
    {
    public:
        void OnRequestSubscriptionWithPlanIDSuccess(
            const FString& pchSubscriptionId
        ) override;
        void OnFailure(
            int nCode,
            const FString& pchMessage
        ) override;
    };

    MyRequestSubscriptionPlanIDCallback myRequestSubscriptionPlanIDCallback;

    class MySubscribeCallback : public MyViveportIAPurchaseListener
    {
    public:
        void OnSubscribeSuccess(
            const FString& pchSubscriptionId
        ) override;
        void OnFailure(
            int nCode,
            const FString& pchMessage
        ) override;
    };

    MySubscribeCallback mySubscribeCallback;

    class MyQuerySubscriptionCallback : public MyViveportIAPurchaseListener
    {
    public:
        void OnQuerySubscriptionSuccess(
            const TArray<FSubscription>& subscriptionlist
        ) override;
        void OnFailure(
            int nCode,
            const FString& pchMessage
        ) override;
    };

    MyQuerySubscriptionCallback myQuerySubscriptionCallback;

public:
    ViveportApiStatus* GetInitCallback() { return &myInitCallback; }
    ViveportApiStatus* GetShutdownCallback() { return &myShutdownCallback; }
    IAPurchaseListener* GetIsReadyCallback() { return &myIsReadyCallback; }
    IAPurchaseListener* GetBalanceCallback() { return &myGetBalanceCallback; }
    IAPurchaseListener* GetRequestSubscriptionPlanIDCallback()
    { return &myRequestSubscriptionPlanIDCallback; }
    IAPurchaseListener* GetSubscribeCallback() { return &mySubscribeCallback; }
    IAPurchaseListener* GetQuerySubscriptionCallback() { return &myQuerySubscriptionCallback; }
};

ViveportIAPSubscriptionDemo.cpp

#include "ViveportSDKPrivatePCH.h"
#include "ViveportIAPSubscriptionDemo.h"
#include "ViveportApi.h"
#include "ViveportIAPurchase.h"

void UViveportIAPSubscriptionDemo::BeginPlay()
{
    Super::BeginPlay();

    myInitCallback.SetDemoPtr(this);
    myShutdownCallback.SetDemoPtr(this);
    myIsReadyCallback.SetDemoPtr(this);
    myGetBalanceCallback.SetDemoPtr(this);
    myRequestSubscriptionPlanIDCallback.SetDemoPtr(this);
    mySubscribeCallback.SetDemoPtr(this);
    myQuerySubscriptionCallback.SetDemoPtr(this);

    // Call ViveportApi::Init()
    UViveportApi::Init(&myInitCallback, VIVEPORT_ID);
}

void UViveportIAPSubscriptionDemo::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    Super::EndPlay(EndPlayReason);

    // Call ViveportApi::Shutdown()
    UViveportApi::Shutdown(&myShutdownCallback);
}

/***************************************************************
*                    MyInitCallback
***************************************************************/

void UViveportIAPSubscriptionDemo::MyInitCallback::OnSuccess()
{
    UE_LOG(ViveportSDK, Log, TEXT("[UViveportIAPSubscriptionDemo][MyInitCallback] Init success."));
    FString fstring("Init success.");
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);

    UViveportIAPurchase::IsReady(mDemo->GetIsReadyCallback(), mDemo->API_KEY);
}

void UViveportIAPSubscriptionDemo::MyInitCallback::OnFailure(int nErrorCode)
{
    UE_LOG(ViveportSDK, Log, TEXT("[UViveportIAPSubscriptionDemo][MyInitCallback] Init failure.
                                   Error = %d"), nErrorCode);
    FString fstring = FString::Printf(TEXT("Init failure. Error = %d"), nErrorCode);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);
}

/***************************************************************
*                    MyShutdownCallback
***************************************************************/

void UViveportIAPSubscriptionDemo::MyShutdownCallback::OnSuccess()
{
    UE_LOG(ViveportSDK, Log, TEXT("[UViveportIAPSubscriptionDemo][MyShutdownCallback]
                                   Shutdown success."));
    FString fstring("Shutdown success.");
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);
}

void UViveportIAPSubscriptionDemo::MyShutdownCallback::OnFailure(int nErrorCode)
{
    UE_LOG(ViveportSDK, Error, TEXT("[UViveportIAPSubscriptionDemo][MyShutdownCallback]
                                     Shutdown failure. Error = %d"), nErrorCode);
    FString fstring = FString::Printf(TEXT("Shutdown failure. error=%d"), nErrorCode);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);
}

/***************************************************************
*                    MyIsReadyCallback
***************************************************************/

void UViveportIAPSubscriptionDemo::MyIsReadyCallback::OnSuccess(const FString& pchCurrencyName)
{
    UE_LOG(ViveportSDK, Log, TEXT("[UViveportIAPSubscriptionDemo][MyIsReadyCallback] IsReady
                                    success. Currency name = %s"), *pchCurrencyName);
    FString fstring = FString::Printf(TEXT("Is ready success, currency name = %s"), *pchCurrencyName);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);

    // Get balance
    UViveportIAPurchase::GetBalance(mDemo->GetBalanceCallback());
}

void UViveportIAPSubscriptionDemo::MyIsReadyCallback::OnFailure(int nCode, const FString& pchMessage)
{
    UE_LOG(ViveportSDK, Error, TEXT("[UViveportIAPSubscriptionDemo][MyIsReadyCallback]
                                     IsReady failure. Code = %d, message = %s"), nCode, *pchMessage);
    FString fstring = FString::Printf(TEXT("IsReady failure. Code = %d, message = %s"),
                                            nCode, *pchMessage);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);
}

/***************************************************************
*                    MyGetBalanceCallback
***************************************************************/

void UViveportIAPSubscriptionDemo::MyGetBalanceCallback::OnBalanceSuccess(const FString& pchBalance)
{
    UE_LOG(ViveportSDK, Log, TEXT("[UViveportIAPSubscriptionDemo][MyGetBalanceCallback] GetBalance
                                   success. Balance = %s"), *pchBalance);
    FString fstring = FString::Printf(TEXT("GetBalance success. Balance = %s"), *pchBalance);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);

    int32 balance = FCString::Atoi(*pchBalance);
    if (balance > 0)
    {
        // Request subscription
        UViveportIAPurchase::RequestSubscriptionWithPlanID(
        mDemo->GetRequestSubscriptionPlanIDCallback(), mDemo->PLAN_ID);
    }
}

void UViveportIAPSubscriptionDemo::MyGetBalanceCallback::OnFailure(int nCode, const FString& pchMessage)
{
    UE_LOG(ViveportSDK, Error, TEXT("[UViveportIAPSubscriptionDemo][MyGetBalanceCallback] GetBalance
                                     failure. Code = %d, message = %s"), nCode, *pchMessage);
    FString fstring = FString::Printf(TEXT("GetBalance failure. code = %d, message = %s"), nCode,
                                      *pchMessage);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);
}

/***************************************************************
*                    MyRequestSubscriptionPlanIDCallback
***************************************************************/

void UViveportIAPSubscriptionDemo::MyRequestSubscriptionPlanIDCallback::
     OnRequestSubscriptionWithPlanIDSuccess(const FString& pchSubscriptionId)
{
    UE_LOG(ViveportSDK, Log, TEXT("[UViveportIAPSubscriptionDemo][MyRequestSubscriptionPlanIDCallback]
                                   Request subscription success. Subscription id = %s"),
                                   *pchSubscriptionId);
    FString fstring = FString::Printf(TEXT("Request subscription success. Subscription id = %s"),
                                            *pchSubscriptionId);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);

    // Subscribe the item
    UViveportIAPurchase::Subscribe(mDemo->GetSubscribeCallback(), pchSubscriptionId);
    //UViveportIAPurchase::QuerySubscription(mDemo->GetQuerySubscriptionCallback(),
                                             pchSubscriptionId);
}

void UViveportIAPSubscriptionDemo::MyRequestSubscriptionPlanIDCallback::OnFailure(int nCode,
                                   const FString& pchMessage)
{
    UE_LOG(ViveportSDK, Error, TEXT("[UViveportIAPSubscriptionDemo]
    [MyRequestSubscriptionPlanIDCallback] Request subscription failure. Code = %d, message = %s")
    , nCode, *pchMessage);
    FString fstring = FString::Printf(TEXT("Request subscription failure. Code = %d, message = %s"),
                                            nCode, *pchMessage);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);
}

/***************************************************************
*                    MySubscribeCallback
***************************************************************/

void UViveportIAPSubscriptionDemo::MySubscribeCallback::OnSubscribeSuccess(
                                   const FString& pchSubscriptionId)
{
    UE_LOG(ViveportSDK, Log, TEXT("[UViveportIAPSubscriptionDemo][MySubscribeCallback] Subscribe
                                   success. Subscription id = %s"), *pchSubscriptionId);
    FString fstring = FString::Printf(TEXT("Subscribe success. Subscription id = %s"),
                                           *pchSubscriptionId);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);

    // Query subscription
    UViveportIAPurchase::QuerySubscription(mDemo->GetQuerySubscriptionCallback(), pchSubscriptionId);
}

void UViveportIAPSubscriptionDemo::MySubscribeCallback::OnFailure(int nCode,
                                                                  const FString& pchMessage)
{
    UE_LOG(ViveportSDK, Error, TEXT("[UViveportIAPSubscriptionDemo][MySubscribeCallback] Subscribe
                                     failure. Code = %d, message = %s"), nCode, *pchMessage);
    FString fstring = FString::Printf(TEXT("Subscribe failure. Code = %d, message = %s"),
                                            nCode, *pchMessage);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);
}

/***************************************************************
*                    MyQuerySubscriptionCallback
***************************************************************/

void UViveportIAPSubscriptionDemo::MyQuerySubscriptionCallback::OnQuerySubscriptionSuccess
                                  (const TArray<FSubscription>& subscriptionlist)
{
    UE_LOG(ViveportSDK, Log, TEXT("[UViveportIAPSubscriptionDemo][MyQuerySubscriptionCallback]
                                   Query subscription success."));
    FString fstring = FString::Printf(TEXT("Query subscription success."));
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);
}

void UViveportIAPSubscriptionDemo::MyQuerySubscriptionCallback::OnFailure(int nCode,
                                   const FString& pchMessage)
{
    UE_LOG(ViveportSDK, Error, TEXT("[UViveportIAPSubscriptionDemo][MyQuerySubscriptionCallback]
                                     Query subscription failure. Code = %d, message = %s"),
                                     nCode, *pchMessage);
    FString fstring = FString::Printf(TEXT("Query subscription failure. Code = %d, message = %s"),
                                            nCode, *pchMessage);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::White, fstring);
}

FAQ

While using In-App Purchase (IAP) SDK and the setup process, the developer needs to pay attention to the following items.

  • The developer needs to input all of the items’ currencies while using In-App Purchase (IAP) SDK. VIVEPORT developer console provides the related currency exchange rate table for the developer’s reference but the developer needs to input the detailed price by the different countries. For example, if the developer’s content is released to United States (US), China (CN), Japan (JP) and Australia (AU), he needs to input the In-App Purchase virtual items prices with those 4 countries’ currency exchange rate. If you don’t enter those four currencies, the end user will not be able to complete the payment process because the system can’t get the right currency.
  • VIVEPORT In-App Purchase (IAP) SDK uses the non-submit item architecture similar to the one used by Alipay. You don’t need to upload IAP details such as price, item description and so on to VIVEPORT server; we only help you to collect the payment. This gives you more freedom to manage your application’s IAP inventories.
  • If the developer wants to change the API-Key, just press the re-generate button and VIVEPORT will request the developer again to confirm the action. Once getting the developer confirmation, the system will generate the new API-Key right away. Remember, the later API-Key starts to work once the developer presses the re-generate button and the former API-Key is out of function instantly.