Overview

HUAWEI In-App Purchases (IAP) aggregates multiple payment methods for global payments, and can be easily integrated into your app to help your increase revenue. Users can purchase a variety of products or services, including common virtual products and subscriptions, directly within your app. For details, please refer to HUAWEI IAP 5.0.

First of all, you need to understand three types of in-app products supported by HUAWEI IAP.

Type

Description

Example

Consumable

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

Extra lives and gems in a game

Non-consumable

Non-consumables are purchased once and do not expire.

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

Auto-renewable subscriptions

Users can purchase access to value-added functions or content in a specified period of time. The subscriptions are automatically renewed on a recurring basis until users decide to cancel.

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

You need to perform the following operations:

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 HUAWEI IAP, you must complete the following preparations:

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

You need to enable HUAWEI IAP before using it.

  1. Sign in to AppGallery Connect and click My projects.
  2. Find your project and click the app for which you want to enable HUAWEI IAP.
  3. On the page that is displayed, go to Project Setting > Manage APIs. Then enable In-App Purchases.
  4. Go to Earning > In-App Purchases from the navigation tree on the left and click Settings.
    If this is the first time you configure HUAWEI IAP, a dialog box will be displayed for you to sign the agreement.
  5. After the configuration is successful, the page displays the public key used for subsequent payment signature verification and a parameter for configuring the subscription notification URL.

Configure a consumable of 10 gems that users can purchase, consume, or purchase again. The configuration procedure is as follows:

  1. Sign in to AppGallery Connect and select My apps.
  2. Select the app for which you want to add a product.
  3. Click the Operate tab. Go to Product Operation > Product Management from the navigation tree on the left, click the Products tab, and click Add Product.
  4. Configure product information and click Save.
  5. In the dialog box that is displayed, click OK to validate the product information.
    After the configuration is complete, the product is in a valid state in the product list.

The HMS Core SDK provides an entry for all external APIs of the IAP. For Android Studio, Huawei provides the HMS Core SDK that can be integrated by using the Maven repository. Before development, you need to add the HMS Core SDK to your Android Studio project. Once this is done, you can call the APIs encapsulated in the HMS Core SDK to connect to the IAP.

Obtaining the Configuration File

  1. Sign in to AppGallery Connect, click My projects, find your project, and click your app. On the page that is displayed, go to Project Setting > General information.
  2. In the App information area, click agconnect-services.json to download the configuration file.
  3. Copy the agconnect-services.json file to the app's root directory.

Adding Build Dependencies

  1. Open the build.gradle file in the app directory.
  2. Add build dependencies in the dependencies block.
    dependencies { implementation 'com.huawei.hms:iap:4.0.4.301' }
  3. Open the modified build.gradle file again. You will find a Sync Now link in the upper right corner of the page. Click Sync Now and wait until the synchronization is complete.

Configuring Obfuscation Scripts

Before building the APK, configure the obfuscation configuration file to prevent the HMS Core SDK from being obfuscated.

  1. Open the obfuscation configuration file proguard-rules.pro of your project.
  2. Add configurations to exclude the HMS Core SDK from obfuscation.
    -ignorewarning -keepattributes *Annotation* -keepattributes Exceptions -keepattributes InnerClasses -keepattributes Signature -keepattributes SourceFile,LineNumberTable -keep class com.hianalytics.android.**{*;} -keep class com.huawei.updatesdk.**{*;} -keep class com.huawei.hms.**{*;}

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

Step 1: Display product information.

Call the obtainProductInfo API to query the gem product configured in AppGallery Connect. If the API call is successful, the gem product is displayed on the phone based on the returned product information.
DemoActivity.kt

/** * 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(TAG, 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 product information page is similar to the following.

Step 2: Purchase the product.

A user can tap the gem icon on the page to purchase the product. This process uses the createPurchaseIntent API to complete the purchase.
1. Create a task for requesting the createPurchaseIntent API.
2. Set OnSuccessListener and OnFailureListener for the task to receive the API request result.

DemoActivity.kt

/** * to show the products * @param productInfoList Product list */ private fun showProduct(productInfoList: List<ProductInfo>) { for (productInfo in productInfoList) { var productsinfo = ProductsListModel(productInfo.productName,productInfo.price,productInfo.productId,R.drawable.blue_ball) productsListModels.add(productsinfo) val adapter = ProductsListAdapter(productsListModels, productItemClick) itemlist.adapter=adapter } } var productItemClick: ProductItemClick = object : ProductItemClick { override fun onClick(data: ProductsListModel?) { val productId: String = data?.id.toString() Log.d("productId",""+productId) 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(TAG,"call createPurchaseIntent") val mClient = Iap.getIapClient(activity) val task = mClient.createPurchaseIntent(createPurchaseIntentReq(type, productId)) task.addOnSuccessListener(OnSuccessListener { result -> Log.i(TAG,"createPurchaseIntent, onSuccess") if (result == null) { Log.e(TAG,"result is null") return@OnSuccessListener } val status = result.status if (status == null) { Log.e(TAG,"status is null") return@OnSuccessListener } // you should pull up the page to complete the payment process. if (status.hasResolution()) { try { status.startResolutionForResult(activity,REQ_CODE_BUY) } catch (exp: SendIntentException) { Log.e(TAG,exp.message) } } else { Log.e(TAG,"intent is null") } }).addOnFailureListener { e -> Log.e(TAG, e.message) Toast.makeText(activity, e.message, Toast.LENGTH_SHORT).show() if (e is IapApiException) { val returnCode = e.statusCode Log.e(TAG,"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 == REQ_CODE_BUY) { 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. 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 } }

The payment pages are similar to the following.

Step 3: Consume the purchased product.

After a user successfully purchases the gem product, it will be delivered to the user, and consumed using the consumeOwnedPurchase API once the delivery is successful. The user will then be able to purchase the product again.
DemoActivity.kt

/** * 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(TAG,"call consumeOwnedPurchase") val mClient = Iap.getIapClient(context) val task =mClient.consumeOwnedPurchase(createConsumeOwnedPurchaseReq(inAppPurchaseData)) task.addOnSuccessListener { // Consume success Log.i(TAG,"consumeOwnedPurchase success") Toast.makeText(context,"Pay success, and the product has been delivered",Toast.LENGTH_SHORT).show() }.addOnFailureListener { e -> Log.e(TAG, e.message) Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show() if (e is IapApiException) { val apiException = e val returnCode = apiException.statusCode Log.e(TAG,"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(TAG,"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