简介

华为应用内支付(In-App Purchases,IAP)服务聚合了多种支付通道,支持全球支付,为应用提供便捷的应用内支付体验,且接入流程简便,可以帮助您快速获取收入。通过应用内支付,用户可以在您的应用内购买各种类型的商品或服务,包括普通虚拟商品和订阅商品服务。具体可参阅支付服务HMS4.0

在此之前,您需要了解IAP支持的几种商品类型:

商品定义

描述

示例

消耗型商品

仅能使用一次,消耗使用后即刻失效,需再次购买。

游戏中额外生命、钻石等

非消耗型商品

一次性购买,永久拥有,无需消耗。

游戏中额外的游戏关卡或应用中无时限的高级会员

订阅型商品

用户购买后在一段时间内允许访问增值功能或内容,周期结束后自动续期购买下一期的服务。

应用中有时限的高级会员,如视频月度会员等

开发者需要,

您将建立什么

在这个Codelab中,您将创建一个demo,通过调用华为应用内支付服务的API来模拟游戏中购买宝石的过程。在Demo Project中您可以体验到:

您将会学到什么

硬件要求

软件要求

集成HUAWEI HMS Core能力,需要完成以下准备工作

具体操作,请按照《HUAWEI HMS Core集成准备》中详细说明来完成。

在接入IAP之前应用需要开通支付服务。
1、使用已注册的开发者帐号登录 AppGallery Connect 网站 ,选择"我的应用"。

2、选择需要开通服务的应用所属产品的名称,进入应用信息页面。
3、点击"开发"页签,点击"概览"后的图标,选择"API管理"。

4、打开In-App Purchase所在行的开关。

5、在"开发"页签,左侧导航选择"商业服务 > 应用内支付",点击"设置"。
如果首次配置会弹出签署协议弹框。

设置成功后,界面展示后续用于支付验签的公钥和配置订阅通知地址的入口,如下图所示。

配置一个商品,表示10个宝石,类型为消耗型商品,购买完成后通过执行消耗操作即可再次购买。下面是配置流程:
1、登录 AppGallery Connect 网站,选择"我的应用"。
2、在产品列表中找到应用所在的产品,点击相关应用链接。

3、选择"运营"页签,在左侧导航栏选择"产品运营 > 商品管理",选择"商品列表"页签,点击"添加商品"。

4、配置商品信息,完成后点击"保存"。

5、在弹出生效提示框,点击"确定",完成配置。
配置完成后,商品列表中此商品的状态为"有效"状态。

HMS SDK中提供了IAP所有对外接口的入口。针对Android Studio开发环境,华为提供了maven仓集成方式的HMS SDK包。在开始开发前,您需要将HMS SDK集成到您的Android Studio项目中,之后您可以通过调用HMS SDK封装好的接口方法来接入IAP。

获取配置文件

添加编译依赖

配置混淆脚本

开发者编译APK前需要配置不要混淆HMS SDK,避免功能异常。

本章节为您简单展示一次支付的流程。在此之前,您需要在手机上登录华为帐号。

步骤1:展示商品信息

通过obtainProductInfo接口查询已在AGC系统中配置的宝石商品,成功回调场景下根据返回的商品信息在您的界面中展示宝石商品。
DemoActivity.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()) { showProduct(result.getProductInfoList()); } } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { Log.e(TAG, 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.INAPP_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; }

效果如下:

步骤2:购买商品

用户点击页面显示的宝石图标,可对商品进行购买。该过程使用createPurchaseIntent接口来完成购买。

  1. 创建createPurchaseIntent请求的task。
  2. 为task设置两个监听器OnSuccessListenerOnFailureListener来接收接口请求结果。
  3. ● 成功场景下返回一个PurchaseIntentResult对象。您需要拉起返回的页面来完成支付流程,结果在onActivityResult中回调。在onActivityResult中,可以使用IapClient中的parsePurchaseResultInfoFromIntent方法获取到一个PurchaseResultInfo对象,该对象包括了此次支付的结果信息。支付成功场景下,应用需要参考对返回结果验签的方法并使用支付公钥对返回数据进行验签。检验成功之后,发放道具,成功后执行消耗操作。支付失败场景下,会返回不同的returnCode,可参考异常处理及通用错误码 并根据实际情况进行处理。

    ● 失败场景下返回一个Exception对象,若该对象为IapApiException对象,可使用getStatusCode获取返回的错误码,具体可参考异常处理及通用错误码

DemoActivity.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.INAPP_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(TAG, "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(TAG, "createPurchaseIntent, onSuccess"); if (result == null) { Log.e(TAG, "result is null"); return; } Status status = result.getStatus(); if (status == null) { Log.e(TAG, "status is null"); return; } // you should pull up the page to complete the payment process. if (status.hasResolution()) { try { status.startResolutionForResult(activity, REQ_CODE_BUY); } catch (IntentSender.SendIntentException exp) { Log.e(TAG, exp.getMessage()); } } else { Log.e(TAG, "intent is null"); } } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { Log.e(TAG, 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(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 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 == REQ_CODE_BUY) { 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. 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; } }

效果图如下:

步骤3:消耗商品

当用户购买宝石成功之后,您需要向用户发放相应数量的宝石,发货成功后调用consumeOwnedPurchase接口执行消耗操作,之后可重复购买该商品。
DemoActivity.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(TAG, "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(TAG, "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(TAG, 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(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 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(TAG, "createConsumeOwnedPurchaseReq JSONExeption"); } return req; }

干得好,你已经成功完成了Codelab并学到了:

您可以阅读下面链接,了解更多相关信息。
相关文档

您可以在Github上下载源码:github.com/Huawei/Consumer/tree/master/Codelabs/IAP

也可以点击下方按钮下载源码。

源码下载

已复制代码