当用户在应用市场里下载APP,体验了免费的基础功能和服务后,经过自主选择付费,以获取和使用应用内提供的增值服务——这种用户应用内购买行为的完成,就须要用到应用内支付(In-App Purchases,简称IAP)。html
应用内支付有丰富的使用场景和需求,游戏类应用中装备和虚拟币的购买、视频类应用中会员的订阅、知识工具类应用中一次性购买开高级服务和功能……应用内支付大大拓宽了移动开发的盈利模式,增长了APP营收的灵活性。java
在华为移动生态体系中,咱们为开发者提供了安全好用的应用内支付基础开发服务,助力开发者降本增效,实现付费转化和持续营收。本文将详解应用内支付开通启用、商品管理、测试和上架等环节操做步骤,呈现应用内商品生成全过程,让开发者轻松实现从入门运用和熟练掌握华为应用内支付服务。git
IAP的启用及商品管理方法
启用服务
1.开启服务开关github
1.1. 登陆 AppGallery Connect 网站,选择“个人项目”,在项目列表中找到相应项目,在项目下的应用列表中选择须要开通服务的应用。json
1.2. 在所选应用页面中点击<个人项目>,进入“API管理”页签,打开须要开通服务所在行的开关。api
*API管理:在此页签确认和开通所需服务能力。支持一个服务有多个API开关,可单独控制各API开启或关闭,也可经过该服务右侧的API总开关一键开启或关闭该服务下的所有API。安全
2.配置支付服务参数
2.1. 在所选应用页面左侧导航栏选择[盈利 > 应用内支付服务],点击[设置]。
若是首次配置会弹出签署协议弹框。服务器
2.2. 点击“订阅通知地址”后的“✔”图标,配置订阅通知地址。app
2.3. 配置完成点击“✔”。ide
建立商品及沙盒测试
一、建立商品:全球订价,一个版本搞定
登陆AppAallery Connect,在[个人应用]界面选择应用,进入[运营]页面。
*数据信息为模拟
在[运营]页面左侧导航栏选择[产品运营 > 商品管理],选择[商品列表],点击[添加商品]。
1.1相关参数说明:
①确认建立的商品类型:华为应用内支付支持三种商品类型:
消耗型商品:便可以消耗使用的商品,好比游戏类应用中的金币、钻石、点券等,可用来兑换和购买应用内虚拟服务和物品的货币。
非消耗型商品:即购买后永久使用的商品。好比游戏中特别篇关卡、教育类应用中无限时的课程学习权限等
订阅型商品:即一种预约支付方式,购买后将来一段时间内容许访问增值更能或内容,周期结束后自动续期购买下一期的服务。好比音视频类、教育课程类应用中的月度会员等。
根据你所需添加的商品属性,来勾选商品类型进行进一步填写。
②商品ID:以大小写字母及数字开头,由字母、数字及下划线 (_) 和句点 (.)组成,字符输入上限148个,一个应用内商品ID不能重复,保存后不能修改,删除后没法再次使用。
③语言:点击[管理语言列表],勾选需支持的语言种类
④商品名称:不能为空,字符上限55个,不支持特殊字符|
⑤商品简介:不能为空,字符上限100个,不支持特殊字符|
⑥商品价格:点击“查看编辑”,为商品适配合适价格。
1.2 面向全球的商品信息设置:若是你的应用开发面向全球多个国家,无需维护管理多个地域版本。
多语言设置:建立商品时,在<语言>栏,勾选需支持的国家语言,并在<商品名称>一栏,输入对应国家语言的商品名及信息。
全球实时汇率订价参考:不一样国家及地域的价格设置,只需定义一个商品价格,不一样国家和地区价格会根据当地汇率来计算并显示,给开发者提供当地订价依据,也可自定义单个地域订价。
商品信息填写完成后,点击[编辑价格],便可进入商品价格编辑界面。
根据以上操做步骤,完成商品建立,回到商品列表页面进行商品的状态编辑,点击<生效>,即添加商品完成。
相关参数说明:
- 通用币种要求国家/地区:支持整数或两位小数,如输入1.34,则默认选取1.34做为该商品的输入价格;
- 特殊币种要求国家/地区:
- 仅支持整数的国家/地区,整数或向上取整做为输入价格,如输入5.02,则默认选取6.00做为该商品的输入价格;
- 仅以五分之一为最小单位的国家/地区,整数或向上取符合五分之一要求的数值做为输入价格,如输入1.23,则默认选取1.40做为该商品的输入价格。
1.3商品激活
填写好信息点击[保存],返回商品列表,新添加商品会显示处于[失效]状态,点击确认[激活],商品即生效。
二、客户端接入,支付过程开发指南
2.1 判断是否支持应用内支付
在使用应用内支付以前,你的应用需向华为应用内支付发送isEnvReady请求,以此判断用户当前登陆的华为账号所在的服务地是否在华为IAP支持结算的国家/地区中。若是应用未接入华为账号的登陆接口,可经过该接口完成登陆操做。
开发步骤以下:
发起isEnvReady请求,并设置两个回调监听来接收接口请求的结果。
• 当接口请求成功时,你的应用将获取到一个IsEnvReadyResult实例对象,表示用户当前登陆的华为账号所在的服务地支持IAP。
• 当接口请求失败时,IAP会返回一个Exception对象,若该对象为IapApiException对象,可以使用其getStatusCode()方法获取这次请求的返回码。
当返回账号未登陆(OrderStatusCode.ORDER_HWID_NOT_LOGIN)时,可以使用IapApiException对象中的status拉起华为账号登陆页面,此后在Activity的onActivityResult方法中获取结果信息。从onActivityResult返回的intent中解析出returnCode,当returnCode=OrderStatusCode.ORDER_STATE_SUCCESS时,则表示当前账号所在服务地支持IAP,其余则表示这次请求有异常。
*Status不支持序列化,请勿对IAP接口返回的Status对象执行序列化操做。
// 获取调用接口的Activity对象 final Activity activity = getActivity(); Task<IsEnvReadyResult> task = Iap.getIapClient(activity).isEnvReady(); task.addOnSuccessListener(new OnSuccessListener<IsEnvReadyResult>() { @Override public void onSuccess(IsEnvReadyResult result) { // 获取接口请求的结果 int accountFlag = result.getAccountFlag(); } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { if (e instanceof IapApiException) { IapApiException apiException = (IapApiException) e; Status status = apiException.getStatus(); if (status.getStatusCode() == OrderStatusCode.ORDER_HWID_NOT_LOGIN) { // 未登陆账号 if (status.hasResolution()) { try { // 6666是您自定义的常量 // 启动IAP返回的登陆页面 status.startResolutionForResult(activity, 6666); } catch (IntentSender.SendIntentException exp) { } } } else if (status.getStatusCode() == OrderStatusCode.ORDER_ACCOUNT_AREA_NOT_SUPPORTED) { // 用户当前登陆的华为账号所在的服务地不在华为IAP支持结算的国家/地区中 } } else { // 其余外部错误 } } }); @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 6666) { if (data != null) { // 使用parseRespCodeFromIntent方法获取接口请求结果 int returnCode = IapClientHelper.parseRespCodeFromIntent(data); // 使用parseAccountFlagFromIntent方法获取接口返回的账号类型 int accountFlag = IapClientHelper.parseAccountFlagFromIntent(data); } } }
2.2 展现商品信息
在华为AppGallery Connect网站上完成商品的配置后,需在你的应用中使用obtainProductInfo接口来获取此类商品的详细信息。
开发步骤以下:
1. 构建请求参数ProductInfoReq,发起obtainProductInfo请求并设置OnSuccessListener和OnFailureListener回调监听器以接收接口请求的结果。您须要在ProductInfoReq中携带您此前已在华为AppGallery Connect网站上定义并生效的商品ID,并根据实际配置的商品指定其priceType。
*obtainProductInfo每次只能查询一种类型的商品。
2. 当接口请求成功时,IAP将返回一个ProductInfoResult对象,您的应用可经过该对象的getProductInfoList方法获取到包含了单个商品信息的ProductInfo对象的列表。您可使用ProductInfo对象包含的商品价格、名称和描述等信息,向用户展现可供购买的商品列表。
List<String> productIdList = new ArrayList<>(); // 查询的商品必须是您在AppGallery Connect网站配置的商品 productIdList.add("ConsumeProduct1001"); ProductInfoReq req = new ProductInfoReq(); // priceType: 0:消耗型商品; 1:非消耗型商品; 2:订阅型商品 req.setPriceType(0); req.setProductIds(productIdList); // 获取调用接口的Activity对象 final Activity activity = getActivity(); // 调用obtainProductInfo接口获取AppGallery Connect网站配置的商品的详情信息 Task<ProductInfoResult> task = Iap.getIapClient(activity).obtainProductInfo(req); task.addOnSuccessListener(new OnSuccessListener<ProductInfoResult>() { @Override public void onSuccess(ProductInfoResult result) { // 获取接口请求成功时返回的商品详情信息 List<ProductInfo> productList = result.getProductInfoList(); } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { if (e instanceof IapApiException) { IapApiException apiException = (IapApiException) e; int returnCode = apiException.getStatusCode(); } else { // 其余外部错误 } } });
2.3 发起购买
AppGallery Connect网站支持托管的商品包括消耗型商品,非消耗型商品和订阅型商品。您的应用可经过createPurchaseIntent接口发起购买请求。开发步骤以下:
1. 构建请求参数PurchaseIntentReq,发起createPurchaseIntent请求。您须要在PurchaseIntentReq中携带您此前已在AGC网站上定义并生效的商品ID。当接口请求成功时,您可获取到一个PurchaseIntentResult对象,其getStatus方法返回了一个Status对象,您的应用须要经过Status对象的startResolutionForResult方法来启动华为IAP收银台。
*Status不支持序列化,请勿对IAP接口返回的Status对象执行序列化操做。
// 构造一个PurchaseIntentReq对象 PurchaseIntentReq req = new PurchaseIntentReq(); // 经过createPurchaseIntent接口购买的商品必须是您在AppGallery Connect网站配置的商品。 req.setProductId("CProduct1"); // priceType: 0:消耗型商品; 1:非消耗型商品; 2:订阅型商品 req.setPriceType(0); req.setDeveloperPayload("test"); // 获取调用接口的Activity对象 final Activity activity = getActivity(); // 调用createPurchaseIntent接口建立托管商品订单 Task<PurchaseIntentResult> task = Iap.getIapClient(activity).createPurchaseIntent(req); task.addOnSuccessListener(new OnSuccessListener<PurchaseIntentResult>() { @Override public void onSuccess(PurchaseIntentResult result) { // 获取建立订单的结果 Status status = result.getStatus(); if (status.hasResolution()) { try { // 6666是您自定义的常量 // 启动IAP返回的收银台页面 status.startResolutionForResult(activity, 6666); } catch (IntentSender.SendIntentException exp) { } } } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { if (e instanceof IapApiException) { IapApiException apiException = (IapApiException) e; Status status = apiException.getStatus(); int returnCode = apiException.getStatusCode(); } else { // 其余外部错误 } } });
2. 在你的应用拉起收银台而且当用户完成支付后(成功购买商品或取消购买),华为IAP会经过onActivityResult方式将这次支付结果返回给应用。可以使用parsePurchaseResultInfoFromIntent方法获取包含结果信息的PurchaseResultInfo对象。
• 当用户购买成功时,可从PurchaseResultInfo对象中获取到购买数据InAppPurchaseData及其签名数据,您须要使用在华为AppGallery Connect分配的公钥进行签名验证,公钥获取和验证方法请参见对返回结果验签。
• 用户购买消耗型商品时,若是返回如下支付异常则须要检查是否存在掉单状况,具体请参见消耗型商品的补单流程。
• 支付失败(OrderStatusCode.ORDER_STATE_FAILED)
• 已拥有该商品(OrderStatusCode.ORDER_PRODUCT_OWNED)
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 6666) { if (data == null) { Log.e("onActivityResult", "data is null"); return; } // 调用parsePurchaseResultInfoFromIntent方法解析支付结果数据 PurchaseResultInfo purchaseResultInfo = Iap.getIapClient(this).parsePurchaseResultInfoFromIntent(data); switch(purchaseResultInfo.getReturnCode()) { case OrderStatusCode.ORDER_STATE_CANCEL: // 用户取消 break; case OrderStatusCode.ORDER_STATE_FAILED: case OrderStatusCode.ORDER_PRODUCT_OWNED: // 检查是否存在未发货商品 break; case OrderStatusCode.ORDER_STATE_SUCCESS: // 支付成功 String inAppPurchaseData = purchaseResultInfo.getInAppPurchaseData(); String inAppPurchaseDataSignature = purchaseResultInfo.getInAppDataSignature(); // 使用您应用的IAP公钥验证签名 // 若验签成功,则进行发货 // 若用户购买商品为消耗型商品,您须要在发货成功后调用consumeOwnedPurchase接口进行消耗 break; default: break; } } }
2.4 确认交易
用户完成一次支付以后,你需根据购买数据InAppPurchaseData的purchaseState字段来判断订单是否已成功支付。若purchaseState为已支付(取值为0),你需发放相应的商品或提供相应的服务,此后须要向华为IAP发送发货确认请求。
- 对于消耗型商品,你需从InAppPurchaseData JSON字符串中解析出purchaseToken信息,用于确认商品的发货状态。在成功发货并记录已发货的商品的purchaseToken以后,你的应用须要使用consumeOwnedPurchase接口消耗该商品,以此通知华为应用内支付服务器更新商品的发货状态。发送consumeOwnedPurchase请求时,请在请求参数中携带purchaseToken。应用成功执行消耗以后,华为应用内支付服务器会将相应商品从新设置为可购买状态,用户便可再次购买该商品。
// 构造ConsumeOwnedPurchaseReq对象 ConsumeOwnedPurchaseReq req = new ConsumeOwnedPurchaseReq(); String purchaseToken = ""; try { // purchaseToken需从购买信息InAppPurchaseData中获取 InAppPurchaseData inAppPurchaseDataBean = new InAppPurchaseData(inAppPurchaseData); purchaseToken = inAppPurchaseDataBean.getPurchaseToken(); } catch (JSONException e) { } req.setPurchaseToken(purchaseToken); // 获取调用接口的Activity对象 final Activity activity = getActivity(); // 消耗型商品发货成功后,需调用consumeOwnedPurchase接口进行消耗 Task<ConsumeOwnedPurchaseResult> task = Iap.getIapClient(activity).consumeOwnedPurchase(req); task.addOnSuccessListener(new OnSuccessListener<ConsumeOwnedPurchaseResult>() { @Override public void onSuccess(ConsumeOwnedPurchaseResult result) { // 获取接口请求结果 } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { if (e instanceof IapApiException) { IapApiException apiException = (IapApiException) e; Status status = apiException.getStatus(); int returnCode = apiException.getStatusCode(); } else { // 其余外部错误 } } });
- 对于非消耗型商品,华为应用内支付服务器默认返回已确认的订单数据,在用户购买成功以后无需确认交易。您须要在用户购买成功以后持续向用户提供相应的商品服务。具体请参见提供非消耗型商品对应的服务。
- 对于订阅型商品,在用户购买成功后,无需您额外执行确认交易操做,但须要在订阅生效期间持续向用户提供相应的商品服务,具体请参见订阅专用功能说明-提供商品对应的服务。
2.5提供非消耗型商品对应的服务
若你的应用为用户提供非消耗型商品,可在应用启动时经过obtainOwnedPurchases接口获取用户已购的非消耗型商品的购买信息,格式请参见InAppPurchaseData。若返回的购买信息列表不为空,请确认每一个购买信息的purchaseState字段。若purchaseState为0,你需提供相应的商品服务。
开发步骤以下:
1. 使用obtainOwnedPurchases获取用户已购非消耗型商品的信息。
2. 你的应用须要在请求参数OwnedPurchasesReq中指定查询的priceType为1。
3. 你可从返回的每一个商品信息中解析出purchaseState,用于判断当前商品的购买状态,以此做为你的应用的发货标志。
// 构造一个OwnedPurchasesReq对象 OwnedPurchasesReq ownedPurchasesReq = new OwnedPurchasesReq(); // priceType: 1:非消耗型商品 ownedPurchasesReq.setPriceType(1); // 获取调用接口的Activity对象 final Activity activity = getActivity(); // 调用obtainOwnedPurchases接口 Task<OwnedPurchasesResult> task = Iap.getIapClient(activity).obtainOwnedPurchases(ownedPurchasesReq); task.addOnSuccessListener(new OnSuccessListener<OwnedPurchasesResult>() { @Override public void onSuccess(OwnedPurchasesResult result) { // 获取接口请求结果 if (result != null && result.getInAppPurchaseDataList() != null) { for (int i = 0; i < result.getInAppPurchaseDataList().size(); i++) { String inAppPurchaseData = result.getInAppPurchaseDataList().get(i); String inAppSignature = result.getInAppSignature().get(i); // 您须要使用您的应用的IAP公钥验证inAppPurchaseData的签名 // 若是验签成功,请检查支付状态 try { InAppPurchaseData inAppPurchaseDataBean = new InAppPurchaseData(inAppPurchaseData); int purchaseState = inAppPurchaseDataBean.getPurchaseState(); } catch (JSONException e) { } } } } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { if (e instanceof IapApiException) { IapApiException apiException = (IapApiException) e; Status status = apiException.getStatus(); int returnCode = apiException.getStatusCode(); } else { // 其余外部错误 } } });
三、沙盒测试:无需真实支付,完成端到端支付测试
添加商品完毕后,可经过沙盒测试,在无需真实付款的条件下,进行端到端的支付环节检测。沙盒测试步骤以下:
3.1配置沙盒测试环境
· 设置测试账号。
在进行测试前,需在AppGallery Connect中的用户与访问添加测试账号,这些测试账号都是真实的华为账号。具体请参见<管理测试账号>。
*说明:沙盒测试账号添加完成以后须要30min~1h才能生效。使用时请检查当前的账号是否支持沙盒测试。
· 配置沙盒测试版本。
若是要测试的应用包此前没有在AGC上架过版本,只须要确保测试包的versionCode大于0;若是已有上架的版本,则测试包的versionCode须要大于上架版本的versionCode。
3.2测试非订阅型商品支付
可在设备上登陆已配置的测试账号,并安装该待测试应用。发起非订阅型商品购买时,华为IAP会检测到该用户为测试用户,跳过实际支付环节,直接支付成功。
效果示例以下:
3.3测试订阅型商品续订
订阅型商品的购买流程和普通商品(非订阅)的购买流程相似,但订阅还有其余细节场景,好比续订成功或失败,续订周期时长。为了帮助开发者快速测试应用的订阅场景,沙盒环境下的订阅续订时间会比正常状况更快,引入“时光机”概念。
时光机:仅针对订阅型商品的续期时间,不影响订阅型商品的生效时间(好比订阅周期为1周,商品在3分钟后发生续期,此时订阅型商品有效期延长了1周)。
效果示例以下:
完成以上步骤,你就顺利搞定应用内商品建立和测试上架。商品管理环节,商品信息修改、失效激活商品、删除商品等操做步骤,咱们下期继续。
华为应用内支付还提供了“零掉单”的订单管理、提营收增加的加强型订阅服务等能力,咱们也会在后期为你们推送超细详解,敬请期待哦~
了解更多详情,请戳:
>>华为应用内支付服务官网
>>获取开发指导文档
>>华为移动服务开源仓库地址:GitHub、Gitee
点击右上角头像右方的关注,第一时间了解华为移动服务最新技术~