Overview

HUAWEI In-App Purchases (IAP) aggregates multiple payment methods for global payments, and can be easily integrated into your app to help you increase revenue. Using IAP, you can sell a variety of products or services, including digital goods and subscriptions, directly within your app. For details, see HUAWEI IAP 5.0.
First of all, you need to understand three types of in-app products supported by IAP.

Type

Description

Example

Consumables

Consumables are used once, then depleted, and can be purchased again.

Extra lives and gems in a game

Non-consumables

Non-consumables are purchased once and do not expire.

Extra game levels in a game or permanent membership of an app

Auto-renewable subscriptions

Subscriptions provide access to services or content on a recurring basis. The subscriptions renew automatically until they're canceled.

Non-permanent membership of an app, such as a monthly video membership

To use IAP, execute the following tasks:

What You Will Create

In this codelab, you will create a demo project and use the APIs of HUAWEI IAP to simulate the process of purchasing gems in a game. You can explore the following processes in the demo project:

What You Will Learn

In this codelab, you will learn how to:

Hardware Requirements

Software Requirements

To integrate IAP, you must complete the following preparations:

For details, please refer to Preparations for Integrating HUAWEI HMS Core.

Before starting your integration, enable the IAP service first.

  1. Sign in to AppGallery Connect and click My projects.
  2. Find your project and click the app for which you want to enable IAP.
  3. Click the Manage APIs tab and toggle on the switch for In-App Purchases.
  4. Go to Earn > In-App Purchases and click Settings. If this is the first time that you configure IAP here, a dialog box will be displayed, asking you to sign an agreement.
  5. After clicking Settings, you'll see a public key (allocated by this system and to be used for signature verification during payments) and also the Subscription notification URL parameter that you can choose to set or not.

Configure a consumable of 10 gems that users can purchase, then consume, and purchase again.

  1. Sign in to AppGallery Connect and click My apps.
  2. Click the app for which you want to add a product.
  3. Go to Operate > Products > Product Management and click Add product.
  4. Configure product information and click Save.
  5. Click View and edit and configure product prices. Set Default price (tax included) and click Convert prices. Prices in other currencies will be updated accordingly.
  6. Click Save. In the dialog box that is displayed, click OK.
  7. Now, you can find this product in the product list, but its status is Inactive. Click Activate in the Operation column. In the dialog box that is displayed, click OK. This product is now available within your app.

Your app will be able to use all APIs of HUAWEI IAP after integrating the IAP SDK. If you are using Android Studio, you can integrate the IAP SDK by using the Maven repository. After the IAP SDK is integrated into your Android Studio project, you can call the APIs of the IAP SDK to use specific IAP functions.

Obtaining the Configuration File

  1. Sign in to AppGallery Connect and click My projects. Find your app project and click the app that needs to integrate the IAP SDK.
  2. Go to Project settings > General information. In the App information area, download the agconnect-services.json file.
  3. Copy the agconnect-services.json file to the app directory of your Android Studio project.

Adding Build Dependencies

  1. Open the build.gradle file in the app directory.
  2. Add a build dependency in the dependencies block.
    dependencies { implementation 'com.huawei.hms:iap:4.0.4.301' }
  3. Click Sync Now to synchronize your project.

Configuring Obfuscation Scripts

Before building the APK, configure obfuscation scripts to prevent the IAP SDK from being obfuscated. If obfuscation arises, the IAP SDK may not function properly.

This section describes a payment process. You need to sign in with your HUAWEI ID on your phone before performing a payment test.

  1. Show product information.
    Call the obtainProductInfo API to query the gem product configured in AppGallery Connect. If the API call is successful, the gem product will be displayed on the phone based on the returned product information.
    Java:

    /** * Load products information and show the products */ private void loadProduct() { listView = (ListView)findViewById(R.id.itemlist); // Obtain in-app product details configured in AppGallery Connect, and then show the products IapClient iapClient = Iap.getIapClient(DemoActivity.this); Task<ProductInfoResult> task = iapClient.obtainProductInfo(createProductInfoReq()); task.addOnSuccessListener(new OnSuccessListener<ProductInfoResult>() { @Override public void onSuccess(ProductInfoResult result) { if (result != null && !result.getProductInfoList().isEmpty()) { // Show products to users. You can customize the following method or refer to the Reference section. showProduct(result.getProductInfoList()); } } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { Log.e("IAP", e.getMessage()); Toast.makeText(DemoActivity.this, "error", Toast.LENGTH_SHORT).show(); } }); } private ProductInfoReq createProductInfoReq() { ProductInfoReq req = new ProductInfoReq(); // In-app product type contains: // 0: consumable // 1: non-consumable // 2: auto-renewable subscription req.setPriceType(IapClient.PriceType.IN_APP_CONSUMABLE); ArrayList<String> productIds = new ArrayList<>(); // Pass in the item_productId list of products to be queried. // The product ID is the same as that set by a developer when configuring product information in AppGallery Connect. productIds.add("CProduct1"); req.setProductIds(productIds); return req; }

    Kotlin:

    /** * Load products information and show the products */ private fun loadProduct() { // obtain in-app product details configured in AppGallery Connect, and then show the products val iapClient = Iap.getIapClient(this) val task =iapClient.obtainProductInfo(createProductInfoReq()) task.addOnSuccessListener { result -> if (result != null && !result.productInfoList.isEmpty()) { showProduct(result.productInfoList) } }.addOnFailureListener { e -> Log.e("IAP", e.message) if (e is IapApiException) { val returnCode = e.statusCode if (returnCode == OrderStatusCode.ORDER_HWID_NOT_LOGIN) { Toast.makeText(this,"Please sign in to the app with a HUAWEI ID.",Toast.LENGTH_SHORT).show() } else { Toast.makeText(this, e.message, Toast.LENGTH_SHORT).show() } } else { Toast.makeText(this, "error", Toast.LENGTH_SHORT).show() } } } private fun createProductInfoReq(): ProductInfoReq? { // In-app product type contains: // 0: consumable // 1: non-consumable // 2: auto-renewable subscription val req = ProductInfoReq() req?.let { productDetails -> productDetails.priceType = IapClient.PriceType.IN_APP_CONSUMABLE val productIds = ArrayList<String>() // Pass in the item_productId list of products to be queried. // The product ID is the same as that set by a developer when configuring product information in AppGallery Connect. productIds.add("Consumable_1") productDetails.productIds = productIds } return req }

    The gem product appears on your app like this:

  2. Purchase the product.
    Tap the gem icon to purchase the product. The createPurchaseIntent API will complete the purchase.
    Create a task for calling the createPurchaseIntent API. Set OnSuccessListener and OnFailureListener for the task to receive the API call result.
    • If the API call is successful, a PurchaseIntentResult object will be returned. Your app needs to bring up the returned screen to complete payment. onActivityResult will be triggered in response to the payment result. In onActivityResult, use the parsePurchaseResultInfoFromIntent method in IapClient to obtain the PurchaseResultInfo object, which contains the payment result. If the payment is successful, signature verification must be performed on the returned data using the public key based on Verifying the Signature in the Returned Result. After a successful verification, the product is delivered, and once upon a successful delivery, it will be consumed. If the payment fails, a returnCode will be received. Rectify the fault by referring to Result Codes.
    • If the API call fails, an Exception object will be returned. If the object is an IapApiException object, use getStatusCode to obtain the result code. Rectify the fault by referring to Result Codes.
      Java:
      listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int pos, long l) { String productId = (String) products.get(pos).get(DemoActivity.this.item_productId); gotoPay(DemoActivity.this, productId, IapClient.PriceType.IN_APP_CONSUMABLE); } }); /** * Create orders for in-app products in the PMS. * * @param activity indicates the activity object that initiates a request. * @param productId ID list of products to be queried. Each product ID must exist and be unique in the current app. * @param type In-app product type. */ private void gotoPay(final Activity activity, String productId, int type) { Log.i("IAP", "call createPurchaseIntent"); IapClient mClient = Iap.getIapClient(activity); Task<PurchaseIntentResult> task = mClient.createPurchaseIntent(createPurchaseIntentReq(type, productId)); task.addOnSuccessListener(new OnSuccessListener<PurchaseIntentResult>() { @Override public void onSuccess(PurchaseIntentResult result) { Log.i("IAP", "createPurchaseIntent, onSuccess"); if (result == null) { Log.e("IAP", "result is null"); return; } Status status = result.getStatus(); if (status == null) { Log.e("IAP", "status is null"); return; } // You should pull up the page to complete the payment process. if (status.hasResolution()) { try { status.startResolutionForResult(activity, 6666); } catch (IntentSender.SendIntentException exp) { Log.e("IAP", exp.getMessage()); } } else { Log.e("IAP", "intent is null"); } } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { Log.e("IAP", e.getMessage()); Toast.makeText(activity, e.getMessage(), Toast.LENGTH_SHORT).show(); if (e instanceof IapApiException) { IapApiException apiException = (IapApiException)e; int returnCode = apiException.getStatusCode(); Log.e("IAP", "createPurchaseIntent, returnCode: " + returnCode); // Handle error scenarios } else { // Other external errors } } }); } /** * Create a PurchaseIntentReq instance. * * @param type In-app product type. * @param productId ID of the in-app product to be paid. * The in-app product ID is the product ID you set during in-app product configuration in AppGallery Connect. * @return PurchaseIntentReq */ private PurchaseIntentReq createPurchaseIntentReq(int type, String productId) { PurchaseIntentReq req = new PurchaseIntentReq(); req.setProductId(productId); req.setPriceType(type); req.setDeveloperPayload("test"); return req; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 6666) { if (data == null) { Toast.makeText(this, "error", Toast.LENGTH_SHORT).show(); return; } PurchaseResultInfo purchaseResultInfo = Iap.getIapClient(this).parsePurchaseResultInfoFromIntent(data); switch(purchaseResultInfo.getReturnCode()) { case OrderStatusCode.ORDER_STATE_SUCCESS: // Verify signature of payment results. // CipherUtil is a custom class that contains the method for verifying signatures. You can find it in the demo project package provided in the Reference section. boolean success = CipherUtil.doCheck(purchaseResultInfo.getInAppPurchaseData(), purchaseResultInfo.getInAppDataSignature(), Key.getPublicKey()); if (success) { // Call the consumeOwnedPurchase interface to consume it after successfully delivering the product to your user. consumeOwnedPurchase(this, purchaseResultInfo.getInAppPurchaseData()); } else { Toast.makeText(this, "Pay successful,sign failed", Toast.LENGTH_SHORT).show(); } return; case OrderStatusCode.ORDER_STATE_CANCEL: // The User cancels payment. Toast.makeText(this, "user cancel", Toast.LENGTH_SHORT).show(); return; case OrderStatusCode.ORDER_PRODUCT_OWNED: // The user has already owned the product. Toast.makeText(this, "you have owned the product", Toast.LENGTH_SHORT).show(); // You can check if the user has purchased the product and decide whether to provide goods // If the purchase is a consumable product, consuming the purchase and deliver product return; default: Toast.makeText(this, "Pay failed", Toast.LENGTH_SHORT).show(); break; } return; } }

      Kotlin:

      var productItemClick: ProductItemClick = object : ProductItemClick { override fun onClick(data: ProductsListModel?) { val productId: String = data?.id.toString() gotoPay(this@DemoActivity, productId, IapClient.PriceType.IN_APP_CONSUMABLE) } } /** * create orders for in-app products in the PMS. * * @param activity indicates the activity object that initiates a request. * @param productId ID list of products to be queried. Each product ID must exist and be unique in the current app. * @param type In-app product type. */ private fun gotoPay(activity: Activity,productId: String?,type: Int) { Log.i("IAP","call createPurchaseIntent") val mClient = Iap.getIapClient(activity) val task = mClient.createPurchaseIntent(createPurchaseIntentReq(type, productId)) task.addOnSuccessListener(OnSuccessListener { result -> Log.i("IAP","createPurchaseIntent, onSuccess") if (result == null) { Log.e("IAP","result is null") return@OnSuccessListener } val status = result.status if (status == null) { Log.e("IAP","status is null") return@OnSuccessListener } // you should pull up the page to complete the payment process. if (status.hasResolution()) { try { status.startResolutionForResult(activity, 6666) } catch (exp: SendIntentException) { Log.e("IAP",exp.message) } } else { Log.e("IAP","intent is null") } }).addOnFailureListener { e -> Log.e("IAP", e.message) Toast.makeText(activity, e.message, Toast.LENGTH_SHORT).show() if (e is IapApiException) { val returnCode = e.statusCode Log.e("IAP","createPurchaseIntent, returnCode: $returnCode") // handle error scenarios } else { // Other external errors } } } /** * Create a PurchaseIntentReq instance. * * @param type In-app product type. * @param productId ID of the in-app product to be paid. * The in-app product ID is the product ID you set during in-app product configuration in AppGallery Connect. * @return PurchaseIntentReq */ private fun createPurchaseIntentReq(type: Int,productId: String?): PurchaseIntentReq? { val req = PurchaseIntentReq() req?.let { productDetails -> productDetails.productId=productId productDetails.priceType=type productDetails.developerPayload="test" } return req } override fun onActivityResult(requestCode: Int,resultCode: Int,data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == 6666) { if (data == null) { Toast.makeText(this, "error", Toast.LENGTH_SHORT).show() return } val purchaseResultInfo =Iap.getIapClient(this).parsePurchaseResultInfoFromIntent(data) when (purchaseResultInfo.returnCode) { OrderStatusCode.ORDER_STATE_SUCCESS -> { // Verify signature of payment results. // CipherUtil is a custom class that contains the method for verifying signatures. You can find it in the demo project package provided in the Reference section. val success: Boolean = CipherUtil.doCheck(purchaseResultInfo.inAppPurchaseData, purchaseResultInfo.inAppDataSignature,resources.getString(R.string.publickey)) if (success) { // Call the consumeOwnedPurchase interface to consume it after successfully delivering the product to your user. consumeOwnedPurchase(this, purchaseResultInfo.inAppPurchaseData) } else { Toast.makeText(this, "Pay successful,sign failed", Toast.LENGTH_SHORT).show() } return } OrderStatusCode.ORDER_STATE_CANCEL -> { // The User cancels payment. Toast.makeText(this, "user cancel", Toast.LENGTH_SHORT).show() return } OrderStatusCode.ORDER_PRODUCT_OWNED -> { // The user has already owned the product. Toast.makeText(this, "you have owned the product", Toast.LENGTH_SHORT).show() // you can check if the user has purchased the product and decide whether to provide goods // if the purchase is a consumable product, consuming the purchase and deliver product return } else -> Toast.makeText(this, "Pay failed", Toast.LENGTH_SHORT).show() } return } }

      Payment screens:

  3. Consume the purchased product.
    After the gem product is successfully purchased, it will be delivered to the user, and then consumed using the consumeOwnedPurchase API once the delivery is successful. The user will then be able to purchase the product again.
    Java:
    /** * Consume the unconsumed purchase with type 0 after successfully delivering the product, then the Huawei payment server will update the order status and the user can purchase the product again. * * @param inAppPurchaseData JSON string that contains purchase order details. */ private void consumeOwnedPurchase(final Context context, String inAppPurchaseData) { Log.i("IAP", "call consumeOwnedPurchase"); IapClient mClient = Iap.getIapClient(context); Task<ConsumeOwnedPurchaseResult> task = mClient.consumeOwnedPurchase(createConsumeOwnedPurchaseReq(inAppPurchaseData)); task.addOnSuccessListener(new OnSuccessListener<ConsumeOwnedPurchaseResult>() { @Override public void onSuccess(ConsumeOwnedPurchaseResult result) { // Consume success Log.i("IAP", "consumeOwnedPurchase success"); Toast.makeText(context, "Pay success, and the product has been delivered", Toast.LENGTH_SHORT).show(); } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { Log.e("IAP", e.getMessage()); Toast.makeText(context, e.getMessage(), Toast.LENGTH_SHORT).show(); if (e instanceof IapApiException) { IapApiException apiException = (IapApiException)e; Status status = apiException.getStatus(); int returnCode = apiException.getStatusCode(); Log.e("IAP", "consumeOwnedPurchase fail,returnCode: " + returnCode); } else { // Other external errors } } }); } /** * Create a ConsumeOwnedPurchaseReq instance. * * @param purchaseData JSON string that contains purchase order details. * @return ConsumeOwnedPurchaseReq */ private ConsumeOwnedPurchaseReq createConsumeOwnedPurchaseReq(String purchaseData) { ConsumeOwnedPurchaseReq req = new ConsumeOwnedPurchaseReq(); // Parse purchaseToken from InAppPurchaseData in JSON format. try { InAppPurchaseData inAppPurchaseData = new InAppPurchaseData(purchaseData); req.setPurchaseToken(inAppPurchaseData.getPurchaseToken()); } catch (JSONException e) { Log.e("IAP", "createConsumeOwnedPurchaseReq JSONExeption"); } return req; }

    Kotlin:

    /** * Consume the unconsumed purchase with type 0 after successfully delivering the product, then the Huawei payment server will update the order status and the user can purchase the product again. * * @param inAppPurchaseData JSON string that contains purchase order details. */ private fun consumeOwnedPurchase(context: Context,inAppPurchaseData: String) { Log.i("IAP","call consumeOwnedPurchase") val mClient = Iap.getIapClient(context) val task =mClient.consumeOwnedPurchase(createConsumeOwnedPurchaseReq(inAppPurchaseData)) task.addOnSuccessListener { // Consume success Log.i("IAP","consumeOwnedPurchase success") Toast.makeText(context,"Pay success, and the product has been delivered",Toast.LENGTH_SHORT).show() }.addOnFailureListener { e -> Log.e("IAP", e.message) Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show() if (e is IapApiException) { val apiException = e val returnCode = apiException.statusCode Log.e("IAP","consumeOwnedPurchase fail,returnCode: $returnCode") } else { // Other external errors } } } /** * Create a ConsumeOwnedPurchaseReq instance. * * @param purchaseData JSON string that contains purchase order details. * @return ConsumeOwnedPurchaseReq */ private fun createConsumeOwnedPurchaseReq(purchaseData: String): ConsumeOwnedPurchaseReq? { val req = ConsumeOwnedPurchaseReq() // Parse purchaseToken from InAppPurchaseData in JSON format. try { val inAppPurchaseData = InAppPurchaseData(purchaseData) req.purchaseToken = inAppPurchaseData.purchaseToken } catch (e: JSONException) { Log.e("IAP","createConsumeOwnedPurchaseReq JSONExeption") } return req }

Well done. You have successfully completed this codelab and learned how to:

For more information, please click the following link:

Documentation
To download the sample code, please click the button below:

Download

Code copied