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:
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:
In this codelab, you will learn how to:
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.
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:
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.
dependencies {
implementation 'com.huawei.hms:iap:4.0.4.301'
}
Before building the APK, configure the obfuscation configuration file to prevent the HMS Core SDK from being obfuscated.
-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.**{*;}
"R.string.hms*",
"R.string.connect_server_fail_prompt_toast",
"R.string.getting_message_fail_prompt_toast",
"R.string.no_available_network_prompt_toast",
"R.string.third_app_*",
"R.string.upsdk_*",
"R.string.agc*",
"R.layout.hms*",
"R.layout.upsdk_*",
"R.drawable.upsdk*",
"R.color.upsdk*",
"R.dimen.upsdk*",
"R.style.upsdk*"
This section describes a payment process. You need to sign in with your HUAWEI ID on your phone before making a payment.
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.
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.
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: