ONE store In-App Purchase Unity Plugin Guides

Introduction to ONE store Unity Plugin

The ONE store unity plug-in is provided to make it easy to apply the ONE store IAP in the Unity development environment. The ONE store unity plug-in includes the method that handles the payment-related functions and the result of calling those functions. The file configuration of plug-in is as follows.

  • It is developed with Unity 5.6.0f3 version.

ONE store Unity Plugin

ONE store Unity Plugin Configuration

If you download ONE store's unity plug-in through Github, you can check the folders seen below.

ONE store Unity Plugin Sample File Configuration

Application of ONE store Unity Plugin

Apply Unity Plugin

  • Put the iap_plugin_all_v17.00.00_20171225.aar (or iap_plugin_v17.00.00_20171225.jar, unity_adaptor_v17.00.00_20171225.jar, and AndroidManifest.xml ) in the Asset/Plugins/Android/Onestore.

    • Or you can use by selecting the Assets/Import Package/Custom Package and importing the IapV17_UnityPluginSample.unityPackage which is attached.

  • When building, enter packageName as com.onestore.iap.apisample, select signkey01.keystore (check in the Use Existing Keystore) and enter Keystore password: signkey01, choose signkey01 in Key Alias and enter password:signkey01.

  • Canvas has 10 buttons, and the OnClick of each button is a method within IapUi.cs and connected one-to-one.

  • Onestore_IapUi GameObject is connecting one script component of Onestore_IapUi.cs.

  • Onestore_IapCallbackManager GameObject is connecting two script components of Onestore_IapCallbackManager.cs and Onestore_IapResultListener.cs.

  • Background GameObject simply acts as the background screen.

ONE store Unity Plugin Reference

Basic Operation & Precautions

Android and Unity mutually exchange all data, including PurchaseData, ProudctDetail, the original copy of the purchased in-app products and signature in JSON format. To change in-app product name, payload, public key for the default test, you can modify Onestore_IapUi_IapUi.cs and perform the test.

In the sample app, it is required to check the list of the purchased in-app products and save data after the in-app products are purchased in order to consume the in-app products and change the status of the subscription. And therefore, the PlayerPrefs is used for simple storage and the productId is saved as the key value in JSON format. As for the productId, you can use it by receiving JSON data as the same key value and passing it to Android or changing it into other types.

To use the 'consume' or ManagerRecurring by saving the in-app product information purchased through getPurchase, getbuyIntent, and etc. it is required to develop the in-app product data by using the data structure that the developer desires to create.

Onestore_IapUi.cs and Onestore_IapCallbackManager.cs are responsible for transmission and reception. However, they have Onestore_IapCallMnager and Onestore_IapResultListener separately to easily change the test value without modifying the logic and to conveniently receive the result value such as string and Json in the form of the changed data, including PurchaseData and ProductDetail.

Unlike IapUi, Onestore_IapCallManger has class variables to change the API version when the update is made later. And it has a feature to display an error pop-up on the button operation when initialization has not been performed. Furthermore, when calling from Android to Unity, the following codes are used to designate which game object should be called by importing the com.onestore.iap.sdk.unity.IapPlugin instance to the constructor part and passing it to the constructor parameter. Developers can change the codes according to their needs.

In the IapUi.cs example, when it comes to the 'cancel/reactivate auto', the 'cancel/reactivate' operation has been implemented as a sample to operate alternately on/off due the screen space issue. After this command is performed, recurringState is not changed in the result value which is shown in a pop-up. You can check the updated recurringState only through getPurchase.

Basic Flow from Unity to Android

Basic Flow from Android to Unity

Introduction to ONE store Unity Plugin Methods and Operation

Operation to Initialize Payment

Create PurchaseClient in plugin by passing 'Service Connect : public key', bind ONE store's in-app service and receive the result through the listener. The calling direction is from top to bottom and Unity will call Android.

// Onestore_IapUi.cs (C#)
public void connectService ()
{
    var base64EncodedPublicKey = "MIGfMA0GCSqGSIb3D....";
    IapCallManager.connectService (base64EncodedPublicKey);
}

// Onestore_IapCallMnager.cs (C#)
public static void connectService (string publicKey)
{
    iapRequestAdapter.Call ("connect", publicKey);
}

// ONE store Unity Plugin (Java)
public void connect(String base64EncodedPublicKey) {
    Log.d(TAG, "connect - base64EncodedPublicKey " + base64EncodedPublicKey);
    mPurchaseClient = new PurchaseClient(getActivity(), base64EncodedPublicKey);
    publicKeyBase64 = base64EncodedPublicKey;
    mPurchaseClient.connect(mServiceConnectionListener);
}

This is a sequence where Android calls Unity through the connection listener. As a destination to receive the result value from Android to Unity, gameObject has already registered the GameObject named the 'IapCallbackManager' in the constructor part of IapCallManager.cs. With this value, Unity Plugin will pass the result value to this GameObject.

Since Onestore_IapCallbackManager.cs has been registered in the GameObject named 'IapCallbackManager', one of the string parameters of 'onConnected', 'onDisconnected' and 'onErrorNeedUpdateException' will be passed as the callback value to the ServiceConnectionListener method of this file. Each of the string parameters means that 'the service is successfully bound', 'the service is unbound' and 'the OSS is not the latest version or not installed'.

When the 'onConnected' is received through the parameter, serviceAvailableEvent() means a successful connection. And therefore, serviceAvailableEvent() is used to display an error pop-up when the connection is not successful.

Onestore_IapResultListener.cs has also been registered in the GameObject named "IapCallbackManager. During initialization, serviceConnectionEvent(callback) will call the serviceConnectionResult of the Onestore_IapResultListener.cs through Onestore_IapCallbackManager.serviceConnectionEvent += serviceConnectionResult, and will eventually display the connection-related message in the pop-up.

// ONE store Unity Plugin (Java)
PurchaseClient.ServiceConnectionListener mServiceConnectionListener = new PurchaseClient.ServiceConnectionListener() {
    @Override
    public void onConnected() {
        unitySendMessage(gameObject, "ServiceConnectionListener", "onConnected");
    }
      
    @Override
    public void onDisconnected() {
        unitySendMessage(gameObject, "ServiceConnectionListener", "onDisconnected");
    }
      
    @Override
    public void onErrorNeedUpdateException() {
        unitySendMessage(gameObject, "ServiceConnectionListener", "onErrorNeedUpdateException");
    }
};


// Onestore_IapCallbackManager.cs (C#)
public static event Action<string> serviceConnectionEvent;
  
public void ServiceConnectionListener (string callback)
{
    if (callback.Contains (preDefinedStrings [CBType.Connected])) {
        serviceAvailableEvent ();
    }
    serviceConnectionEvent (callback);
}

// Onestore_IapResultListener.cs (C#)
Onestore_IapCallbackManager.serviceConnectionEvent += serviceConnectionResult;
  
void serviceConnectionResult (string result)
{
    AndroidNative.showMessage ("serivce connect", result, "ok");
}

Check Support

The calling direction is from top to bottom and Unity will call Android.

// Onestore_IapUi.cs (C#)
public void isBillingSupported ()
{
    Onestore_IapCallManager.isBillingSupported ();
}

// Onestore_IapCallMnager.cs (C#)
public static void isBillingSupported ()
{
    checkServiceAvailable();
    iapRequestAdapter.Call("isBillingSupported", IAP_API_VERSION);
}


// ONE store Unity Plugin (Java)
public void isBillingSupported(int apiVersion) {
    if (mPurchaseClient == null) {
        Log.i(TAG, "PurchaseClient is not initialized");
        return;
    }
    mPurchaseClient.isBillingSupportedAsync(apiVersion, mBillingSupportedListener);
}

This is a sequence where Android calls Unity through the connection listener. The string types of the result value received from the callback are as follows.

  • onSuccess: if checking status information is success

  • onError : if failed, an error code will be passed to the application. A code composed of numbers, and descriptions in the string form are passed in the form of ToString.

  • onErrorRemoteException: it occurs when a Remote call is requested at the time of unbinding the service.

  • onErrorSecurityException: it occurs when the application is falsified or abnormal in the APK.

  • onErrorNeedUpdateException: it occurs when the OSS is not the latest version or not installed.

// ONE store Unity Plugin (Java)
PurchaseClient.BillingSupportedListener mBillingSupportedListener = new PurchaseClient.BillingSupportedListener() {
  
    @Override
    public void onSuccess() {
        unitySendMessage(gameObject, "BillingSupportedListener", "onSuccess");
    }
  
    @Override
    public void onError(IapResult result) {
        unitySendMessage(gameObject, "BillingSupportedListener", "onError" + result.toString());
    }
  
    @Override
    public void onErrorRemoteException() {
        unitySendMessage(gameObject, "BillingSupportedListener", "onErrorRemoteException");
    }
  
    @Override
    public void onErrorSecurityException() {
        unitySendMessage(gameObject, "BillingSupportedListener", "onErrorSecurityException");
    }
  
    @Override
    public void onErrorNeedUpdateException() {
        unitySendMessage(gameObject, "BillingSupportedListener", "onErrorNeedUpdateException");
    }
};

// Onestore_IapCallbackManager.cs (C#)
public static event Action<string> isBillingSupportedEvent;
  
public void BillingSupportedListener (string callback)
{
    isBillingSupportedEvent (callback);
}

{// Onestore_IapResultListener.cs (C#)
IapCallbackManager.isBillingSupportedEvent += isBillingSupportedResult;
  
void isBillingSupportedResult (string result)
{
    AndroidNative.showMessage ("isBillingSupported", result, "ok");
}

Check Purchase History

The calling direction is from top to bottom and Unity will call Android.


// Onestore_IapUi.cs (C#)
public void getPurchases ()
{
    Onestore_IapCallManager.getPurchases ();
}

// Onestore_IapCallMnager.cs (C#)
//inapp과 auto 두 가지 상품 타입이 존재하는데 각각 상품에 대해서 각각 따로 호출해야 합니다.  개발사에서 변경 가능한 부분
public static void getPurchases ()
{
    checkServiceAvailable ();
    iapRequestAdapter.Call ("getPurchase", IAP_API_VERSION, "inapp");
    iapRequestAdapter.Call ("getPurchase", IAP_API_VERSION, "auto");
}

// ONE store Unity Plugin (Java)
public void getPurchase(int apiVersion, final String productType) {
    if (mPurchaseClient == null) {
        Log.d(TAG, "PurchaseClient is not initialized");
        return;
    }
  
    mPurchaseClient.queryPurchasesAsync(apiVersion, productType, mQueryPurchaseListener);
}

This is a sequence where Android calls Unity through the connection listener. The string types of the result value received from the callback are as follows.

  • onSuccess: a success response to the 'check purchase information' calling :

if the purchased data exists, the format of “onSuccess” + json (productType: if the in-app product type is the inapp or the auto, purchaseData: the purchase data of the purchased original json, signature: the purchased signature) will be passed. If there is no purchase data, the format of “onSuccess”+ “[]” + producttype(“inapp” or “auto”) will be passed.

  • onErrorRemoteException

  • onErrorSecurityException

  • onErrorNeedUpdateException

  • onError: if failed, an error code will be passed to the application. A code composed of numbers, and descriptions in the string form are passed in the form of ToString.

The current signature is received from the IapCallbackManager but is not passed here. It is because that the review has been performed by default through the purchase data and signature within the SDK. If the signature is needed, it can be imported from the IapCallbackManager and used. Setting the PlayerPref value is for the test apps, and developers are required to actually manage or save items.

// ONE store Unity Plugin (Java)
PurchaseClient.QueryPurchaseListener mQueryPurchaseListener = new PurchaseClient.QueryPurchaseListener() {
    @Override
    public void onSuccess(List<PurchaseData> purchaseDataList, String productType) {
        if (purchaseDataList.size() > 0) {
            for (PurchaseData purchaseData : purchaseDataList) {
                unitySendMessage(gameObject, "QueryPurchaseListener", "onSuccess" + JsonUtil.makePurchaseDataToJson(purchaseData, productType));
            }
        } else {
            unitySendMessage(gameObject, "QueryPurchaseListener", "onSuccess" + purchaseDataList + productType);
        }
    }
  
    @Override
    public void onErrorRemoteException() {
        unitySendMessage(gameObject, "QueryPurchaseListener", "onErrorRemoteException");
    }
  
    @Override
    public void onErrorSecurityException() {
        unitySendMessage(gameObject, "QueryPurchaseListener", "onErrorSecurityException");
    }
  
    @Override
    public void onErrorNeedUpdateException() {
        unitySendMessage(gameObject, "QueryPurchaseListener", "onErrorNeedUpdateException");
    }
  
    @Override
    public void onError(IapResult result) {
        unitySendMessage(gameObject, "QueryPurchaseListener", "onError" + result.toString());
    }
};

// Onestore_IapCallbackManager.cs (C#)
public static event Action<PurchaseData> getPurchaseSuccessEvent;
public static event Action<string> getPurchaseErrorEvent;
  
public void QueryPurchaseListener (string callback)
{
    string data = findStringAfterCBType (callback, CBType.Success);
    if (data.Length > 0) {
        try {
            PurchaseResponse purchaseRes = JsonUtility.FromJson<PurchaseResponse> (data);
            PurchaseData purchaseData = JsonUtility.FromJson<PurchaseData> (purchaseRes.purchaseData);
            getPurchaseSuccessEvent (purchaseData);
        } catch (System.Exception ex) {
            Debug.Log ("QueryPurchaseListener Exception " + ex.Message);
            getPurchaseErrorEvent (data); //success but no data
        }
    } else {
        //because only the onError is followed by the additional data IapResult, pass the additional data only. Since no additional data exists in the rest of the errors, pass the callback as it is.
        string errorData = findStringAfterCBType (callback, CBType.Error);
        if (errorData.Length > 0) {
            getPurchaseErrorEvent (errorData);
        } else {
            getPurchaseErrorEvent (callback);
        }
    }
}


// IapResultListener.cs (C#)
void getPurchaseSuccessResult (PurchaseData result)
{
    AndroidNative.showMessage ("getPurchase success", result.ToString (), "ok");
    PlayerPrefs.SetString (result.productId, JsonUtility.ToJson (result));
}
  
void getPurchaseErrorResult (string result)
{
    if (result.Contains ("inapp")) {
        PlayerPrefs.SetString ("p5000", "");
    } else {
        PlayerPrefs.SetString ("a100000", "");
    }
    AndroidNative.showMessage ("getPurchase error", result, "ok");
}

Click 'getProductDetail ' Button : Method for Checking IAP SDK V17 Purchase Information

The calling direction is from top to bottom and Unity will call Android.

// Onestore_IapUi.cs (C#)
public void getProductDetails ()
{
    var inapp_products = new string[] {inapp_pId1, inapp_pId2, inapp_pId3};
    var auto_products = new string[] {auto_pId1};
  
    IapCallManager.getProductDetails (inapp_products, inapp_type);
    IapCallManager.getProductDetails (auto_products, auto_type);
}

// Onestore_IapCallMnager.cs (C#)
public static void getProductDetails (string[] products, string productType)
{
    checkServiceAvailable ();
    iapRequestAdapter.Call ("getProductDetails", new object[]{IAP_API_VERSION, products, productType});
}

// ONE store Unity Plugin (Java)
public void getProductDetails(int apiVersion, Object[] productItems, String productType) {
    if (mPurchaseClient == null) {
        Log.d(TAG, "PurchaseClient is not initialized");
        return;
    }
    ArrayList<Object> obj_products = new ArrayList<>(Arrays.asList(productItems));
    ArrayList<String> products = new ArrayList<>();
    for (Object object : obj_products) {
        products.add(object != null ? object.toString() : null);
    }
      
    mPurchaseClient.queryProductsAsync(apiVersion, products, productType, mQueryProductsListener);
}

This is a sequence where Android calls Unity through the connection listener. The string types of the result value received from the callback are as follows

  • onSuccess: a success response to the 'check purchase information' calling, json format. Developers are required to process the information, including the list of in-app products available for purchase and the in-app product type, by receiving data from the end IapResultListener. The sample simply shows the result in a pop-up.

  • onErrorRemoteException

  • onErrorSecurityException

  • onErrorNeedUpdateException

  • onError: if failed, an error code will be passed to the application. A code composed of numbers, and descriptions in the string form are passed in the form of ToString.

// ONE store Unity Plugin (Java)
PurchaseClient.QueryProductsListener mQueryProductsListener = new PurchaseClient.QueryProductsListener() {
    @Override
    public void onSuccess(List<ProductDetail> productDetails) {
        for (ProductDetail detaildata : productDetails) {
            unitySendMessage(gameObject, "QueryProductsListener", "onSuccess" + JsonUtil.makeProductDetailToJson(detaildata));
        }
    }
  
    @Override
    public void onErrorRemoteException() {
        unitySendMessage(gameObject, "QueryProductsListener", "onErrorRemoteException");
    }
  
    @Override
    public void onErrorSecurityException() {
        unitySendMessage(gameObject, "QueryProductsListener", "onErrorSecurityException");
    }
  
    @Override
    public void onErrorNeedUpdateException() {
        unitySendMessage(gameObject, "QueryProductsListener", "onErrorNeedUpdateException");
    }
  
    @Override
    public void onError(IapResult result) {
        unitySendMessage(gameObject, "QueryProductsListener", "onError" + result.toString());
    }
};


// Onestore_IapCallbackManager.cs (C#)
public static event Action<ProductDetail> queryProductsSuccessEvent;
public static event Action<string> queryProductsErrorEvent;
  
public void QueryProductsListener (string callback)
{
    string data = findStringAfterCBType (callback, CBType.Success);
    if (data.Length > 0) {
        ProductDetail productDetail = JsonUtility.FromJson<ProductDetail> (data);
        queryProductsSuccessEvent (productDetail);
    } else {
        string errorData = findStringAfterCBType (callback, CBType.Error);
        queryProductsErrorEvent (errorData);
    }
}

// Onestore_IapResultListener.cs (C#)
void queryProductsSuccessResult (ProductDetail result)
{
    AndroidNative.showMessage ("queryProducts success", result.ToString (), "ok");
}
  
void queryProductsErrorResult (string result) {
    AndroidNative.showMessage ("queryProducts error", result, "ok");
}

Purchase In-App Products

The calling direction is from top to bottom and Unity will call Android. When it comes to the inapp and auto products, only the in-app ID and in-app product type differs in IapUi.cs and the rest of the flow is the same. gameUserId and promotionApplicable is not frequently used in general, and therefore, if developers desire to use it by passing it to the default value ("" , false), they can put it by changing the value in the IapCallManager.

  • gameUserId - enter the unique identification number of a user who is using the application. This value is used as a key value to determine if it is available to participate in and to use promotions.

  • promotionApplicable - this delivers if it is available to participate in the promotion.

    • true : the user who is passed to the gameUserId is allowed to participate in a single promotion only once.

    • false : the user who is passed to the gameUserId cannot participate in the promotion.

In case of purchase, the Activity in the Plugin is called because the Activity of the service is required and the result value must be received. And the parameter value is saved in the intent and passed.

// Onestore_IapUi.cs (C#)
public void buyProductInapp ()
{
    //Up to 100 bytes are allowed for the size of the developPayload.
    //'null' is not allowed for developPayload.
    IapCallManager.buyProduct (inapp_pId1,  inapp_type, devPayload);
}

// Onestore_IapCallMnager.cs (C#)
public static void buyProduct (string productId, string productType,  string payload)
{
    checkServiceAvailable ();
    string gameUserId = "";
    bool promotionApplicable = false;
    iapRequestAdapter.Call ("launchPurchaseFlow", IAP_API_VERSION, productId, productType, payload, gameUserId, promotionApplicable);
}

// ONE store Unity Plugin (Java)
public void launchPurchaseFlow(int apiVersion, final String productId, final String productType,
        final String payload, final String gameUserId, boolean promotionApplicable) {
      
    Intent i = new Intent(getActivity(), IapUnityPluginActivity.class);
    i.putExtra("productId", productId);
    i.putExtra("productType", productType);
    i.putExtra("payload", payload);
    i.putExtra("apiVersion", apiVersion);
    i.putExtra("gameUserId", gameUserId);
    i.putExtra("promotionApplicable", promotionApplicable);
      
    getActivity().startActivity(i);
}

After calling mPurchaseClient.launchPurchaseFlow, receive the result value of the completed purchase through the onActivityResult. Verify if the intent data delivered when the purchase is completed in the mPurchaseClient.handlePurchaseData has been signed with the designated signature, and then pass the result value to the listener which was registered during the purchase.

// ONE store Unity Plugin (Java) : IapUnityPluginActivity
public class IapUnityPluginActivity extends Activity {
  
    private static final int LOGIN_REQUEST_CODE = 1001;
    private static final int PURCHASE_REQUEST_CODE = 2001;
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
          
        Intent i = getIntent();
        final String productId = i.getStringExtra("productId");
        final String productType = i.getStringExtra("productType");
        final String payload = i.getStringExtra("payload");
        final int apiVersion = i.getIntExtra("apiVersion", 5);
  
        final String gameUserId = i.getStringExtra("gameUserId");
        final boolean promotionApplicable = i.getBooleanExtra("promotionApplicable", false);
  
        if (TextUtils.isEmpty(productId) == false && TextUtils.isEmpty(productType) == false) {
            IapPlugin.INSTANCE.mPurchaseClient.launchPurchaseFlow(
                    apiVersion,
                    IapUnityPluginActivity.this,
                    PURCHASE_REQUEST_CODE,
                    productId,
                    "",//productName
                    productType,
                    payload,
                    gameUserId, //String gameUserId
                    promotionApplicable, //boolean promotionApplicable
                    IapPlugin.INSTANCE.mPurchaseFlowListener);
        }
    }
}

// ONE store Unity Plugin (Java) : IapUnityPluginActivity
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    Log.d("IapUnityPluginActivity", "onActivityResult requestCode " + requestCode);
  
    switch (requestCode) {
        case PURCHASE_REQUEST_CODE:
             /*
             * The intent data which is received upon calling the launchPurchaseFlowAsync API is parsing the response value through the handlePurchaseData.
             * After the parsing, the response result is passed through the PurchaseFlowListener which was delivered upon calling the launchPurchaseFlowAsync.
             */
            if (resultCode == RESULT_OK) {
                if (IapPlugin.INSTANCE.mPurchaseClient.handlePurchaseData(data) == false) {
                    // listener is null
                }
            } else {
                // user canceled , do nothing..
            }
            break;
        default:
    }
    finish();
}

This is a sequence where Android calls Unity through the connection listener. The string types of the result value received from the callback are as follows.

  • onSuccess - a success response to the 'request purchase'

  • onErrorRemoteException

  • onErrorSecurityException

  • onErrorNeedUpdateException

  • onError: if failed, an error code will be passed to the application. A code composed of numbers, and descriptions in the string form are passed in the form of ToString.

// ONE store Unity Plugin (Java)
PurchaseClient.PurchaseFlowListener mPurchaseFlowListener = new PurchaseClient.PurchaseFlowListener() {
    @Override
    public void onSuccess(PurchaseData purchaseData) {
        unitySendMessage(gameObject, "PurchaseFlowListener", "onSuccess" + JsonUtil.makePurchaseDataToJson(purchaseData, ""));
    }
  
    @Override
    public void onErrorRemoteException() {
        unitySendMessage(gameObject, "PurchaseFlowListener", "onErrorRemoteException");
    }
  
    @Override
    public void onErrorSecurityException() {
        unitySendMessage(gameObject, "PurchaseFlowListener", "onErrorSecurityException");
    }
  
    @Override
    public void onErrorNeedUpdateException() {
        unitySendMessage(gameObject, "PurchaseFlowListener", "onErrorNeedUpdateException");
    }
  
    @Override
    public void onError(IapResult result) {
        unitySendMessage(gameObject, "PurchaseFlowListener", "onError" + result.toString());
    }
};

// Onestore_IapCallbackManager.cs (C#)
public static event Action<PurchaseData> getPurchaseIntentSuccessEvent;
public static event Action<string> getPurchaseIntentErrorEvent;
  
public void PurchaseFlowListener (string callback)
{
    string data = findStringAfterCBType (callback, CBType.Success);
  
    if (data.Length > 0) {
        try {
            PurchaseResponse purchaseRes = JsonUtility.FromJson<PurchaseResponse> (data);
            PurchaseData purchaseData = JsonUtility.FromJson<PurchaseData>  (purchaseRes.purchaseData);
            getPurchaseIntentSuccessEvent (purchaseData);
        } catch (System.Exception ex) {
            Debug.Log ("PurchaseFlowListener Exception " + ex.Message);
        }
    } else {
        // because only the onError is followed by the additional data IapResult, pass the additional data only. Since no additional data exists in the rest of the errors, pass the callback as it is.
        string errorData = findStringAfterCBType (callback, CBType.Error);
        if (errorData.Length > 0) {
            getPurchaseIntentErrorEvent (errorData);
        } else {
            getPurchaseIntentErrorEvent (callback);
        }
    }
}

// Onestore_IapResultListener.cs (C#)
void getPurchaseIntentSuccessResult (PurchaseData result)
{
    AndroidNative.showMessage ("getPurchaseIntent sucess",  result.ToString (), "ok");
    PlayerPrefs.SetString (result.productId, JsonUtility.ToJson  (result));
}
  
void getPurchaseIntentErrorResult (string result)
{
    AndroidNative.showMessage ("getPurchaseIntent error", result, "ok");
}

Consume In-App Products

The calling direction is from top to bottom and Unity will call Android. To perform the 'consume', the JSON data on the purchase in-app product that is saved through getPurchase or buyItem is required. And to import the JSON data, it is required to import the in-app ID as a key value by using the PlayerPrefs and deliver it to Android. Eventually, Android uses Json by converting it into the PurchaseData type.

// Onestore_IapUi.cs (C#)
public void consume ()
{
    string inapp_json = PlayerPrefs.GetString  (inapp_pId1);
  
    if (inapp_json.Length > 0) {
        IapCallManager.consume (inapp_json);
    } else {
        AndroidNative.showMessage ("error", "no  data to consume", "ok");
    }
}

// Onestore_IapUi.cs (C#)
public void consume ()
{
    string inapp_json = PlayerPrefs.GetString  (inapp_pId1);
  
    if (inapp_json.Length > 0) {
        IapCallManager.consume (inapp_json);
    } else {
        AndroidNative.showMessage ("error", "no  data to consume", "ok");
    }
}

// ONE store Unity Plugin (Java)
public void consumeItem(int apiVersion, final String data) {
    if (mPurchaseClient == null) {
        Log.d(TAG, "PurchaseClient is not initialized");
        return;
    }
    mPurchaseClient.consumeAsync(apiVersion, JsonUtil.makeJsonToPurchaseData(data), mConsumeListener);
}

This is a sequence where Android calls Unity through the connection listener. The string types of the result value received from the callback are as follows.

  • onSuccess: a success response to the 'consume' request.

  • onErrorRemoteException

  • onErrorSecurityException

  • onErrorNeedUpdateException

  • onError: if failed, an error code will be passed to the application. A code composed of numbers, and descriptions in the string form are passed in the form of ToString.

// ONE store Unity Plugin (Java)
PurchaseClient.ConsumeListener mConsumeListener = new PurchaseClient.ConsumeListener() {
    @Override
    public void onSuccess(PurchaseData purchaseData) {
        unitySendMessage(gameObject, "ConsumeListener", "onSuccess" + JsonUtil.makePurchaseDataToJson(purchaseData));
    }
  
    @Override
    public void onErrorRemoteException() {
        unitySendMessage(gameObject, "ConsumeListener", "onErrorRemoteException");
    }
  
    @Override
    public void onErrorSecurityException() {
        unitySendMessage(gameObject, "ConsumeListener", "onErrorSecurityException");
    }
  
    @Override
    public void onErrorNeedUpdateException() {
        unitySendMessage(gameObject, "ConsumeListener", "onErrorNeedUpdateException");
    }
  
    @Override
    public void onError(IapResult result) {
        unitySendMessage(gameObject, "ConsumeListener", "onError" + result.toString());
    }
};

// Onestore_IapCallbackManager.cs (C#)
public static event Action<PurchaseData> consumeSuccessEvent;
public static event Action<string> consumeErrorEvent;
  
public void ConsumeListener (string callback)
{
    string data = findStringAfterCBType (callback, CBType.Success);
    if (data.Length > 0) {
        PurchaseData purchaseData = JsonUtility.FromJson<PurchaseData> (data);
        consumeSuccessEvent (purchaseData);
    } else {
        string errorData = findStringAfterCBType (callback, CBType.Error);
        if (errorData.Length > 0) {
            consumeErrorEvent (errorData);
        } else {
            consumeErrorEvent (callback);
        }
    }
}

// Onestore_IapResultListener.cs (C#)
void consumeSuccessResult (PurchaseData  result)
{
    AndroidNative.showMessage ("consume sucess", result.ToString (), "ok");
    PlayerPrefs.SetString (result.productId, "");
}
  
void consumeErrorResult (string result)
{
    AndroidNative.showMessage ("consume error", result.ToString (), "ok");
}

Change Subscription Status (Reservation of Cancellation / Cancellation of the Reservation of the Cancellation)

The calling direction is from top to bottom and Unity will call Android. When it come to the subscription(auto) which is the automatic payment in-app product, if recurringState is 0, that means the automatic payment has already made. You can send the 'cancel' command by sending the 'cancel' parameter which cancels the automatic payment. If recurringState is 1, that means the automatic payment has been cancelled. You can make the automatic payment again by sending the 'reactivate' parameter. In the IapUi.cs example, the 'cancel/reactivate' operation has been implemented as a sample to operate alternately on/off.

// Onestore_IapUi.cs (C#)
public void cancelAutoItem ()
{
    string auto_json = PlayerPrefs.GetString (auto_pId1);
    PurchaseData response = JsonUtility.FromJson<PurchaseData> (auto_json);
  
    if (auto_json.Length > 0) {
        string command = "";
        if (response.recurringState == 0) {
            command = "cancel";
        } else if (response.recurringState == 1) {
            command = "reactivate";
        }
        IapCallManager.manageRecurringAuto (auto_json, command);
    } else {
        AndroidNative.showMessage ("Warning!!", "no data for manageRecurringAuto", "ok");
    }
}

// Onestore_IapCallMnager.cs (C#)
public static void manageRecurringAuto (string auto_json, string command)
{
    checkServiceAvailable ();
    iapRequestAdapter.Call("manageRecurringAuto", IAP_API_VERSION, auto_json, command);
}

// ONE store Unity Plugin (Java)
// 월정액 상품(auto)의 상태변경(해지예약 / 해지예약 취소)을 진행합니다.
public void manageRecurringAuto(int apiVersion, final String data, final String action) {
    if (mPurchaseClient == null) {
        Log.d(TAG, "PurchaseClient is not initialized");
        return;
    }
      
    mPurchaseClient.manageRecurringProductAsync(apiVersion, JsonUtil.makeJsonToPurchaseData(data), action, mManageRecurringProductListener);
}

This is a sequence where Android calls Unity through the connection listener. The string types of the result value received from the callback are as follows.

  • onSuccess: a success response to the 'change the subscription status' calling

  • onErrorRemoteException

  • onErrorSecurityException

  • onErrorNeedUpdateException

  • onError: if failed, an error code will be passed to the application. A code composed of numbers, and descriptions in the string form are passed in the form of ToString.


// ONE store Unity Plugin (Java)
PurchaseClient.ManageRecurringProductListener mManageRecurringProductListener = new PurchaseClient.ManageRecurringProductListener() {
    @Override
    public void onSuccess(PurchaseData purchaseData, String manageAction) {
        unitySendMessage(gameObject, "ManageRecurringProductListener", "onSuccess" + JsonUtil.makePurchaseDataToJson(purchaseData));
    }
  
    @Override
    public void onErrorRemoteException() {
        unitySendMessage(gameObject, "ManageRecurringProductListener", "onErrorRemoteException");
    }
  
    @Override
    public void onErrorSecurityException() {
        unitySendMessage(gameObject, "ManageRecurringProductListener", "onErrorSecurityException");
    }
  
    @Override
    public void onErrorNeedUpdateException() {
        unitySendMessage(gameObject, "ManageRecurringProductListener", "onErrorNeedUpdateException");
    }
  
    @Override
    public void onError(IapResult result) {
        unitySendMessage(gameObject, "ManageRecurringProductListener", "onError" + result.toString());
    }
};


// Onestore_IapCallbackManager.cs (C#)
public static event Action<PurchaseData>manageRecurringSuccessEvent;
public static event Action<string> manageRecurringErrorEvent;
  
public void ManageRecurringProductListener (string callback)
{
    string data = findStringAfterCBType (callback, CBType.Success);
    if (data.Length > 0) {
        PurchaseData response =  JsonUtility.FromJson<PurchaseData> (data);
        manageRecurringSuccessEvent (response);
    } else {
        string errorData = findStringAfterCBType (callback,  CBType.Error);
        if (errorData.Length > 0) {
            manageRecurringErrorEvent (errorData);
        } else {
            manageRecurringErrorEvent (callback);
        }
    }
}

// IapResultListener.cs (C#)
void manageRecurringSuccessResult (PurchaseData result)
{
    AndroidNative.showMessage ("ManageRecurring sucess", result.ToString (), "ok");
    PlayerPrefs.SetString (result.productId, JsonUtility.ToJson (result));
}
  
void manageRecurringErrorResult (string result)
{
    AndroidNative.showMessage ("ManageRecurring error", result.ToString (), "ok");
}

Request ONE store Login

The calling direction is from top to bottom and Unity will call Android. The login check is performed by default before the payment is made. Use this button when login-related errors occur while each command is being executed.

// Onestore_IapUi.cs (C#)
public void login ()
{
    IapCallManager.login ();
}

// Onestore_IapCallMnager.cs (C#)
public static void login ()
{
    checkServiceAvailable ();
    iapRequestAdapter.Call ("launchLoginFlow", IAP_API_VERSION);
}

// ONE store Unity Plugin (Java)
public void launchLoginFlow(int apiVersion) {
    if (mPurchaseClient == null) {
        Log.d(TAG, "PurchaseClient is not initialized");
        return;
    }
  
    Intent i = new Intent(getActivity(), IapUnityPluginActivity.class);
    i.putExtra("apiVersion", apiVersion);
    getActivity().startActivity(i);
}

After calling mPurchaseClient.launchLoginFlow, receive the result value of the completed purchase through the onActivityResult. Parse the intent data delivered when the login is completed in the mPurchaseClient.handleLoginData and then pass success or failure to the end listener.

// ONE store Unity Plugin (Java) : IapUnityPluginActivity
public class IapUnityPluginActivity extends Activity {
  
    private static final int LOGIN_REQUEST_CODE = 1001;
    private static final int PURCHASE_REQUEST_CODE = 2001;
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent i = getIntent();
        final String productId = i.getStringExtra("productId");
        final String productType = i.getStringExtra("productType");
        final String payload = i.getStringExtra("payload");
        final int apiVersion = i.getIntExtra("apiVersion", 5);
  
        final String gameUserId = i.getStringExtra("gameUserId");
        final boolean promotionApplicable = i.getBooleanExtra("promotionApplicable", false);
  
        IapPlugin.INSTANCE.mPurchaseClient.launchLoginFlow(
                apiVersion,
                IapUnityPluginActivity.this,
                LOGIN_REQUEST_CODE,
                IapPlugin.INSTANCE.mLoginFlowListener);
    }
}

// ONE store Unity Plugin (Java) : IapUnityPluginActivity
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    Log.d("IapUnityPluginActivity", "onActivityResult requestCode " + requestCode);
  
    switch (requestCode) {
        case LOGIN_REQUEST_CODE:
            /*
             * The intent data which is received upon calling the launchLoginFlow API is parsing the response value through the handleLoginData.
             * After the parsing, the response result is passed through the LoginFlowListener which was delivered upon calling the launchLoginFlow.
             */
            if (resultCode == RESULT_OK) {
                if (IapPlugin.INSTANCE.mPurchaseClient.handleLoginData(data) == false) {
                    // listener is null
                }
            } else {
                // user canceled , do nothing..
            }
            break;
    }
}

This is a sequence where Android calls Unity through the connection listener. Only one Action event is configured since it consists only of success and error strings. The string types of the result value received from the callback are as follows.

  • onSuccess: a success response to the login success.

  • onErrorRemoteException

  • onErrorSecurityException

  • onErrorNeedUpdateException

  • onError: if failed, an error code will be passed to the application. A code composed of numbers, and descriptions in the string form are passed in the form of ToString.

// ONE store Unity Plugin (Java)
PurchaseClient.LoginFlowListener mLoginFlowListener = new PurchaseClient.LoginFlowListener() {
    @Override
    public void onSuccess() {
        unitySendMessage(gameObject, "LoginFlowListener", "onSuccess");
    }
  
    @Override
    public void onErrorRemoteException() {
        unitySendMessage(gameObject, "LoginFlowListener", "onErrorRemoteException");
    }
  
    @Override
    public void onErrorSecurityException() {
        unitySendMessage(gameObject, "LoginFlowListener", "onErrorSecurityException");
    }
  
    @Override
    public void onErrorNeedUpdateException() {
        unitySendMessage(gameObject, "LoginFlowListener", "onErrorNeedUpdateException");
    }
  
    @Override
    public void onError(IapResult result) {
        unitySendMessage(gameObject, "LoginFlowListener", "onErrorNeedUpdateException" + result.toString());
    }
};

// Onestore_IapCallbackManager.cs (C#)
public static event Action<string> getLoginIntentEvent;
  
public void LoginFlowListener (string callback)
{
    getLoginIntentEvent (callback);//just pass
}

// Onestore_IapResultListener.cs (C#)
void getLoginIntentEvent (string result)
{
    AndroidNative.showMessage ("getLoginIntent ", result.ToString (), "ok");
}

Unbind Payment Operation

The calling direction is from top to bottom and Unity will call Android. Unbind the AIDL service binding.

// Onestore_IapUi.cs (C#)
public void destroy ()
{
    IapCallManager.destroy ();
}

// Onestore_IapCallMnager.cs (C#)
public static void destroy ()
{
    iapRequestAdapter.Call ("dispose");
    isServiceCreated = false;
}

// ONE store Unity Plugin (Java)
public void dispose() {
    if (this.mPurchaseClient != null) {
        mPurchaseClient.terminate();
    }
    this.mPurchaseClient = null;
}
  
public void terminate() {
    if (mContext != null && mServiceConnection != null) {
        try {
            mContext.unbindService(mServiceConnection);
        } catch (Exception e) {
            Log.d("PurchaseClient", e.toString());
        }
  
        mContext = null;
        mServiceConnection = null;
        mInAppPurchaseService = null;
    }
}

ONE store Unity Plugin Package / Download Plugin

Move to the download page(Github)

  • UnityPackage (IapV17_UnityPluginSample.unityPackage)

  • plugin AAR

  • If Unity imports IapV17_UnityPluginSample.unityPackage, all related Assets will be automatically imported.

  • Unity Sample V17 Project & Sample APK

Last updated