Implementation of In-App Purchase

IAP can be implemented in two ways; the first way is to directly implement interface of the IAP service provided by ONE store, and the second way is to use the IAP SDK which is wrapping the IAP service interface implementation. IAP can be implemented by using one of the two ways above. Each developer can select a method appropriate for its applications. However, for a more convenient implementation, the implementation by using the IAP SDK is recommended rather than the direct implementation of the service.

Directly Implement In-App Purchase Service Interface

  • You are required to follow a development method optimized for the environment when developing plug-in forms of the various types of development platforms (including Unity and Unreal 3D), and therefore in some cases you may need to directly implement the IAP service interface. (Unity and the Unreal 3D ONE store IAP SDK will be provided) Interface of ONE store's IAP service is provided as the ‘IInAppPurchseService.aidl’ file which is the AIDL(Android Interface Definition Language) type. The method to directly implement interface by using the AIDL file without the IAP SDK is as follows:

    • Bind AIDL

      • To use IAP by allowing a developer's apps and ONE store (service) apps to communicate, you are required to bind IAP to the ONE store IAP service by using the IInAppPurchseService.aidl. file. To this end, create a ServiceConnection object.

        private IInAppPurchaseService mService; mServiceConn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = IInAppPurchaseService.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { mService = null; }};

      • And then, if you bind the created Connection object by calling the bindService method according to the life cycle of application in the app, you can request IAP by using the mService object.

        Intent serviceIntent = new Intent();serviceIntent.setComponent(new ComponentName("com.skt.skaf.OA00018282", "com.onestore.extern.iap.InAppPurchaseService"));serviceIntent.setAction("com.onestore.extern.iap.InAppPurchaseService.ACTION"); if (Context.getPackageManager().resolveService(serviceIntent, 0) != null) { Log.d(LOG_TAG, "bindService "); mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);} else { Log.e(LOG_TAG, "no service ");}

      • When an activity or a process that uses payment is terminated, you must release the bound IAP service connection in the onDestroy method.

    • Unbind AIDL

      • If the bound service is not released, the created connection will be maintained and that could cause a problem in the function of application.

        if (mServiceConn != null) { mContext.unbindService(mServiceConn);}

    • Check Support - isBillingSupported()

      • Check whether support is provided for IAP before using the ONE store IAP API in a developer's app. If you use other API without processing a failure code, the same failure code might be passed to you. You can find detailed response codes in the Table 1 in In-App Purchase Reference.

      • This method may be blocked when called in the UI thread, and therefore you must call it by creating a thread.

        int apiVersion = 5; // IAP API VersionString packageName = "App package name"; int responseCode = -1;try { responseCode = mService.isBillingSupported(apiVersion, packageName);} catch (RemoteException e) { e.printStackTrace();} // check the responseCode valueif (responseCode != 0) { Log.e("ERROR", "error result code : " + responseCode); return;}

    • Check In-App Product Information - getProductDetails()

      • Call the getProductDetails method with the in-app product type and in-app ID list information that you want to check by using the API which is for getting in-app product information registered on the ONE store Developer Center. The in-app ID is a Custom in-app ID which is designated when developers register an in-app product on the Developer Center. You can find detailed response codes and specifications in In-App Purchase Reference.

      • This method may be blocked when called in the UI thread, and therefore you must call it by creating a thread.

        int apiVersion = 5;String packageName = "App package name";String productType = "inapp"; // managed product(inapp), subscription(auto)Bundle productsBundle = new Bundle(); // product ID list bundleproductsBundle.putStringArrayList("productDetailList", productList); Bundle result = new Bundle();try { result = mService.getProductDetails(apiVersion, packageName, productType, productsBundle);} catch (RemoteException e) { e.printStackTrace();} // check the responseCode valueint responseCode = result.getInt("responseCode");if (responseCode != 0) { Log.e("ERROR", "error result code : " + responseCode); return;} ArrayList<String> list = result.getStringArrayList("productDetailList");

    • Request Purchase

      • The ONE store service (OSS) provides two types of API - the getPurchaseIntent and the getPurchaseIntentExtraParam - to request purchase. You can find difference between each API in In-App Purchase Reference.

    • getPurchaseIntent()

      • Upon payment request, proceed with getting the purchase intent screen by using the getPurchaseIntent. The method is called by entering in-app ID, in-app product name, in-app product type and the developerPayload(up to 100bytes) randomly put by developers, and this value is used to confirm data consistency or additional data after the payment is made. The code that executes the payment process by using the results gained from calling the getPurchaseIntent and methods is as follows:

      • This method may be blocked when called in the UI thread, and therefore you must call it by creating a thread.

        int apiVersion = 5;String packageName = "App package name";String productId = "p5000"; // product IDString productName = ""; // in-app product name (default value - "")String productType = "inapp"; // managed product(inapp), subscription(auto)String devPayload = "developer payload"; // developer payload (up to 100byte) Bundle result = new Bundle();try { result = mService.getPurchaseIntent(apiVersion, packageName, productId, productName, productType, devPayload);} catch (RemoteException e) { e.printStackTrace();} // check the responseCode valueint responseCode = result.getInt("responseCode");if (responseCode != 0) { Log.e("ERROR", "error result code : " + responseCode); return;} int requestCodePurchase = 1511;Intent purchaseIntent = result.getParcelable("purchaseIntent");act.startActivityForResult(purchaseIntent, requestCodePurchase);

    • getPurchaseIntentExtraParam()

      • This is much the same as the getPurchaseIntent specification used by developers for purchase and delivers bundle data to the last parameter. You can find the contents defined in the Bundle data in In-App Purchase Reference. The code that executes the payment process by using the results gained from calling the getPurchaseIntentExtraParam and methods is as follows:

      • This method may be blocked when called in the UI thread, and therefore you must call it by creating a thread.

        The gameUserId, protectionApplicable parameters are optional and must be used only in advance for the promotion with ONE store department manager. In general, the value should not be sent. In addition, the gameUserId should be sent to a hashed unique value so that there is no privacy information protection issue when sending values in advance.

        int apiVersion = 5;String productId = "p5000"; // product IDString productName = ""; // in-app product name (default value - "")String productType = "inapp"; // managed product(inapp), subscription(auto)String devPayload = "developer payload"; // developer payload (up to 100Byte)String gameUserId = "";boolean promotionApplicable = false; Bundle result = new Bundle(); Bundle extraParams = new Bundle();extraParams.putString("gameUserId", gameUserId);extraParams.putBoolean("promotionApplicable", promotionApplicable);try { result = mService.getPurchaseIntent(apiVersion, packageName, productId, productName, productType, devPayload, extraParams);} catch (RemoteException e) { e.printStackTrace();} // check the responseCode valueint responseCode = result.getInt("responseCode");if (responseCode != 0) { Log.e("ERROR", "error result code : " + responseCode); return;} int requestCodePurchase = 1511;Intent purchaseIntent = result.getParcelable("purchaseIntent");act.startActivityForResult(purchaseIntent, requestCodePurchase);

      • You can find the detailed response codes and specifications of purchase request in In-App Purchase Reference. Upon the completion of payment process, the result value is passed to the onActivityResult method of the Activity that implemented IAP. At this moment, the Activity.RESULT_OK value or the Activity.RESULT_CANCELED value is passed to the resultCode, and you will receive the payment result code value and purchase information data as Bundle in the Intent data.

        @Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == requestCodePurchase) { int responseCode = data.getIntExtra(responseCode, -1); if (resultCode == Activity.RESULT_OK && responseCode != 0) { Log.e("ERROR", "error result code : " + responseCode); return; } String purchaseData = data.getStringExtra(ApiCommon.KEY_PURCHASE_DATA); String signature = data.getStringExtra(ApiCommon.KEY_PURCHASE_SIGNATURE); // proceed with signature verification (refer to the sample app or the SDK logic for the verifySignature logic.) boolean isSuccess = verifySignature(BASE64_PUBLIC_KEY, purchaseJson, signature); if (isSuccess) { // as for the managed product, request the consumption of the product } else { // signature verification failed }}

    • Check Purchase History - getPurchases()

      • The API for checking purchase history can get the information of managed products(inapp) which are not consumed yet and the list of subscription(auto) products which are in use. On the first call, the continuationKey value is sent as a blank(""). When there are more than 100 in-app product information items, you can receive the information items below the 100th rank by putting the continuationKey which was downloaded from the server in the first response into the second call parameter. The response result is the list of purchase history IDs, the list of purchase history information and the list of signatures used for signature verification. Developers are required to proceed with signature verification by using a single purchase history information item and signature information.

      • This method may be blocked when called in the UI thread, and therefore you must call it by creating a thread.

        int apiVersion = 5;String packageName = "App package name";String productType = "inapp"; // managed product(inapp), subscription(auto)String continuationKey = ""; // token downloaded from server for the next inquiry in case there are more than 100 server responses. ArrayList<String> productIdList;ArrayList<String> purchaseDataList;ArrayList<String> purchaseSignatureList;do { Bundle result = new Bundle(); try { result = mService.getPurchases(apiVersion, packageName, productType, continuationKey); } catch (RemoteException e) { e.printStackTrace(); } // check the responseCode value int responseCode = result.getInt("responseCode"); if (responseCode != 0) { Log.e("ERROR", "error result code : " + responseCode); return; } productIdList = result.getStringArrayList("productIdList"); purchaseDataList = result.getStringArrayList("purchaseDetailList"); purchaseSignatureList = result.getStringArrayList("purchaseSignatureList"); continuationKey = result.getString("continuationKey");} while (!TextUtils.isEmpty(continuationKey)); // Signature check logicfor (int i = 0; i < purchaseDataList.size(); i++) { String purchaseJson = purchaseDataList.get(i); String signature = signatureList.get(i); // proceed with signature verification (refer to the sample app or the SDK logic for the verifySignature logic.) boolean isSuccess = verifySignature(BASE64_PUBLIC_KEY, purchaseJson, signature); if (isSuccess) { // as for the managed product, request the consumption of the product } else { // signature verification failed }}

    • Consume In-App Product - consumePurchase()

      • The managed product(inapp) cannot be repurchased until the purchased product is consumed. Immediately after a managed product is purchased, the purchase information of the product is managed by ONE store. If the purchased product is consumed, the user's right to purchase the product will be immediately withdrawn. In other words, if a managed product is not consumed after purchase, the product can be used like a non-consumable type product. If the purchased product is consumed immediately, it can be used like a consumable type product. If the purchased product is consumed after a certain period of time, it can be used like a subscription-type product. For details, refer to 'How to Implement In-App Purchases Case by Case'. To consume the purchased product, pass to the consumePurchase method the 'purchaseId' which is delivered when the purchase is completed or purchase history is checked, and call. The following is the sample code to call the method. After the completion of purchase process, if you want to provide the items of a developer's app for users, you must provide them after confirming that the result of the request of the method which consumes the purchased product is success.

      • This method may be blocked when called in the UI thread, and therefore you must call it by creating a thread.

        int apiVersion = 5;String packageName = "App package name";String purchaseId = "purchase ID"; // the purchaseId which is included in the response after the purchase is completed. Bundle result = new Bundle();try { result = mService.consumePurchase(apiVersion, packageName, purchaseId);} catch (RemoteException e) { e.printStackTrace();} // check the responseCode valueint responseCode = result.getInt("responseCode");if (responseCode != 0) { Log.e("ERROR", "error result code : " + responseCode); return;} String responsePurchaseId = result.getString("purchaseId");

    • Request Change to Subscription Status - manageRecurringProduct()

      • As for the subscription(auto) product, payment is automatically made one month after the first purchase. If you want to change the status of the subscription product before the automatic payment is made, revise the status of subscription(recurringState) using this method (status information is included in the 'check purchase history' information). At the time when you reserve the cancellation of subscription, pass the action field as "cancel" and call. If you want to cancel the reservation of the subscription cancellation, pass the action field as "reactive" and call. The following is the sample code to call the method.

      • This method may be blocked when called in the UI thread, and therefore you must call it by creating a thread.

        int apiVersion = 5;String packageName = "App package name";String action = "cancel"; // reserve the cancellation of subscription (cancel), cancel the reservation of the subscription cancellation(reactivate)String purchaseId = "purchase ID"; // the purchaseId which is included in the response after the purchase is completed. Bundle result = new Bundle();try { result = mService.manageRecurringProduct(apiVersion, packageName, action, purchaseId);} catch (RemoteException e) { e.printStackTrace();} // check the responseCode valueint responseCode = result.getInt("responseCode");if (responseCode != 0) { Log.e("ERROR", "error result code : " + responseCode); return;} String responsePurchaseId = result.getString("purchaseId");

    • Request Login to ONE store - getLoginIntent()

      • ONE store's payment module supports the processing of the user who is not logged in. If the RESULT_NEED_LOGIN ('refer to (v0.9) 8. In-App Purchase Reference > Server Response Codes' ) is passed as the result value of ONE store's API call, developers can notify users that the ONE store login is needed, or can expose ONE store's login screen via the getLoginIntent. You can call the screen with the startActivityForResult after receiving the intent of ONE store's login activity by using the getLoginIntent method in an app applied with ONE store's IAP SDK. The following is the sample code to call the method.

      • This method may be blocked when called in the UI thread, and therefore you must call it by creating a thread.

        int apiVersion = 5;String packageName = "App package name"; Bundle result = new Bundle();try { result = mService.getLoginIntent(apiVersion, packageName);} catch (RemoteException e) { e.printStackTrace();} // check the responseCode valueint responseCode = result.getInt("responseCode");if (responseCode != 0) { Log.e("ERROR", "error result code : " + responseCode); return;} int requestCodeLogin = 1611;Intent loginIntent = result.getParcelable("loginIntent");act.startActivityForResult(loginIntent, requestCodeLogin);

      • You can find the detailed response codes and specifications in In-App Purchase Reference. Upon the completion of login process, the result value is passed to the onActivityResult method of the Activity that called the login method. At this moment, the Activity.RESULT_OK value or the Activity.RESULT_CANCELED value is passed to the resultCode, and you will receive the payment result code value and login information data as Bundle in the Intent data.

        @Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == requestCodeLogin) { int responseCode = data.getIntExtra(responseCode, -1); if (resultCode == Activity.RESULT_OK && responseCode != 0) { Log.e("ERROR", "error result code : " + responseCode); return; } else { // success } }}

Implement Using In-App Purchase SDK

  • If you directly create the IAP code through the" Directly Implement In-App Purchase Service Interface" approach, you can develop it more extensively, however have no choice but to increase the amount of the code. For a more convenient implementation, use the IAP SDK which wrapped the IAP service. The IAP SDK provides the PurchaseClient class for a more convenient and intuitive use by hiding the parts that use the IAP service. Refer to "Add In-App Purchase Library" to find a way to add the SDK to the development project.

    • PurchaseClient Class

      • The SDK defines any additional error codes that could happen only in the SDK, other than the In-App Purchase Reference > Server Response Code that occurs when using the AIDL.

      • Developers must check the Server Response Codes in addition to these error codes and proceed to handle each error situation.

        Response CodeValueDescriptionHow to handle

        IAP_ERROR_DATA_PARSING

        1001

        Error on parsing response data

        This error occurs if the specification of response is invalid when purchase is requested. It would be a temporary server error. However, if this error continues to happen, contact the person in charge in ONE store and inquire the log and purchase information.

        IAP_ERROR_SIGNATURE_VERIFICATION

        1002

        Error on signature verification of purchase information

        If you cannot verify the response with the signature value downloaded from the ONE store Developer Center, such response would be a forged/falsified one.

        If this error occurs in the development stage, allow developers to check the signature value and the verify logic.

        IAP_ERROR_ILLEGAL_ARGUMENT

        1003

        Input of abnormal parameters

        This error happens if abnormal parameters are put when calling the SDK method. It is required to be modified at the development stage.

        IAP_ERROR_UNDEFINED_CODE

        1004

        Undefined error

        This error occurs if an error does not constitute the server error and the SDK error code. If this error continues to happen, contact the person in charge in ONE store and inquire the log and purchase information.

  • About Major Classes in SDK

    • IInAppPurchaseService AIDL InterfaceIt is the AIDL interface file which is defined to communicate with ONE store's IAP service.

    • PurchaseClient ClassAll methods used for IAP are included in this class. Each method is implemented by wrapping the features provided by the ONE store IAP service. The method which has 'Async' postfix in its name is processed by creating threads internally to ensure a safe operation while being executed on the UI thread. The table below shows the methods provided by the PurchaseClient class and the method information mapped to the IAP service.

      Class MethodDescriptionAIDL Method

      PurchaseClient()

      Initialize the PurchaseClient to use IAP.

      NA

      connect()

      Bind services to use the IAP service.

      NA

      terminate()

      Unbind the IAP service and release resources.

      NA

      isBillingSupportedAsync()

      Check the IAP support.

      isBillingSupported()

      queryPurchasesAsync()

      Request to check purchase history.

      getPurchases()

      queryProductsAsync()

      Request to check in-app product information.

      getProductDetails()

      consumeAsync()

      Request to consume purchased product.

      consumePurchase()

      manageRecurringProductAsync()

      Request to change the subscription status.

      manageRecurringProduct()

      launchPurchaseFlowAsync()

      Request IAP.

      getPurchaseIntent()

      getPurchaseIntentExtraParams()

      handlePurchaseData()

      Process the result of IAP.

      NA

      launchLoginFlowAsync()

      Request the ONE store login.

      getLoginIntent()

      handleLoginData()

      Process the result of the ONE store login.

      NA

      launchUpdateOrInstallFlow()

      Request a pop-up for the installation and update of ONE store.

      NA

    • IapEnum ClassThis class defines the enum value used in the methods of the apps that use the IAP SDK. It also defines the ProductType("inapp, auto), the RecurringType(-1, 0, 1), and the RecurringAction("cancel", "reactivate").IapResult ClassThis class defines the error code value which is passed to the onError listener after completing the method calls by using the IAP SDK.

    • Security ClassThis class provides a signature verification feature to confirm that data about purchase result or purchase information is not forged/falsified when the ONE store IAP is used. Verification is made using an authentication key issued by developers. For details, refer to 'Preparatory Work > Issue License Key'.

    • ProductDetail / PurchaseData Class

    • This class defines a response model for checking in-app product information in the SDK and a response model for purchase results

    • AppInstaller ClassThis class provides a feature that induces the installation and update of the OSS apps because the OSS apps are required upon in-app purchases.

  • Initialize & Connect In-App Purchases

    • To use the IAP SDK, you are required to create a PurchaseClient object and proceed with initialization to execute the purchase method. First of all, put the Context information of current Activity and the signature verification key value when creating the PurchaseClient object. After the object is created, proceed with connection by executing the connect. During the process, a connection with the IAP service is made in the SDK and a variety of variables for purchase are set up.

      /* * The connect API callback listener of the PurchaseClient * Response to success/failure of binding and to the need for service update is passed. */PurchaseClient.ServiceConnectionListener mServiceConnectionListener = new PurchaseClient.ServiceConnectionListener() { @Override public void onConnected() { Log.d(TAG, "Service connected"); } @Override public void onDisconnected() { Log.d(TAG, "Service disconnected"); } @Override public void onErrorNeedUpdateException() { Log.e(TAG, "connect onError, Update is needed for the ONE store service app"); PurchaseClient.launchUpdateOrInstallFlow(this); }}; @Overridepublic void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // initialize the PurchaseClient - the public key to check context and signature is passed to the parameter. mPurchaseClient = new PurchaseClient(this, AppSecurity.getPublicKey()); // request a service binding for IAP to the ONE store service. mPurchaseClient.connect(mServiceConnectionListener);}

    • The thing to be cautious of when performing the 'connect' is that the onErrorNeedUpdateException() of the ServiceConnectionListener will be called if the ONE store service (OSS) apps are not installed or the version of the OSS apps does not support IAP v 17. Once this error is called, call the PurchaseClient.launchUpdateOrInstallFlow method which updates or installs the OSS apps as does the code seen above. And, when the activity is terminated, put in the onDestroy method the code which releases the PurchaseClient.

      @Overrideprotected void onDestroy() { super.onDestroy(); if (mPurchaseClient == null) { Log.d(TAG, "PurchaseClient is not initialized"); return; } // once the app is terminated, terminate service by using the PurchaseClient. mPurchaseClient.terminate();}

  • Initialize & Connect In-App Purchases

    • To use the IAP SDK, you are required to create a PurchaseClient object and proceed with initialization to execute the purchase method. First of all, put the Context information of current Activity and the signature verification key value when creating the PurchaseClient object. After the object is created, proceed with connection by executing the connect. During the process, a connection with the IAP service is made in the SDK and a variety of variables for purchase are set up.

      /* * Response to success/failure of binding and to the need for service update is passed. */PurchaseClient.BillingSupportedListener mBillingSupportedListener = new PurchaseClient.BillingSupportedListener() { @Override public void onSuccess() { Log.d(TAG, "isBillingSupportedAsync onSuccess"); } @Override public void onError(IapResult result) { Log.e(TAG, "isBillingSupportedAsync onError, " + result.toString()); } @Override public void onErrorRemoteException() { Log.e(TAG, "isBillingSupportedAsync onError, Failed to connect to the ONE store service."); } @Override public void onErrorSecurityException() { Log.e(TAG, "isBillingSupportedAsync onError, Payment is requested in an abnormal app."); } @Override public void onErrorNeedUpdateException() { Log.e(TAG, "isBillingSupportedAsync onError, Update is needed for the ONE store service app"); }}; // ONE store's IAP API versionint IAP_API_VERSION = 5;mPurchaseClient.isBillingSupportedAsync(IAP_API_VERSION, mBillingSupportedListener);

  • Check In-App Product Information

    • If in-app IDs that you want to get are put in the form of the ArrayList in the parameter of the queryProductAsync method and called, the result will be passed to the registered listener. The in-app ID is a Custom in-app ID which is designated when developers register in-app products on the Developer Center. In-app product information gives a response in the form of the ProductDetail model of the SDK to the onSuccess listener.

      /* * The queryProductsAsync API (check in-app product information) callback listener of the PurchaseClient */PurchaseClient.QueryProductsListener mQueryProductsListener = new PurchaseClient.QueryProductsListener() { @Override public void onSuccess(List<ProductDetail> productDetails) { Log.d(TAG, "queryProductsAsync onSuccess, " + productDetails.toString()); } @Override public void onErrorRemoteException() { Log.e(TAG, "queryProductsAsync onError, Failed to connect to the ONE store service."); } @Override public void onErrorSecurityException() { Log.e(TAG, "queryProductsAsync onError, Payment is requested in an abnormal app."); } @Override public void onErrorNeedUpdateException() { Log.e(TAG, "queryProductsAsync onError, Update is needed for the ONE store service app"); } @Override public void onError(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 = new ArrayList<>();productCodes.add("p5000");productCodes.add("p10000"); mPurchaseClient.queryProductsAsync(IAP_API_VERSION, productCodes, productType, mQueryProductsListener);

  • Request Purchase

    • To execute purchase, call the launchPurchaseFlowAsync method. When the method is called, developers randomly enter in-app ID, in-app product name, in-app product type and the developerPayload(up to 100bytes), and these values are used to confirm data consistency or additional data after the payment is made. And the requestCode delivered through parameters is used later to confirm the data passed to the onActivityResult.

    • When the purchase is successful, the onSuccess listener receives a response and will be passed to the PurchaseData specification of the SDK. Developers confirm data consistency or additional data through the developerPayload according to the passed responses, and proceed with signature verification using signature information. Finally, when it comes to the managed product, provide users with the product by consuming the product.

    • ONE store is proceeding with various benefits promotions for users, including discount coupons and cash-back services. When purchase is requested, developers can limit or allow the application's users to participate in the promotions by using the gameUserId and promotionApplicable parameters. Developers select and pass a unique user identification number of the app in question and the app's participation in the promotions, and then ONE store applies the user's promotion benefits based on that value.

      The gameUserId, protectionApplicable parameters are optional and must be used only in advance for the promotion with ONE store department manager. In general, the value should not be sent. In addition, the gameUserId should be sent to a hashed unique value so that there is no privacy information protection issue when sending values in advance.

      /* * The launchPurchaseFlowAsync API (purchase) callback listener of the PurchaseClient */PurchaseClient.PurchaseFlowListener mPurchaseFlowListener = new PurchaseClient.PurchaseFlowListener() { @Override public void onSuccess(PurchaseData purchaseData) { Log.d(TAG, "launchPurchaseFlowAsync onSuccess, " + purchaseData.toString()); // when the purchase is completed, perform the developer payload verification. if (!isValidPayload(purchaseData.getDeveloperPayload())) { Log.d(TAG, "launchPurchaseFlowAsync onSuccess, Payload is not valid."); return; } // upon the completion of purchase, perform the signature verification. boolean validPurchase = AppSecurity.isValidPurchase(purchaseData.getPurchaseData(), purchaseData.getSignature()); if (validPurchase) { if (product5000.equals(purchaseData.getProductId())) {{ // the managed product(inapp) is consumed when the purchase is completed. consumeItem(purchaseData); } } else { Log.d(TAG, "launchPurchaseFlowAsync onSuccess, Signature is not valid."); return; } } @Override public void onError(IapResult result) { Log.e(TAG, "launchPurchaseFlowAsync onError, " + result.toString()); } @Override public void onErrorRemoteException() { Log.e(TAG, "launchPurchaseFlowAsync onError, Failed to connect to the ONE store service."); } @Override public void onErrorSecurityException() { Log.e(TAG, "launchPurchaseFlowAsync onError, Payment is requested in an abnormal app."); } @Override public void onErrorNeedUpdateException() { Log.e(TAG, "launchPurchaseFlowAsync onError, Update is needed for the ONE store service app"); }}; int IAP_API_VERSION = 5;int PURCHASE_REQUEST_CODE = 1000; // the request code to be received from the onActivityResultString product5000 = "p5000"; // in-app ID for purchase requestString productName = ""; // if the value is "", expose the in-app product name registered on the Developer Center.String productType = IapEnum.ProductType.IN_APP.getType(); // "inapp"String devPayload = AppSecurity.generatePayload();String gameUserId = ""; // default ""boolean promotionApplicable = false; mPurchaseClient.launchPurchaseFlowAsync(IAP_API_VERSION, "호출Activity".this, PURCHASE_REQUEST_CODE, product5000, productName, productType, devPayload, gameUserId, promotionApplicable, mPurchaseFlowListener);

    • For more information on the purchase to be passed to the listener when payment is successful, refer to 'In-App Purchase Reference'. The result of the completion of payment is passed to the onActivityResult of the Activity which called the launchPurchaseFlowAsync, and the handlePurchaseData method must be added here to process the purchase result within the SDK. When the handlePurchaseData method is executed and if the PurchaseFlowListener which is entered as a purchase request parameter is null, a false is returned. When it comes to processing success and error, the response result is passed through the PurchaseFlowListener listener.

      @Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.e(TAG, "onActivityResult resultCode " + resultCode); switch (requestCode) { case PURCHASE_REQUEST_CODE: /* * The intent data which is received upon calling the launchPurchaseFlowAsync API is parsing the response value through the andlePurchaseData. * After the parsing, the response result is passed through the PurchaseFlowListener which was delivered upon calling the launchPurchaseFlowAsync. */ 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: }}

  • Check Purchase History

    • By calling the queryPurchasesAsync method, get information about a user's managed products(inapp) which are not consumed yet and subscription(auto) currently in use. Within the SDK, the signature verification is proceeded to check whether the data on purchase information is forged/falsified. If the signature verification fails, the IapResult.IAP_ERROR_SIGNATURE_VERIFICATION value defined in ‘IapResult’ will be passed to the onError listener. If this error occurs, the purchase information result data might have been forged, and therefore you are required to check whether there were abusing attacks related to the purchase.

    • When it comes to the managed product(inapp) received from the 'check purchase history', developers can provide the user with the products by comsuming the product.

      /* * The queryPurchasesAsync API (check purchase history) callback listener of the PurchaseClient */PurchaseClient.QueryPurchaseListener mQueryPurchaseListener = new PurchaseClient.QueryPurchaseListener() { @Override public void onSuccess(List<purchasedata> purchaseDataList, String productType) { Log.d(TAG, "queryPurchasesAsync onSuccess, " + purchaseDataList.toString()); if (IapEnum.ProductType.IN_APP.getType().equalsIgnoreCase(productType)) { // when it comes to the managed product(inapp) received from the 'check purchase history', proceed with signature verification. If successful, proceed with the consumption of the product. } else if (IapEnum.ProductType.AUTO.getType().equalsIgnoreCase(productType)) { // when it comes to the subscription(auto) received from the 'check purchase history', perform the signature verification and then proceed with a subsequent scenario depending on the processing of each developer's app. } } @Override public void onErrorRemoteException() { Log.e(TAG, "queryPurchasesAsync onError, Failed to connect to the ONE store service."); } @Override public void onErrorSecurityException() { Log.e(TAG, "queryPurchasesAsync onError, Payment is requested in an abnormal app."); } @Override public void onErrorNeedUpdateException() { Log.e(TAG, "queryPurchasesAsync onError, Update is needed for the ONE store service app"); } @Override public void onError(IapResult result) { Log.e(TAG, "queryPurchasesAsync onError, " + result.toString()); }}; int IAP_API_VERSION = 5;String productType = IapEnum.ProductType.IN_APP.getType(); // "inapp"mPurchaseClient.queryPurchasesAsync(IAP_API_VERSION, productType, mQueryPurchaseListener);</purchasedata>

  • Consume In-App Product

    • The managed product(inapp) cannot be repurchased until the purchased product is consumed. Immediately after a managed product is purchased, the purchase information of the product is managed by ONE store. If the purchased product is not consumed, the user's right to purchase the product will be immediately withdrawn. In other words, if a managed product is consumed after purchase, it can be used like a non-consumable type product. If the purchased product is consumed immediately, it can be used like a consumable type product. If the purchased product is consumed after a certain period of time, it can be used like a subscription-type product. To consume the purchased product, pass the purchase information received from the launchPurchaseFlowAsync or queryPurchasesAsync to the parameter of the consumeAsync method and call.

      /* * The consumeAsync API (consume product) callback listener of the PurchaseClient */PurchaseClient.ConsumeListener mConsumeListener = new PurchaseClient.ConsumeListener() { @Override public void onSuccess(PurchaseData purchaseData) { Log.d(TAG, "consumeAsync onSuccess, " + purchaseData.toString()); // after the consumption of product is successful, proceed with each developer's subsequent purchase completion scenario. } @Override public void onErrorRemoteException() { Log.e(TAG, "consumeAsync onError, Failed to connect to the ONE store service."); } @Override public void onErrorSecurityException() { Log.e(TAG, "consumeAsync onError, Payment is requested in an abnormal app."); } @Override public void onErrorNeedUpdateException() { Log.e(TAG, "consumeAsync onError, Update is needed for the ONE store service app"); } @Override public void onError(IapResult result) { Log.e(TAG, "consumeAsync onError, " + result.toString()); }}; int IAP_API_VERSION = 5;PurchaseData purchaseData; // the PurchaseData received from the 'check purchase history' and purchase request.mPurchaseClient.consumeAsync(IAP_API_VERSION, purchaseData, mConsumeListener);

  • Request Change to Subscription Status

    • As for the subscription(auto) products, payment is automatically made one month after the first purchase. If you want to change the status of the subscription before the automatic payment is made, revise the status of the subscription(recurringState) using this method. (status information is included in the 'check purchase history' information) If you want to request change to the status of the subscription, pass the launchPurchaseFlowAsync or queryPurchasesAsync received from purchase information to the parameter of the manageRecurringProductAsync method. At the time when you reserve the cancellation of subscription, pass the action field as "cancel" and call. If you want to cancel the reservation of the subscription cancellation, pass the action field as "reactive" and call. The following is the sample code to call the method.

      /* * PurchaseClient의 manageRecurringProductAsync API (월정액상품 상태변경) 콜백 리스너 */PurchaseClient.ManageRecurringProductListener mManageRecurringProductListener = new PurchaseClient.ManageRecurringProductListener() { @Override public void onSuccess(PurchaseData purchaseData, String manageAction) { Log.d(TAG, "manageRecurringProductAsync onSuccess, " + manageAction + " " + purchaseData.toString()); } @Override public void onErrorRemoteException() { Log.e(TAG, "manageRecurringProductAsync onError, Failed to connect to the ONE store service."); } @Override public void onErrorSecurityException() { Log.e(TAG, "manageRecurringProductAsync onError, Payment is requested in an abnormal app."); } @Override public void onErrorNeedUpdateException() { Log.e(TAG, "manageRecurringProductAsync onError, Update is needed for the ONE store service app"); } @Override public void onError(IapResult result) { Log.e(TAG, "manageRecurringProductAsync onError, " + result.toString()); }}; int IAP_API_VERSION = 5;PurchaseData purchaseData; // the PurchaseData received from the 'check purchase history' and purchase request.String action = IapEnum.RecurringAction.CANCEL.getType(); // "cancel"mPurchaseClient.manageRecurringProductAsync(IAP_API_VERSION, purchaseData, action, mManageRecurringProductListener);

  • Request ONE store Login

    • To perform the ONE store login, call the launchLoginFlowAsync method. The requestCode passed to the parameter is used to confirm the data that will be passed to the onActivityResult later.

      /* * PurchaseClient의 launchLoginFlowAsync API (로그인) 콜백 리스너 */PurchaseClient.LoginFlowListener mLoginFlowListener = new PurchaseClient.LoginFlowListener() { @Override public void onSuccess() { Log.d(TAG, "launchLoginFlowAsync onSuccess"); // developers are required to designate a subsequent scenario after a login success. } @Override public void onError(IapResult result) { Log.e(TAG, "launchLoginFlowAsync onError, " + result.toString()); } @Override public void onErrorRemoteException() { Log.e(TAG, "launchLoginFlowAsync onError, Failed to connect to the ONE store service."); } @Override public void onErrorSecurityException() { Log.e(TAG, "launchLoginFlowAsync onError, Payment is requested in an abnormal app."); } @Override public void onErrorNeedUpdateException() { Log.e(TAG, "launchLoginFlowAsync onError, Update is needed for the ONE store service app"); } }; int IAP_API_VERSION = 5;int LOGIN_REQUEST_CODE = 2000; // the request code to be received from the onActivityResult mPurchaseClient.launchLoginFlowAsync(IAP_API_VERSION, "Call Activity".this, LOGIN_REQUEST_CODE, mLoginFlowListener)

  • The result of the completion of login is passed to the onActivityResult of the Activity that called the launchLoginFlowAsync, and the handleLoginData method must be added here to process the purchase result within the SDK. When the handleLoginData method is executed and if the LoginFlowListener which is entered as a purchase request parameter is null, a false is returned. When it comes to processing success and error, the response result is passed through the LoginFlowListener listener.

    @Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.e(TAG, "onActivityResult resultCode " + resultCode); switch (requestCode) { case LOGIN_REQUEST_CODE: /* * The intent data which is received upon calling the launchPurchaseFlowAsync API is parsing the response value through the handleLoginData. * After the parsing, the response result is passed through the LoginFlowListener delivered upon calling the launchPurchaseFlowAsync. */ 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; }}

Cautions for Implementation of Consumable Products

  • If purchased once, the managed product is managed by ONE store and the same product cannot be repurchased. To use it as a consumable-type product which can be purchased repeatedly, the purchased product is required be consumed immediately after the purchase is completed, and items must be provided to users after the consumption request is successful.

  • Even if the purchase by users is completed, the result might be passed as failure to a developer's app when the network is temporarily disconnected during the purchase process. Or the purchase result may not be received at all due to the app crashing caused by unknown errors. In this case, a serious problem will arise in which users are not provided with the items of a developer's app even though they paid the amount of money. To minimize such a problem and prevent problems from occurring when users buy the same products, the list of in-app products which were not consumed should be imported and the consumption of product should be proceeded, during the execution of app or by providing a purchase recovery button. And then a logic must be entered to reprocess the provision of a developer's app items.

Last updated