从零开始详解应用内支付——商品创建及测试上架
当用户在应用市场里下载APP,体验了免费的基础功能和服务后,通过自主选择付费,以获取和使用应用内提供的增值服务——这种用户应用内购买行为的完成,就需要用到应用内支付(In-App Purchases,简称IAP)。
应用内支付有丰富的使用场景和需求,游戏类应用中装备和虚拟币的购买、视频类应用中会员的订阅、知识工具类应用中一次性购买开高级服务和功能……应用内支付大大拓宽了移动开发的盈利模式,增加了APP营收的灵活性。
在华为移动生态体系中,我们为开发者提供了安全好用的应用内支付基础开发服务,助力开发者降本增效,实现付费转化和持续营收。
本文将详解应用内支付开通启用、商品管理、测试和上架等环节操作步骤,呈现应用内商品生成全过程,让开发者轻松实现从入门运用和熟练掌握华为应用内支付服务。
IAP的启用及商品管理方法
启用服务
1.开启服务开关
1.1. 登录 AppGallery Connect 网站,选择“我的项目”,在项目列表中找到相应项目,在项目下的应用列表中选择需要开通服务的应用。
1.2. 在所选应用页面中点击<我的项目>,进入“API管理”页签,打开需要开通服务所在行的开关。
*API管理:在此页签确认和开通所需服务能力。支持一个服务有多个API开关,可单独控制各API开启或关闭,也可通过该服务右侧的API总开关一键开启或关闭该服务下的全部API。
2.配置支付服务参数
2.1. 在所选应用页面左侧导航栏选择[ 盈利 > 应用内支付服务 ],点击[ 设置 ]。
如果首次配置会弹出签署协议弹框。
2.2. 点击“订阅通知地址”后的“√”图标,配置订阅通知地址。
2.3. 配置完成点击“√”。
创建商品及沙盒测试
1、创建商品:全球定价,一个版本搞定
登录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、客户端接入,支付过程开发指南
2.1判断是否支持应用内支付
发起isEnvReady请求,并设置两个回调监听来接收接口请求的结果。
- 当接口请求成功时,你的应用将获取到一个IsEnvReadyResult实例对象,表示用户当前登录的华为帐号所在的服务地支持IAP。
- 当接口请求失败时,IAP会返回一个Exception对象,若该对象为IapApiException对象,可使用其getStatusCode()方法获取此次请求的返回码。
*Status不支持序列化,请勿对IAP接口返回的Status对象执行序列化操作。
// 获取调用接口的Activity对象
final Activity activity = getActivity();
Task<IsEnvReadyResult> task = Iap.getIapClient(activity).isEnvReady();
task.addOnSuccessListener(new OnSuccessListener<IsEnvReadyResult>() {@Overridepublic void onSuccess(IsEnvReadyResult result) {// 获取接口请求的结果int accountFlag = result.getAccountFlag();}
}).addOnFailureListener(new OnFailureListener() {@Overridepublic 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);}}
}
在华为AppGallery Connect网站上完成商品的配置后,需在你的应用中使用obtainProductInfo接口来获取此类商品的详细信息。
- 构建请求参数ProductInfoReq,发起obtainProductInfo请求并设置
OnSuccessListener和OnFailureListener回调监听器以接收接口请求的结果。您需要在ProductInfoReq中携带您此前已在华为AppGallery Connect网站上定义并生效的商品ID,并根据实际配置的商品指定其priceType。
*obtainProductInfo每次只能查询一种类型的商品。 - 当接口请求成功时,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>() {@Overridepublic void onSuccess(ProductInfoResult result) {// 获取接口请求成功时返回的商品详情信息List<ProductInfo> productList = result.getProductInfoList();}
}).addOnFailureListener(new OnFailureListener() {@Overridepublic void onFailure(Exception e) {if (e instanceof IapApiException) {IapApiException apiException = (IapApiException) e;int returnCode = apiException.getStatusCode();} else {// 其他外部错误}}
});
AppGallery Connect网站支持托管的商品包括消耗型商品,非消耗型商品和订阅型商品。您的应用可通过createPurchaseIntent接口发起购买请求。开发步骤如下:
- 构建请求参数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>() {@Overridepublic 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() {@Overridepublic void onFailure(Exception e) {if (e instanceof IapApiException) {IapApiException apiException = (IapApiException) e;Status status = apiException.getStatus();int returnCode = apiException.getStatusCode();} else {// 其他外部错误}} });
- 在你的应用拉起收银台并且当用户完成支付后(成功购买商品或取消购买),华为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 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>() {@Overridepublic void onSuccess(ConsumeOwnedPurchaseResult result) {// 获取接口请求结果} }).addOnFailureListener(new OnFailureListener() {@Overridepublic void onFailure(Exception e) {if (e instanceof IapApiException) {IapApiException apiException = (IapApiException) e;Status status = apiException.getStatus();int returnCode = apiException.getStatusCode();} else {// 其他外部错误}} });
- 对于非消耗型商品,华为应用内支付服务器默认返回已确认的订单数据,在用户购买成功之后无需确认交易。您需要在用户购买成功之后持续向用户提供相应的商品服务。具体请参见提供非消耗型商品对应的服务。
- 对于订阅型商品,在用户购买成功后,无需您额外执行确认交易操作,但需要在订阅生效期间持续向用户提供相应的商品服务,具体请参见订阅专用功能说明-提供商品对应的服务。
2.5 提供非消耗型商品对应的服务
- 使用obtainOwnedPurchases获取用户已购非消耗型商品的信息。
- 你的应用需要在请求参数OwnedPurchasesReq中指定查询的priceType为1。
- 你可从返回的每个商品信息中解析出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>() {@Overridepublic 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() {@Overridepublic void onFailure(Exception e) {if (e instanceof IapApiException) {IapApiException apiException = (IapApiException) e;Status status = apiException.getStatus();int returnCode = apiException.getStatusCode();} else {// 其他外部错误}}
});
3、沙盒测试:无需真实支付,完成端到端支付测试
如果要测试的应用包此前没有在AGC上架过版本,只需要确保测试包的versionCode大于0;如果已有上架的版本,则测试包的versionCode需要大于上架版本的versionCode。
3.2测试非订阅型商品支付
可在设备上登录已配置的测试帐号,并安装该待测试应用。发起非订阅型商品购买时,华为IAP会检测到该用户为测试用户,跳过实际支付环节,直接支付成功。
效果示例如下:
时光机:仅针对订阅型商品的续期时间,不影响订阅型商品的生效时间(比如订阅周期为1周,商品在3分钟后发生续期,此时订阅型商品有效期延长了1周)。
效果示例如下:
完成以上步骤,你就顺利搞定应用内商品创建和测试上架。商品管理环节,商品信息修改、失效激活商品、删除商品等操作步骤,我们下期继续。
华为应用内支付还提供了“零掉单”的订单管理、提营收增长的增强型订阅服务等能力,我们也会在后期为大家推送超细详解,敬请期待哦~
从零开始详解应用内支付——商品创建及测试上架相关推荐
- WEB前端之网页设计③----最新最全详解/如何在网页上创建表格
WEB前端之网页设计③--最新最全详解/如何在网页上创建表格 一.表格效果图1: <table border="0px" cellspacing="1px" ...
- mysql如何进行压测_详解MySQL如何按表创建千万级的压测数据
有时我们要对系统做压测,或者数据库压力测试,这时候需要对某些表插入几百万或者上千万数据,下面介绍下怎么利用MySQL循环和存储过程对特定表的创建千万行数据. 1. 准备测试表 CREATE TABLE ...
- 新手入门 - 详解 frp 内网穿透 frpc.ini 配置
本文为 Stille 原创文章.经实践,测试,整理发布.如需转载请联系作者获得授权,并注明转载地址. 转载地址:新手入门 - 详解 frp 内网穿透 frpc.ini 配置 - 思有云 - IOIOX ...
- Java 多线程详解(二)------如何创建进程和线程
Java 多线程详解(一)------概念的引入:https://blog.csdn.net/weixin_39816740/article/details/80089790 在上一篇博客中,我们已经 ...
- 保姆级swap分区详解!手把手带你创建swap分区(两种方式,建议收藏)涉及fdisk、gdisk、df、parted、partprobe、mkswap、swapon、free、dd、od等命令
Swap分区的详解 && 创建 什么是swap分区? 方法一:使用物理分区创建Swap分区 1. 利用fdisk / gdisk在磁盘上划出一个分区 1.1 lsblk -- 查看本机 ...
- python int函数详解_Python内置函数OCT详解
英文文档:oct ( x ) Convert an integer number to an octal string. The result is a valid Python expression ...
- Linux系统文件目录指令详解——文件路径查看、创建和删除目录、文件移动与重命名、文件查看
文件目录指令详解 pwd 指令:查看路径 基本语法 应用实例 ls 指令:显示当前目录所在的文件和目录 基本语法 常用选项 应用实例 cd 指令:切换目录 基本语法 如何理解绝对路径和相对路径 常用参 ...
- 微信回调 java_详解APP微信支付(java后台_统一下单和回调)
1.微信配置信息 global.properties 2.方法wxpay用于生成预支付订单信息 方法notifyWeiXinPay用于微信支付成功后的回调, 注意: 在手机端使用微信支付成功后,微信服 ...
- html标记ruby,html5 ruby标签的定义及使用方法详解(内有实例介绍)
本篇文章主要为大家讲解了html5中一个新标签,html5 ruby标签,虽然不算是新的,但也是在html5里新的标签,html5 ruby标签的定义和具体的用法实例都在这篇文章中,希望大家认真阅读 ...
最新文章
- 送给产品经理的一段代码 (!(~+[]) + {})[--[~+][+[]] * [~+[]] + ~~!+[]] + ({} + [])[[~!+[]] ...
- IHttpHandler 介绍演示(from 张子阳)
- LeetCode 1669合并两个链表-中等
- 活动选择(信息学奥赛一本通-T1323)
- n皇后问题c语言报告,关于N皇后问题
- 微信公众号发送小程序卡片_微信公众号里怎么添加小程序-如何在微信[[公众号]]添加小程序卡片-微信关联小程序...
- [Avalon]如何实现自定义MarkupExtension.
- Ubuntu10.0.4下CyanogenMod编译环境的搭建和可刷机文件的编译及打包
- 凸优化系列二:确定步长一维搜索算法
- Pandas python
- 笔记本进入pe却看不到计算机硬盘,一些笔记本进WINPE后找不到硬盘的解决办法...
- SpringBoot项目 四种读取properties文件的方式
- 微型计算机是第四代计算机的产物,计算机的发展已经过了4代,其中()是第四代计算机 - 问答库...
- Tomcat启动,提示 The JRE_HOME environment variable is not defined correctly 问题。
- 好的Python培训机构具备的条件
- 远程控制 - 手机完全控制电脑之TeamViewer
- 机器学习导论——机器学习三要素
- springboot2 集成shiro-spring-boot-web-starter
- 深度学习在推荐领域的应用:Lookalike 算法
- python白名单验证是什么意思_JWT黑名单和白名单
热门文章
- 每年都有几个研究生,被“爸爸”逼得跳楼
- 软件系统三员管理_3个可怕的系统管理员故事
- python提示unmatched_Python: Unmatched group exception
- riscv 开发板 HiFive Unmatched 总览
- linux 播放.ev4播放器,ev4文件怎么播放
- 【渝粤题库】广东开放大学 标准化专题讲座1 形成性考核
- Linux网络连接命令
- Hadoop的数据压缩
- Jquery 实现表格单行获取数据
- php int 32 64,php从32位升级到64位需要注意的几点