Apply ONE store IAP

Apply ONE store IAP

This document describes how to add ONE store IAP to app by using ONE store IAP SDK.

IAP SDK provides the PurchaseClient class for more convenient and intuitive use.

For the way to add the SDK to development project, refer to the following steps.

Add ONE store IAP SDK library

  • Download the In-App purchase SDK library file, and then export this file into the ‘libs’ folder within the developer’s app project.

  • Export the global-appstores.json file into the ‘assets’ folder.

  • Add the following line to the dependency section of the app's build.gradle file.

dependencies {
...
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
}

Set ONE store billing screen

ONE store billing screen provides both types of full (vertically fixed) and pop-up (vertical & horizontal) screens.

If you want the pop-up billing screen, add meta-data and set the “popup” value in "iap:view_option".

If no value is set, it is equivalent to setting the full screen (android:value="full") by default.

<meta-data
    android:name="iap:view_option"
    android:value="full" >
    
<meta-data
    android:name="iap:view_option"
    android:value="popup" >

Initialize & connect ONE store IAP

To request billing to ONE store, you first need to connect to ONE store Service (OSS) with the steps below.

  1. Generate the PurchaseClient instance by calling PurchaseClient.newBuilder(). Enter the current Activity’s Context information when generating PurchaseClient. In addition, you need to register PurchaseUpdatedListener in setListener() to receive the value for the purchase. setBase64PublicKey() is an optional value, however it is recommended to use. When public key is entered, this optional value performs signature verification in the SDK to confirm whether the purchase data is forged or falsified.

  2. Connect to OSS through startConnection. You must implement PurchaseClientStateListener to be able to receive the response when the client setup is completed and when you are ready to make additional requests.

  3. When the client connection is cut, onServiceDisconnected() will be called up. Re-define and implement the response method to be able to connect to the disconnected OSS and to make it try the connection again on its own. For instance, the connection to PurchaseClient might be cut after having been left in the background for a long time. If this happens, you need to connect again by calling up the PurchaseClient.startConnection() method before making additional requests.

The following example shows how to start the connection and perform the test to see if it is ready to use.

If the connection between the app and OSS is disconnected, it is better to redefine the onServiceDisconnectied() method to allow the method to try the connection again on its own.

All the methods of PurchaseClient must be executed while the connection is maintained.

Kotlin

private lateinit var mPurchaseClient: PurchaseClient
...
mPurchaseClient = PurchaseClient.newBuilder(activity)
                       .setListener(this)
                       .setBase64PublicKey(/*your public key*/) // optional
                       .build()
mPurchaseClient.startConnection(object : PurchaseClientStateListener {
    override fun onSetupFinished(iapResult: IapResult) {
        if (iapResult.isSuccess) {
            // The PurchaseClient is ready. You can query purchases here.
        } else if (iapResult.responseCode == ResponseCode.RESULT_NEED_LOGIN) {
            // The connection is completed successfully but login is required. You must write your login code here.
        } else if (iapResult.responseCode == ResponseCode.RESULT_NEED_UPDATE) {
            // You need the required version of the ONE store service in the SDK.
        } else {
            // Other error codes.
        }
    }
 
    override fun onServiceDisconnected() {
        // Try to restart the connection on the next request to
        // ONE store service by calling the startConnection() method.
    }
})

Java

private PurchaseClient mPurchaseClient;
...
mPurchaseClient = PurchaseClient.newBuilder(activity)
                       .setListener(this)
                       .setBase64PublicKey(/*your public key*/) // optional
                       .build();
mPurchaseClient.startConnection(new PurchaseClientStateListener() {
    @Override
    public void onSetupFinished(IapResult iapResult) {
        if (iapResult.isSuccess()) {
            // The PurchaseClient is ready. You can query purchases here.
        } else if (iapResult.getResponseCode() == ResponseCode.RESULT_NEED_LOGIN) {
            // The connection is completed successfully but login is required. You must write your login code here.
        } else if (iapResult.getResponseCode() == ResponseCode.RESULT_NEED_UPDATE) {
            // You need the required version of the ONE store service in the SDK.
        } else {
            // Other error codes.
        }
    }
 
    @Override
    public void onServiceDisconnected() {
        // Try to restart the connection on the next request to
        // ONE store service by calling the startConnection() method.
    }
});

And when Activity ends, you must enter the code to get PurchaseClient terminated at the onDestory() method. If the bound service is not removed, then the generated connection will continue, and thereby might cause problems in the app performance.

Kotlin

override fun onDestory() {
    super.onDestory()
    mPurchaseClient.endConnection()
}

Java

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mPurchaseClient != null) {
        mPurchaseClient.endConnection();
        mPurchaseClient = null;
    }
}

Check in-app product details

In-app ID issued when registering the in-app will be used to search the in-app product details. You can search the in-app product details by using queryProductDetailsAsync().

When calling this method, you must generate the instance of ProductDetailsParams and specify the in-app IDs in the form of ArrayList in setProductIdList().

Furthermore, you must input the in-app type to check the in-app product details.

There are two types of in-app: Managed product (ProductType.INAPP) and Monthly auto-renewal product (ProductType.AUTO). Enter ProductType.ALL to check both types of in-app.

To check the in-app product details, the app uses the in-app ID, which has been defined when the in-app is registered on ONE store Developer Center. For details, refer to Developer Center.

You must implement the ProductDetailsListener interface to check the in-app product details.

If the in-app product details are successfully received, IapResult.getResponseCode() becomes ResponseCode.RESULT_OK and sends the response in the ProductDetail list format.

The following example shows how to check the in-app product details and receive the response.

Kotlin

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mPurchaseClient != null) {
        mPurchaseClient.endConnection();
        mPurchaseClient = null;
    }
}

Java

List<String> products = new ArrayList<>();
products.add("p5000");
products.add("p50000");
 
ProductDetailsParams params = ProductDetailsParams.newBuilder()
        .setProductIdList(products).setProductType(ProductType.INAPP).build();
 
mPurchaseClient.queryProductDetailsAsync(params, new ProductDetailsListener() {
    @Override
    public void onProductDetailsResponse(IapResult iapResult, List<ProductDetail> productDetailList) {
        // Process the result.
    }
});

p50000, the in-app ID in the example, indicates non-consumable in-app in this document. To configure the non-consumable in-app, you must not consume the in-app but acknowledge the purchase only. The in-app ID of p5000 indicates consumable in-app in this document. The consumable in-app must be process as ‘consumed’ to be repurchased.

The app must manage the list of the in-app IDs through its own security back-end server.

You can search the response code and check the relevant error code by using IapResult.getResponseCode().

Request purchase

To request purchase in the app, call the launchPurchaseFlow() in UI thread.

During calls, enter the in-app ID (productId) and in-app type (ProductType.INAPP for Managed product, and ProductType.AUTO for Monthly auto-renewal product) in PurchaseFlowParams.

Also input develperPayload(up to 200byte) randomly entered by the developer. This value can be used to check data consistency and additional data after the billing is made.

ONE store is offering various benefit promotions for users, including discount coupons and cashback.

The developer can allow or limit the app user’s participation in the promotion by using the gameUserId, promotionApplicable parameters at the request of purchase.

If the developer sends the app’s unique user identification number and selects whether to participate in the promotion, ONE store will apply the user’s promotion benefits based on this value.

The gameUserId, promotionApplicable parameters are optional values and must be used after a prior consultation on the promotion with the person in charge of ONE store business department. In general cases, the values shall not be sent. In addition, if these values are sent after the prior consultation, gameUserId must be sent in a hashed unique value for protection of personal information.

The following example shows how to request the purchase of in-app.

Kotlin

// Retrieve a value for "productDetail" by calling queryProductDetailsAsync().
val params = PurchaseFlowParams.newBuilder()
        .setProductId(productId)    // productDetail.getProductId()
        .setProductName("")
        .setProductType(ProductType.INAPP)
        .setPayload(devPayload)
        .build()
mPurchaseClient.launchPurchaseFlow(activity, params)

Java

// Retrieve a value for "productDetail" by calling queryProductDetailsAsync().
PurchaseFlowParams params = PurchaseFlowParams.newBuilder()
        .setProductId(productId)    // productDetail.getProductId()
        .setProductName("")
        .setProductType(ProductType.INAPP)
        .setPayload(devPayload)
        .build();
 
mPurchaseClient.launchPurchaseFlow(activity, params);

If you call the launchPurchaseFlow() method, ONE store billing screen will be displayed.

Fig.1 shows ONE store in-app billing screen

Fig.1 ONE store in-app billing screen

If the billing is successful, the value will be transmitted through the PurchaseUpdatedListener interface, which has been implemented with setListener() when PurchaseClient is initialized.

The following example shows how to process the value transmitted as the response when the billing is successful.

Kotlin

override fun onPurchasesUpdated(iapResult: IapResult, purchaseData: List?) {
    if (iapResult.isSuccess && purchaseData != null) {
        for (purchase in purchaseData) {
            if (!isValidPayload(purchase.developerPayload)) {
                // invalid dev payload
                return
            }
            // A signature check is required to secure the purchased product.
            val validPurchase = AppSecurity.verifyPurchase(purchase.originalJson, purchase.signature)
            if (validPurchase) {
                if ("p50000".equals(purchase.productId)) {
                    // This state is managed by the app itself.
                } else {
                    // Grant entitlement to the user.
                    consumeItem(purchase)
                }
            } else {
                // Signature information is invalid.
            }
        }
    } else if (iapResult.responseCode == ResponseCode.RESULT_NEED_UPDATE) {
        // You need the required version of the ONE store service in the SDK.
    } else {
        // Other error codes.
    }
}

Java

@Override  
public void onPurchasesUpdated(IapResult iapResult, List purchaseData) {
    if (iapResult.isSuccess() && purchaseData != null) {
        for (PurchaseData p : purchaseData) {
            if (!isValidPayload(p.getDeveloperPayload())) {
                // invalid dev payload
                return;
            }
            // A signature check is required to secure the purchased product.
            boolean validPurchase = AppSecurity.verifyPurchase(p.getOriginalJson(), p.getSignature());
            if (validPurchase) {
                if ("p50000".equals(p.getProductId())) {
                    // This state is managed by the app itself.
                } else {
                    // Grant entitlement to the user.
                    consumeItem(p);
                }
            } else {
                // Signature information is invalid.
            }
        }
    } else if (iapResult.getResponseCode() == ResponseCode.RESULT_NEED_UPDATE) {
        // You need the required version of the ONE store service in the SDK.
    } else {
        // Other error codes.
    }
}

If the billing is successful, a purchase token will be generated. The purchase token is a unique identifier showing the purchase ID bought by the user. In the app, the purchase token can be stored in the local or security back-end server and can be used to check the purchase.

The purchase token for Managed product is unique for every purchase ID, however the purchase token for Monthly auto-renewal product remains the same while the in-app is being renewed.

The user also receives a transaction receipt containing a receipt number via email. As for Managed product, he/she will receive the email whenever purchasing this in-app. As for Monthly auto-renewal product, he/she will receive the email at the time of first purchase and every renewal afterwards.

Acknowledge purchase

If ONE store IAP library v6 (SDK V19) and above is used, purchase must be acknowledged within 3 days. Otherwise, it will be judged that the in-app has not been provided, and thereby the purchase amount will be refunded.

You can acknowledge the purchase by using one of the following methods.

  • If the in-app is consumable, use PurchaseClient.consumeAsync().

  • If the in-app is not consumable, use PurchaseClient.acknowledgeAsync().

As for Monthly auto-renewal product, you need to acknowledge the purchase only for the first billing.

The PurchaseData object includes the isAcknowledged() method, which shows if the purchase is acknowledged. If you use this method before acknowledging the purchase, you can figure out if the purchase has already been acknowledged.

The following example shows how to acknowledge the purchase of Monthly auto-renewal product.

Kotlin

val client: PurchaseClient = ...
val listener: AcknowledgeListener = ...
 
override fun onPurchasesUpdated(iapResult: IapResult, purchaseData: List?) {
    if (iapResult.isSuccess && purchaseData != null) {
        for (purchase in purchaseData) {
            ...
            if (!purchase.isAcknowledged) {
                val acknowledgeParams = AcknowledgeParams.newBuilder()
                    .setPurchaseData(purchase)
                    .build()
                client.acknowledgeAsync(acknowledgeParams, listener)
            }
            ...
        }
    }
    ...
}

Java

PurchaseClient client = ...
AcknowledgeListener listener = ...
 
@Override  
public void onPurchasesUpdated(IapResult iapResult, List purchaseData) {
    if (iapResult.isSuccess() && purchaseData != null) {
        for (PurchaseData purchase : purchaseData) {
            ...
            if (!purchase.isAcknowledged()) {
                AcknowledgeParams acknowledgeParams = AcknowledgeParams.newBuilder()
                    .setPurchaseData(purchase)
                    .build()
                client.acknowledgeAsync(acknowledgeParams, listener)
            }
            ...
        }
    }
    ...
}

Consume Managed product

Managed product cannot be re-purchased until the purchased in-app is consumed.

If Managed product is purchased but not consumed, then it can be used as non-consumable in-app. If Managed product is consumed immediately after the purchase, it can be used as consumable in-app. If it is consumed after a certain period, then it can be used as Monthly auto-renewal product.

Call up PurchaseClient.consumeAsync() to consume the in-app.

Generate the ConsumeParams object and enter PurchaseData received from ‘Check purchase history’ in it.

The following example shows how to consume Managed product.

Kotlin

// Enter the purchaseData received through onPurchasesUpdated() or onPurchasesResponse().
val params = ConsumeParams.newBuilder().setPurchaseData(purchaseData).build()
mPurchaseClient.consumeAsync(params) { iapResult, purchaseData ->
    if (iapResult.isSuccess) {
        // Process the result.
    } else if (iapResult.responseCode == ResponseCode.RESULT_NEED_UPDATE) {
        // You need the required version of the ONE store service in the SDK.
    } else {
        // Other error codes.
    }
}

Java

// Enter the purchaseData received through onPurchasesUpdated() or onPurchasesResponse().
ConsumeParams params  = ConsumeParams.newBuilder().setPurchaseData(purchaseData).build();
mPurchaseClient.consumeAsync(params, new ConsumeListener() {
    @Override
    public void onConsumeResponse(IapResult iapResult, PurchaseData purchaseData) {
        if (iapResult.isSuccess()) {
            // Process the result.
        } else if (iapResult.getResponseCode() == ResponseCode.RESULT_NEED_UPDATE) {
            // You need the required version of the ONE store service in the SDK.
        } else {
            // Other error codes.
        }
    }
});

Check purchase history

Get the information on unconsumed Managed product and Monthly auto-renewal product in use by using the PurchaseClient.queryPurchasesAsync() method.

If you entered the public key while initializing PurchaseClient, perform the signature verification within the SDK to check whether the purchase details data is forged or falsified. If the verification fails, the error code of IapResult (ResponseCode.ERROR_SIGNATURE_VERIFICATION) will be transmitted. If this error occurs, that indicates that the purchase result data is likely forged and falsified. In that case, you need to see if there have been any abusing attacks related to the purchase.

To allow the user to check the history of the purchase made in the app, call up queryPurchasesAsync() in PurchaseClient together with the in-app type as seen in the following example.

Kotlin

val client: PurchaseClient = ...
val listener: PurchasesListener = ...
 
client.queryPurchasesAsync(ProductType.INAPP, listener)

Java

PurchaseClient client = ...
PurchasesListener listener = ...
 
client.queryPurchasesAsync(ProductType.INAPP, listener)

You can check the in-app details including the purchase status or time by calling a range of methods in the PurchaseData object. For the type of in-app details that you can check, refer to the method list of the PurchaseData class.

If you call queryPurchaseAsync(), you can figure out the history of all the purchases and uses in the app that the user would have made when the app is not running.

If you call queryPurchaseAsync(), you can also figure out the user’s purchase history, which has been omitted from the app for some reasons even though the user purchased the in-app while the app was executing.

queryPurchaseAsync() must be called for the following cases.

  • Whenever the app starts, call upon queryPurchaseAsync() to restore the user’s history of all the purchases, which have been made since the app stopped for the last time.

  • Call upon queryPurchaseAsync() in the onResume() method because the user might purchase when the app is in the background.

Change Monthly auto-renewal product status

Monthly auto-renewal product is the in-app that will be automatically re-paid one month after its initial purchase to renew the use period.

By using the following method, you can change the Monthly auto-renewal product status even before the renewal.

  • You can check the status information through PurchaseData.getRecurringState() received from ‘Check purchase history’.

  • If you want to change the Monthly auto-renewal product status, call PurchaseClient.manageRecurringProductAsync()

The action value received from onRecurringResponse() transmits the currently-applied value. You can check it in PurchaseClient.RecurringAction. In addition, PurchaseData transmitted as the response result is the data received at the time of request, and therefore you need to receive once again the changed data through ‘Check purchase history’ and then you can check the changed RecurringState value.

Renewal cycle for Monthly auto-renewal product

The automatic payment for Monthly auto-renewal product shall be made at the same date as the purchased date. However, if the same date does not exist in the following month, the automatic payment shall be made at the last date of the following month.

  • If the first payment is made on January 31;

  • The renewal shall be made on February 28 or 29

  • The renewal shall be made on March 28 or 29

The following example shows how to change the Monthly auto-renewal product status.

Kotlin

// Enter the purchaseData received through onPurchasesUpdated() or onPurchasesResponse().
val params = RecurringProductParams.newBuilder()
                .setPurchaseData(purchaseData)
                .setRecurringAction(recurringAction)
                .build()
mPurchaseClient.manageRecurringProductAsync(params) { iapResult, purchaseData, action ->
    if (iapResult.isSuccess) {
        Log.d(TAG, "manageRecurringProductAsync onRecurringResponse, $action ${purchaseData?.toString()}")
         
        if (RecurringAction.CANCEL.equals(action, true)) {
            ...
        } else {
            ...
        }
 
    }
    ...
}

Java

// Enter the purchaseData received through onPurchasesUpdated() or onPurchasesResponse().
RecurringProductParams params = RecurringProductParams.newBuilder()
                                    .setPurchaseData(purchaseData)
                                    .setRecurringAction(recurringAction)
                                    .build();
mPurchaseClient.manageRecurringProductAsync(params, new RecurringProductListener() {
    @Override
    public void onRecurringResponse(IapResult iapResult, PurchaseData purchaseData, @RecurringAction String action) {
        if (iapResult.isSuccess()) {
            Log.d(TAG, "manageRecurringProductAsync onSuccess, " + action + " " + purchaseData.toString());
 
            if (RecurringAction.CANCEL.equalsIgnoreCase(action)) {
                ...
            } else {
                ...
            }
 
        ...
    }
});

Request ONE store login

ONE store IAP SDK works only while the user remains logged in to ONE store.

If there is the previous login history, ONE store will automatically log in. However, if ONE store login is needed, IapResult.getResponseCode() will be sent to RESULT_NEED_LOGIN from the response listeners of most APIs that the PurchaseClient instance requests.

When RESULT_NEED_LOGIN is sent, you can normally use the IAP SDK only after executing the method, which requests the login.

The following example shows how to request ONE store login.

Kotlin

mPurchaseClient.launchLoginFlowAsync(activity) { iapResult ->
    if (iapResult.isSuccess) {
        // You need to specify the scenario after successful login.
    }
    ...
}

Java

mPurchaseClient.launchLoginFlowAsync(activity, new IapResultListener() {
    @Override
    public void onResponse(IapResult iapResult) {
        if (iapResult.isSuccess()) {
            // You need to specify the scenario after successful login.
        }
        ...
    }
});

Install ONE store Service (OSS)

In-app purchase is not available if ONE store service (OSS) version is low or none.

You can check it in IapResult.getResponseCode() when connecting through PurchaseClient.startConnection().

If RESULT_NEED_UPDATE occurs, you must call upon the launchUpdateOrInstallFlow() method.

The following example shows how to install OSS.

Kotlin

mPurchaseClient.launchUpdateOrInstallFlow(activity) { iapResult ->
    if (iapResult.isSuccess) {
        // If the installation is completed successfully,
        // you should try to reconnect with the ONE store service.
        startConnection()
    } else {
        ...
    }
}

Java

mPurchaseClient.launchUpdateOrInstallFlow(activity, new IapResultListener() {
    @Override
    public void onResponse(IapResult iapResult) {
        if (iapResult.isSuccess()) {
        // If the installation is completed successfully,
        // you should try to reconnect with the ONE store service.
            startConnection();
        } else {
            ...
        }
    }
});

Obtain market identification code

For IAP library V6 and above, the market identification code is necessary to use Server to Server API.

You can obtain the market identification code through getStoreInfoAsync().

The following example shows how to obtain the market identification code.

Kotlin

val client: PurchaseClient = ...
val listener: StoreInfoListener = ...
 
client.getStoreInfoAsync { iapResult, storeCode ->
    // Save storecode and use it in Server to Server API.
}

Java

PurchaseClient client = ...
 
client.getStoreInfoAsync(new StoreInfoListener() {
    @Override
    public void onStoreInfoResponse(IapResult iapResult, String storeCode) {
        // Save storecode and use it in Server to Server API.
    }
});

Error code definition

0RESULT_OKSuccess

1

RESULT_USER_CANCELED

The billing is cancelled.

2

RESULT_SERVICE_UNAVAILABLE

An error occurred in the terminal or the server network.

3

RESULT_BILLING_UNAVAILABLE

An error occurred in the process of processing the purchase.

4

RESULT_ITEM_UNAVAILABLE

The in-app is not on sale or cannot be purchased.

5

RESULT_DEVELOPER_ERROR

The request is not valid.

6

RESULT_ERROR

Another undefined error occurred.

7

RESULT_ITEM_ALREADY_OWNED

You have already owned the item.

8

RESULT_ITEM_NOT_OWNED

You cannot consume the item because you do not have it.

9

RESULT_FAIL

The billing failed. Check the billing availability and billing method before making the billing again.

10

RESULT_NEED_LOGIN

The store app login is required.

11

RESULT_NEED_UPDATE

The billing module update is required.

12

RESULT_SECURITY_ERROR

The billing has been requested from an invalid app.

13

RESULT_BLOCKED_APP

The request has been blocked.

14

RESULT_NOT_SUPPORT_SANDBOX

The function is not supported in the test environment.

1001

ERROR_DATA_PARSING

An error occurred in the response data parsing.

1002

ERROR_SIGNATURE_VERIFICATION

An error occurred in the signature review of the purchase information.

1003

ERROR_ILLEGAL_ARGUMENT

An invalid parameter is entered.

1004

ERROR_UNDEFINED_CODE

An undefined error occurred.

1005

ERROR_SIGNATURE_NOT_VALIDATION

The entered license key is not valid.

1006

ERROR_UPDATE_OR_INSTALL

The billing module installation has failed.

1007

ERROR_SERVICE_DISCONNECTED

The billing module connection has been cut.

1008

ERROR_FEATURE_NOT_SUPPORTED

The function is not supported.

1009

ERROR_SERVICE_TIMEOUT

The communication time with the service has exceeded.

99999

RESULT_EMERGENCY_ERROR

The server is under maintenance.

Last updated