Using ONE store IAP (SDK V21) from Unity

Overview

The ONE store purchase plugin extends assets to the Unity environment to provide the latest features of the ONE store purchase library in games. This guide explains how to set up the project to use the plugin and how to implement features of the ONE store purchase library.

Development Version

Unity

2020.3.48f1

Java SDK (Java 11)

Purchase: v21.01.00

App License Checker: v2.1.0

Setting up ONE store Purchase Plugin

Downloading and Importing Plugin

  1. Download latest version of the ONE store In-app purchase plugin for Unity from the release page on GitHub.

  2. In the Unity menu bar, click Assets > Import Package > Custom Package.

  3. Navigate to the location of download and select the .unitypackage file.

  4. In the Import Unity Package dialog, keep all assets selected and click Import.

After importing the package, a folder will be added. This folder contains the ONE store purchase library.

  • Assets/OneStoreCorpPlugins

    • /Common

    • /Authentication

    • /Purchase

    • /AppLicenseChecker

EDM4U (External Dependency Manager for Unity) is distributed alongside it. If you are already using it, you can uncheck ExternalDependencyManager during the Import Package step and apply it.

Including External Dependencies

To include repositories and dependencies in your project, follow these steps:

Project Settings > Player > Publishing Settings > Build

Check the following two options:

  • Custom Main Manifest

  • Custom Main Gradle Template

Select Assets > External Dependency Manager > Android Resolver > Force Resolve.

You can see the following applied in the mainTemplete.gradle file:

  • In-app Purchase dependencies are listed here: Assets/OneStoreCorpPlugins/Purchase/Editor/PurchaseDependencies.xml

  • App License Checker dependencies are listed here: Assets/OneStoreCorpPlugins/AppLicenseChecker/Editor/AppLicenseCheckerDependencies.xml

<queries> Setting

You must set the <queries> in your AndroidManifest.xml file. For details, refer to the notice.

If you don’t set the <queries> tag, the SDK won’t be able to find the ONE store service.

<manifest>
    <!-- 
        if your binary use ONE store's In-app SDK,
        Please make sure to declare the following query on Androidmanifest.xml. 
        Refer to the notice for more information.
        https://dev.onestore.net/devpoc/support/news/noticeView.omp?noticeId=32968
     -->
    <queries>
        <intent>
            <action android:name="com.onestore.ipc.iap.IapService.ACTION" />
        </intent>
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <data android:scheme="onestore" />
        </intent>
    </queries>
    ...
    <application>
        ...
    </application>
</manifest>

Test Option Settings for Global Store Distribution

This option forces the SDK to integrate with the global store. This feature has been implemented from SDK v21.01.00.

<manifest>
    ...
    <application>
        <activity>
            ...
        </activity>
        ...
        
        <!-- Options for in-app testing on your global store -->
        <meta-data android:name="onestore:dev_option" android:value="global" />
    </application>
</manifest>

In the distribution build version, this option must be removed.

Implementing the ONE store In-app Purchase Library in Games

Setting the Log Level

During development, you can set the log level to expose the flow of data in the SDK in more detail. It operates based on values defined in android.util.Log.

using OneStore.Common;
/// <summary>
/// Warning! This code must be deleted during release build as it may be vulnerable to security.
/// Use <seealso cref="OneStoreLogger.SetLogLevel(int)"/> only for development.
/// </summary>
OneStoreLogger.SetLogLevel(2);
Constants Value Value

VERBOSE

2

DEBUG

3

INFO (default)

4

WARN

5

ERROR

6

For the distribution build version, remove this option as it may be vulnerable.

Initializing ONE store In-app Purchase

To request in-app purchases, initialize the PurchaseClientImpl object using the license key provided by the ONE store developer center. Call the Initialize() function to complete basic setup. This is a prerequisite for connecting to the ONE store service.

using OneStore.Purchasing;

class YourCallback: IPurchaseCallback
{
    // IPurchaseCallback implementations.
}

var purchaseClient = new PurchaseClientImpl("your license key");
purchaseClient.Initialize(new YourCallback());

Searching for Detailed Product Information

After initializing the PurchaseClientImpl object, request product information through QueryProductDetails() and receive a response at PurchaseCallback.OnProductDetailsSucceeded().

ProductTypeis as follows:

ProductEnum

Managed Product

ProductType.INAPP

Subscription Product

ProductType.SUBS

Monthly Subscription Product

ProductType.AUTO (This product will not be supported in the future.)

To search for all types of data at once, set ProductType.ALL.

ProductType.ALL can only be used for searching detailed product information, not for initiating purchase requests or retrieving purchase history.

using OneStore.Purchasing;
  
List items = ...
purchaseClient.QueryProductDetails(items.AsReadOnly(), [ProductType.ALL | ProductType.INAPP | ProductType.SUBS]);

// IPurchaseCallback implementations
public void OnProductDetailsSucceeded(List productDetails)
{
    ...
}

public void OnProductDetailsFailed(IapResult iapResult)
{
    ...
}

The list of in-app product IDs should be managed by the developer's secure backend server.

Initiating Purchase Request

To make a purchase request in the app, call the Purchase() function on the main thread.

Create a PurchaseFlowParams object based on the values of the ProductDetail object obtained by calling the QueryProductDetails() API. Use the PurchaseFlowParams.Builder class to create a PurchaseFlowParams object.

SetDeveloperPayload() is an optional field with a maximum of 200 bytes, set by the developer. It can be used after payment to verify data integrity, and for additional data. SetProductName() is used to change and display the product name at the time of payment. SetQuantity() applies only to managed in-app products and is used when purchasing multiple units of a product.

ONE store offers various reward promotions to users such as discount coupons, cashback, etc. Developers can restrict or allow user participation in promotions using gameUserId and promotionApplicable parameters during a purchase request. Developers pass their app's unique user identification number and the choice of promotion participation, and ONE store applies the user's promotional benefits based on these values.

gameUserId and promotionApplicable parameters are optional and should only be used after prior consultation with the ONE store business department. In general cases, these values are not sent. Even when sending values after consultation, the gameUserId should be sent as a hashed unique value for privacy reasons.

  using OneStore.Purchasing;
  
  ProductDetail productDetail = ...
  ProductType productType = ProductType.Get(productDetail.type);

  var purchaseFlowParams = new PurchaseFlowParams.Builder()
          .SetProductId(productId)                // mandatory
          .SetProductType(productType)            // mandatory
          .SetDeveloperPayload(developerPayload)  // optional
          .SetQuantity(quantity)                  // optional
          // .SetProductName(null)                // optional: Change the name of the product to appear on the purchase screen.
          
          // It should be used only in advance consultation with the person in charge of the One Store business, and is not normally used.
          // .SetGameUserId(null)                 // optional: User ID to use for promotion.
          // .SetPromotionApplicable(false)       // optional: Whether to participate in the promotion.
          .Build(); 

  purchaseClient.Purchase(purchaseFlowParams);

A successful call to Purchase() displays the following screen: [Figure 1] represents the managed product purchase screen.

When a purchase is successful, the result is sent to the IPurchaseCallback.OnPurchaseSucceeded() function. In case of failure, the IPurchaseCallback.OnPurchaseFailed() function is called.

  using OneStore.Purchasing;
  
  // IPurchaseCallback implementations
  public void OnPurchaseSucceeded(List purchases)
  {
      handlePurchase(purchases);
  }

  public void OnPurchaseFailed(IapResult iapResult)
  {
      ...
  }

When a purchase is successful, a purchase token, a unique identifier indicating the user and product ID, is also generated. While the purchase token can be stored within the app, it is safer to pass it to a backend server that can authenticate the purchase and protect against fraud.

The purchase token for managed and subscription products is issued each time a payment occurs. For monthly subscription products, the purchase token remains the same while the automatic payment is renewed.)

Additionally, users receive a transaction receipt via email that includes the receipt number. For managed products, an email is received each time a purchase is made, and for monthly subscription and subscription products, an email is received when first purchased and thereafter when renewed.

Subscription

Subscriptions renew automatically until canceled. Subscriptions can have the following statuses:

  • Active: User is in good standing and can access their subscription.

  • Schedule Pause: Users can select when to pause subscriptions during use.

    • Weekly Subscription: Can be paused at week 1 to 3.

    • Monthly Subscription: Can be paused at month 1 to 3.

    • Annual Subscription: Does not support pauses.

  • Scheduled Cancellation: Users can select when to cancel their subscription during use. Payment will not be made on the next payment date.

  • Grace & Hold: If the user encounters payment issues, the payment will not be made on the next payment date. Immediate "Subscription Cancellation" is possible without scheduling a cancellation.

Allowing users to upgrade, downgrade, or change their subscriptions.

To upgrade or downgrade subscriptions, you can set a proration mode at time of purchase or set how changes affect subscription users. The following table shows the available proration mode (OnestoreProrationMode).

Proration Mode

Description

IMMEDIATE_WITH_TIME_PRORATION

Subscription replacement occurs immediately, and remaining time is adjusted based on the price difference, either credited or charged. (This is default behavior.)

IMMEDIATE_AND_CHARGE_PRORATED_PRICE

Subscription replacement occurs immediately, and billing cycle remains the same. The price for the remaining period is charged. (This option is only available for upgrades.)

IMMEDIATE_WITHOUT_PRORATION

Subscription replacement occurs immediately, and a new price is charged on the next payment date. The billing cycle remains the same.

DEFERRED

Replacement applies when existing plan expires, and the new fee is charged simultaneously.

Upgrade or Downgrade

Subscription can offer users upgrades or downgrades using the same API as when initiating purchase request. However, to apply an upgrade or downgrade to a subscription, the existing subscription purchase token and prorated mode value are required. As in the following example, you must provide information about the current subscription, future (upgrade or downgrade) subscription, and proration mode.

using OneStore.Purchasing;
  
public void UpdateSubscription(ProductDetail productDetail, PurchaseData oldPurchase, OneStoreProrationMode mode, string developerPayload)
{
    ProductType productType = ProductType.Get(productDetail.type);
  
    var purchaseFlowParams = new PurchaseFlowParams.Builder()
             .SetProductId(productId)                        // mandatory
             .SetProductType(productType)                    // mandatory
             .SetOldPurchaseToken(oldPurchase.PurchaseToken) // mandatory
             .SetProrationMode(mode)                         // mandatory
  
             .SetDeveloperPayload(developerPayload)          // optional
             // .SetProductName(null)                        // optional: Change the name of the product to appear on the purchase screen.
             .Build(); 

    purchaseClient.UpdateSubscription(purchaseFlowParams);
}

In cases of upgrades or downgrades, the response is received in the same way as initiating purchase request logic. Responses can also be received from retrieving purchase history. Even when subscription is purchased in proration mode, post-purchase processing is needed using PurchaseClient.acknowledgeAsync() just like any other regular purchases.

Post-Purchase Processing

Once a user completes a purchase, the app needs to process it. In most cases, the app receives purchase notifications through OnPurchaseSucceeded(). Or as described in retrieving purchase history, the app may process it by calling the PurchaseClientImpl.QueryPurchases() function.

Consuming Purchase

A managed product cannot be repurchased until consumed.

To consume a product, call ConsumePurchase(). The result of the consumption operation is then handled through the IPurchaseCallback.OnConsumeSucceeded() call.

If a managed product is not consumed it can be used as a permanent product type, and if consumed immediately after purchase it can be used as a consumable product. Additionally, if consumed after a certain period, it can be used as a limited-time product.

using OneStore.Purchasing;

public void handlePurchase(PurchaseData purchaseData)
{
    purchaseClient.ConsumePurchase(purchaseData);
}

// IPurchaseCallback implementations
public void OnConsumeSucceeded(PurchaseData purchase)
{
    ...
}

public void OnConsumeFailed(IapResult iapResult)
{
    ...
}

Since consumption requests can sometimes fail, it is necessary to check with a secure backend server to ensure that each purchase token has not been used. This prevents the app from granting multiple entitlements for the same purchase. Alternatively, you can wait until a successful consumption response is received before granting entitlements.

If a purchase is not acknowledged or consumed within 3 days, it will be determined that the user hasn't received the product and an automatic refund will be issued.

Purchase Acknowledgement

To authenticate non-consumable products, use the PurchaseClientImpl.AcknowledgePurchase() function. This can be used for managed products, monthly subscription products, and subscription products.

Use the PurchaseData.Acknowledged() function to determine if the product has been authenticated. Additionally, if the authentication operation is successful, the IPurchaseCallback.OnAcknowledgeSucceeded() function is called.

using OneStore.Purchasing;

public void handlePurchase(PurchaseData purchaseData)
{
    if (!purchaseData.Acknowledged())
    {
         purchaseClient.AcknowledgePurchase(purchaseData);
    }
}

// IPurchaseCallback implementations
public void OnAcknowledgeSucceeded(PurchaseData purchase)
{
    ...
}

public void OnAcknowledgeFailed(IapResult iapResult)
{
    ...
}

Retrieving Purchase History

Processing a purchase does not guarantee that the app has handled all purchases. There are several scenarios where the app might fail to recognize or track a purchase:

  • Network Issues: If the network connection is lost before device receives the purchase notification after a user successfully makes a purchase and is confirmed by ONE store.

  • Multiple Devices: Users expect an item purchased on one device to be available when they switch to another device.

To handle these situations, call PurchaseClientImpl.QueryPurchases() in the app's Start() or OnApplicationPause() to verify that all purchases have been successfully processed, as described in post-purchase processing.

using OneStore.Purchasing;
  
// IPurchaseCallback implementations
public void OnPurchaseSucceeded(List purchases)
{
    handlePurchase(purchases);
}

public void OnPurchaseFailed(IapResult iapResult)
{
    ...
}

Changing Monthly Subscription Product Status (Deprecated)

A monthly subscription product renews every 30 days after the initial purchase. The status of a monthly subscription product can be checked through PurchaseData.RecurringState().

To change the status of a monthly subscription product, use PurchaseClientImpl.ManageRecurringProduct(). Enter the purchase data and the desired RecurringAction value.

using OneStore.Purchasing;
  
RecurringAction recurringAction = purchaseData.RecurringState == 0 ? RecurringAction.CANCEL : RecurringAction.REACTIVATE;
purchaseClient.ManageRecurringProduct(purchaseData, recurringAction); 

// IPurchaseCallback implementations
public void OnManageRecurringProduct(IapResult iapResult, PurchaseData purchase, RecurringAction action)
{
     if (iapResult.IsSuccessful())
    {
        ...
    }
    else
    {   
        ...
    }
}

Opening the Subscription Management Screen

You can display a screen to manage the status of subscription products. Inserting PurchaseData as a parameter checks the purchase data and executes the management screen for that subscription product. However, inserting null will launch the user's subscription list screen. The following is an example of how to display the subscription management screen.

using OneStore.Purchasing;
  
PurchaseData purchaseData = ...;
purchaseClient.LaunchManageSubscription(purchaseData); 

Obtaining Market Distinction Code

From SDK v19 onward, a market distinction code is needed to use the S2S API. When initializing the PurchaseClientImpl object, the SDK attempts to connect to the payment module. Upon successful connection, the storeCode is automatically obtained and assigned to the PurchaseClientImpl.storeCode variable.

using OneStore.Purchasing;
  
// Possible after calling the OneStoreImpl.RetrieveProducts()
var storeCode = oneStoreImpl.StoreCode;

Installing ONE store Service

In-app payments cannot be used if the version of ONE store service is outdated or is not installed. The first API call initially attempts to connect to the ONE store service. If RESULT_NEED_UPDATE occurs, you must call the LaunchUpdateOrInstallFlow() method.

using OneStore.Purchasing;

purchaseClient.LaunchUpdateOrInstallFlow(); 

Requesting ONE store Login

The ONE store in-app SDK requires the user to be logged into ONE store. Internally, it first attempts to log in using the login token. If it fails or in cases like the initial login where a user's information is not available, a foreground login screen is displayed to prompt the user to log in.

using OneStore.Auth;
  
new OneStoreAuthClientImpl().LaunchSignInFlow({signInResult} => {
    if (signInResult.IsSuccessful())
    {
        ...
    }
});

Last updated