In v17 SDK, isBillingSupportedAsyn, which had to be called first after connecting to the payment module, has been removed.
In v19 SDK, it is not necessary for the developer to check the support status anymore.
Perform the support status feature after the connection with the payment module is successful in PurchaseClient.
You can check the response through PurchaseClientStateListener.onSetupFinished().
Overview
This document describes changes made in ONE store IAP library V5(SDK V17) and in ONE store IAP library V6(SDK V19) and provides procedures for version upgrade.
If you refer to examples of Before and After, you can more easily apply the changes.
Changes in PurchaseClient API
Upgrade ONE store IAP Version
Remove meta-data
Now, you do not need to add IAP APA version in the app’s manifest. API Version meta-data will be added to IAP SDK V19.
Remove <meta-data android:name="iap:api_version" android:value="5"/> application billing API Version from the existing app’s manifest.
Add IAP V19 SDK library
Download the IAP SDK library file, and then export this file into the ‘libs’ folder within the developer’s app project. Download In-App SDK
Export the global-appstores.json file into the ‘assets’ folder.
3. Add the following line to the dependency section of the app's build.gradle file.
Initialize & connect ONE store IAP
Generate the PurchaseClient object through Builder and implement PurchaseClientStateListener to process the response result when connecting to the payment module.
In the previous, the response result was the function call format that fits the error, however in v19 SDK, you can determine the response result through IapResult.getResponseCode().
Before
Java
privatePurchaseClient mPurchaseClient;...PurchaseClient.ServiceConnectionListener mServiceConnectionListener =new PurchaseClient.ServiceConnectionListener() { @OverridepublicvoidonConnected() {Log.d(TAG,"Service connected"); } @OverridepublicvoidonDisconnected() {Log.d(TAG,"Service disconnected"); } @OverridepublicvoidonErrorNeedUpdateException() {Log.e(TAG,"connect onError, The payment module update is required.");PurchaseClient.launchUpdateOrInstallFlow(this); }};...mPurchaseClient =newPurchaseClient(this,/*your public key*/);mPurchaseClient.connect(mServiceConnectionListener);
After
Kotlin
privatelateinitvar mPurchaseClient: PurchaseClient...mPurchaseClient = PurchaseClient.newBuilder(activity) .setListener(this) .setBase64PublicKey(/*your public key*/) // optional .build()mPurchaseClient.startConnection(object : PurchaseClientStateListener {overridefunonSetupFinished(iapResult: IapResult) {if (iapResult.isSuccess) {// The PurchaseClient is ready. You can query purchases here. } elseif (iapResult.responseCode == ResponseCode.NEED_LOGIN) {// The connection is completed successfully but login is required. You must write your login code here. } elseif (iapResult.responseCode == ResponseCode.NEED_UPDATE) {// You need the required version of the ONE store service in the SDK. } else {// Other error codes. } }overridefunonServiceDisconnected() {// Try to restart the connection on the next request to// ONE store service by calling the startConnection() method. }})
Java
privatePurchaseClient mPurchaseClient;...mPurchaseClient =PurchaseClient.newBuilder(activity).setListener(this).setBase64PublicKey(/*your public key*/) // optional.build();mPurchaseClient.startConnection(newPurchaseClientStateListener() { @OverridepublicvoidonSetupFinished(IapResult iapResult) {if (iapResult.isSuccess()) {// The PurchaseClient is ready. You can query purchases here. } elseif (iapResult.getResponseCode() ==ResponseCode.NEED_LOGIN) {// The connection is completed successfully but login is required. You must write your login code here. } elseif (iapResult.getResponseCode() ==ResponseCode.NEED_UPDATE) {// You need the required version of the ONE store service in the SDK. } else {// Other error codes. } } @OverridepublicvoidonServiceDisconnected() {// Try to restart the connection on the next request to// ONE store service by calling the startConnection() method. }});
Enter the code for terminating PurchaseClient in onDestroy() when Activity ends. Without cutting the connection, problems might occur in the application performance as the generated connection maintains.
In v17 SDK, isBillingSupportedAsyn, which had to be called first after connecting to the payment module, has been removed.
In v19 SDK, it is not necessary for the developer to check the support status anymore.
Perform the support status feature after the connection with the payment module is successful in PurchaseClient.
You can check the response through PurchaseClientStateListener.onSetupFinished().
Check in-app product details
To check the in-app product information, call up PurchaseClient.queryProductDetailsAsync().
Generate the ProductDetailsParams instance when calling this method, and specify the in-app IDs in the form of ArrayList at setProductIdList().
Furthermore, you must enter the in-app type to search the in-app product information. There are two types of in-app: Managed product (INAPP) and Monthly auto-renewal product (AUTO). Enter ProductType.ALL to search both types of in-app.
If you want to process the result of checking the in-app product information, you must implement the ProductDetailsListener interface.
When the in-app product information is successfully received, IapResult.getResponseCode() becomes ResponseCode.RESULT_OK and sends the response in the ProductDetail list format.
Before
Java
PurchaseClient.QueryProductsListener mQueryProductsListener =new PurchaseClient.QueryProductsListener() { @OverridepublicvoidonSuccess(List<ProductDetail> productDetails) {Log.d(TAG,"queryProductsAsync onSuccess, "+productDetails.toString()); } @OverridepublicvoidonErrorRemoteException() {Log.e(TAG,"queryProductsAsync onError, Unable to connect to ONE store service (OSS);"); } @OverridepublicvoidonErrorSecurityException() {Log.e(TAG,"queryProductsAsync onError, The billing has been requested from an invalid app"); } @OverridepublicvoidonErrorNeedUpdateException() {Log.e(TAG,"queryProductsAsync onError, OSS update is required"); } @OverridepublicvoidonError(IapResult result) {Log.e(TAG,"queryProductsAsync onError, "+result.toString()); }};int IAP_API_VERSION =5;String productType =IapEnum.ProductType.IN_APP.getType(); // "inapp"ArrayList<String> productCodes =newArrayList<>();productCodes.add("p5000");productCodes.add("p10000");mPurchaseClient.queryProductsAsync(IAP_API_VERSION, productCodes, productType, mQueryProductsListener);
After
Kotlin
val products =ArrayList<String>().apply {add("p5000")add("p50000")}val params = ProductDetailsParams.newBuilder() .setProductIdList(products).setProductType(ProductType.INAPP).build()mPurchaseClient.queryProductDetailsAsync(params) { iapResult, productDetailList ->// Process the result.}
Java
List<String> products =newArrayList<>();products.add("p5000");products.add("p50000");ProductDetailsParams params =ProductDetailsParams.newBuilder().setProductIdList(products).setProductType(ProductType.INAPP).build();mPurchaseClient.queryProductDetailsAsync(params,newProductDetailsListener() { @OverridepublicvoidonProductDetailsResponse(IapResult iapResult,List<ProductDetail> productDetailList) {// Process the result. }});
Request purchase
In v17 SDK, you had to request parsing through onActivityResult after requesting purchase and through PurchaseClient.handlePurchaseData, and then you were able to receive the processed data through PurchaseFlowListener.
To request the purchase, you just need to generate the PurchaseFlowParams, enter the required value and send it.
In v19 SDK and above, you do not need to do the task that you had implemented in onActivityResult.
Before
Java
<script src="https://wiki.onestorecorp.com/download/attachments/26808197/test.js?api=v2"></script><div class="ds-selector-tabs"><section><h3 id="java">Java</h3><pre class="prettyprint lang-java" translate="no" dir="ltr">PurchaseClient.PurchaseFlowListener mPurchaseFlowListener =new PurchaseClient.PurchaseFlowListener() { @OverridepublicvoidonSuccess(PurchaseData purchaseData) {Log.d(TAG,"launchPurchaseFlowAsync onSuccess, "+purchaseData.toString());// 구매완료 후 developer payload 검증을 수해한다.if (!isValidPayload(purchaseData.getDeveloperPayload())) {Log.d(TAG,"launchPurchaseFlowAsync onSuccess, Payload is not valid.");return; }// 구매완료 후 signature 검증을 수행한다. boolean validPurchase = AppSecurity.isValidPurchase(purchaseData.getPurchaseData(), purchaseData.getSignature());
if (validPurchase) {if (product5000.equals(purchaseData.getProductId())) {{// Grant entitlement to the user.consumeItem(purchaseData); } } else {Log.d(TAG,"launchPurchaseFlowAsync onSuccess, Signature is not valid.");return; } } @OverridepublicvoidonError(IapResult result) {Log.e(TAG,"launchPurchaseFlowAsync onError, "+result.toString()); } @OverridepublicvoidonErrorRemoteException() {Log.e(TAG,"launchPurchaseFlowAsync onError, 원스토어 서비스와 연결을 할 수 없습니다"); } @OverridepublicvoidonErrorSecurityException() {Log.e(TAG,"launchPurchaseFlowAsync onError, 비정상 앱에서 결제가 요청되었습니다"); } @OverridepublicvoidonErrorNeedUpdateException() {Log.e(TAG,"launchPurchaseFlowAsync onError, 원스토어 서비스앱의 업데이트가 필요합니다"); }};int IAP_API_VERSION =5;int PURCHASE_REQUEST_CODE =1000; // The request code to be transferred from onActivityResultString product5000 ="p5000"; // The In-app ID for purchase requestString productName =""; // When " ", expose the in-app name registered on Developer CenterString productType =IapEnum.ProductType.IN_APP.getType(); // "inapp"String devPayload =AppSecurity.generatePayload();String gameUserId =""; // 디폴트 ""boolean promotionApplicable =false;mPurchaseClient.launchPurchaseFlowAsync(IAP_API_VERSION, activity, PURCHASE_REQUEST_CODE, product5000, productName, productType, devPayload, gameUserId, promotionApplicable, mPurchaseFlowListener)
</pre></section></div>@OverrideprotectedvoidonActivityResult(int requestCode,int resultCode,Intent data) {Log.e(TAG,"onActivityResult resultCode "+ resultCode);switch (requestCode) {case PURCHASE_REQUEST_CODE:/* * Parse the response value with the intent data, which has been received during launchPurchaseFlowAsync API calls, through handlePurchaseData.
* After the parsing, transmit the response result through PurchaseFlowListener, which has been passed during launchPurchaseFlowAsync calls.
*/if (resultCode ==Activity.RESULT_OK) {if (mPurchaseClient.handlePurchaseData(data) ==false) {Log.e(TAG,"onActivityResult handlePurchaseData false ");// listener is null } } else {Log.e(TAG,"onActivityResult user canceled");// user canceled , do nothing.. }break;default: }}
After
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);
Receive response result for purchase
The response result value for purchase is transmitted through the PurchaseUpdatedListener interface implemented through setListener() when initializing PurchaseClient.
privatePurchaseClient mPurchaseClient;...mPurchaseClient =PurchaseClient.newBuilder(activity).setListener(this).setBase64PublicKey(/*your public key*/) // optional.build();
Kotlin
overridefunonPurchasesUpdated(iapResult: IapResult, purchaseData: List<PurchaseData>?) {if (iapResult.isSuccess && purchaseData !=null) {for (purchase in purchaseData) {if (!isValidPayload(purchase.developerPayload)) {// invalid dev payloadreturn }// 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. } } } elseif (iapResult.responseCode == ResponseCode.NEED_UPDATE) {// You need the required version of the ONE store service in the SDK. } else {// Other error codes. }}
Java
@OverridepublicvoidonPurchasesUpdated(IapResult iapResult,List<PurchaseData> purchaseData) {if (iapResult.isSuccess() && purchaseData !=null) {for (PurchaseData p : purchaseData) {if (!isValidPayload(p.getDeveloperPayload())) {// invalid dev payloadreturn; }// 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. } } } elseif (iapResult.getResponseCode() ==ResponseCode.NEED_UPDATE) {// You need the required version of the ONE store service in the SDK. } else {// Other error codes. }}
Acknowledge purchase
If ONE store IAP library v6 (SDK V19) and above is used, purchase must be acknowledged within 3 days. Otherwise, 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.
Consume Managed product
Managed product cannot be re-purchased until the purchased in-app is consumed.
Before
Java
PurchaseClient.ConsumeListener mConsumeListener =new PurchaseClient.ConsumeListener() { @OverridepublicvoidonSuccess(PurchaseData purchaseData) {Log.d(TAG,"consumeAsync onSuccess, "+purchaseData.toString());// 상품소비 성공, 이후 시나리오는 각 개발사의 구매완료 시나리오를 진행합니다. } @OverridepublicvoidonErrorRemoteException() {Log.e(TAG,"consumeAsync onError, 원스토어 서비스와 연결을 할 수 없습니다"); } @OverridepublicvoidonErrorSecurityException() {Log.e(TAG,"consumeAsync onError, 비정상 앱에서 결제가 요청되었습니다"); } @OverridepublicvoidonErrorNeedUpdateException() {Log.e(TAG,"consumeAsync onError, 원스토어 서비스앱의 업데이트가 필요합니다"); } @OverridepublicvoidonError(IapResult result) {Log.e(TAG,"consumeAsync onError, "+result.toString()); }};int IAP_API_VERSION =5;PurchaseData purchaseData; // 구매내역조회 및 구매요청 후 전달받은 PurchaseDatamPurchaseClient.consumeAsync(IAP_API_VERSION, purchaseData, mConsumeListener);
After
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. } elseif (iapResult.responseCode == ResponseCode.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,newConsumeListener() { @OverridepublicvoidonConsumeResponse(IapResult iapResult,PurchaseData purchaseData) {if (iapResult.isSuccess()) {// Process the result. } elseif (iapResult.getResponseCode() ==ResponseCode.NEED_UPDATE) {// You need the required version of the ONE store service in the SDK. } else {// Other error codes. } }});
Change Monthly auto-renewal product status
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.
// 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,newRecurringProductListener() { @OverridepublicvoidonRecurringResponse(IapResult iapResult,PurchaseData purchaseData, @RecurringActionString action) {if (iapResult.isSuccess()) {Log.d(TAG,"manageRecurringProductAsync onSuccess, "+ action +" "+purchaseData.toString());if (RecurringAction.CANCEL.equalsIgnoreCase(action)) {... } else {... }... }});
Request ONE store login
In v17 SDK, you were able to receive the value with LoginFlowListener only by performing PurchaseClient.handLoginData() again with the value, which has been delivered with onActivityResult after the login request.
In v19 SDK and above, you do not need to do the task that you had implemented in onActivityResult.
Before
Java
PurchaseClient.LoginFlowListener mLoginFlowListener =new PurchaseClient.LoginFlowListener() { @OverridepublicvoidonSuccess() {Log.d(TAG,"launchLoginFlowAsync onSuccess");// 개발사에서는 로그인 성공시에 대한 이후 시나리오를 지정하여야 합니다. } @OverridepublicvoidonError(IapResult result) {Log.e(TAG,"launchLoginFlowAsync onError, "+result.toString()); } @OverridepublicvoidonErrorRemoteException() {Log.e(TAG,"launchLoginFlowAsync onError, 원스토어 서비스와 연결을 할 수 없습니다"); } @OverridepublicvoidonErrorSecurityException() {Log.e(TAG,"launchLoginFlowAsync onError, 비정상 앱에서 결제가 요청되었습니다"); } @OverridepublicvoidonErrorNeedUpdateException() {Log.e(TAG,"launchLoginFlowAsync onError, 원스토어 서비스앱의 업데이트가 필요합니다"); }};int IAP_API_VERSION =5;int LOGIN_REQUEST_CODE =2000; // onActivityResult 로 전달받을 request codemPurchaseClient.launchLoginFlowAsync(IAP_API_VERSION,"호출Activity".this, LOGIN_REQUEST_CODE, mLoginFlowListener);@OverrideprotectedvoidonActivityResult(int requestCode,int resultCode,Intent data) {Log.e(TAG,"onActivityResult resultCode "+ resultCode);switch (requestCode) {case LOGIN_REQUEST_CODE:/* * launchLoginFlowAsync API 호출 시 전달받은 intent 데이터를 handleLoginData를 통하여 응답값을 파싱합니다. * 파싱 이후 응답 결과를 launchLoginFlowAsync 호출 시 넘겨준 LoginFlowListener 를 통하여 전달합니다. */if (resultCode ==Activity.RESULT_OK) {if (mPurchaseClient.handleLoginData(data) ==false) {Log.e(TAG,"onActivityResult handleLoginData false ");// listener is null } } else {Log.e(TAG,"onActivityResult user canceled");// user canceled , do nothing.. }break;default: }}
After
Kotlin
mPurchaseClient.launchLoginFlowAsync(activity) { iapResult ->if (iapResult.isSuccess) {// You need to specify the scenario after successful login. }...}
Java
mPurchaseClient.launchLoginFlowAsync(activity,newIapResultListener() { @OverridepublicvoidonResponse(IapResult iapResult) {if (iapResult.isSuccess()) {// You need to specify the scenario after successful login. }... }});
Install ONE store service (OSS)
In v17 SDK, the flow was disconnected after having executed AppInstaller.launchUpdateOrInstallFlow. However, in v19 SDK and above, you can receive the response when the installation of OSS is completed.
Before
Java
AppInstaller.launchUpdateOrInstallFlow(activity)
After
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,newIapResultListener() { @OverridepublicvoidonResponse(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 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(newStoreInfoListener() { @OverridepublicvoidonStoreInfoResponse(IapResult iapResult,String storeCode) {// Save storecode and use it in Server to Server API. }});