android--GooglePay 谷歌支付内购接入(1)
由于谷歌应用市场的限制令,需要把目前的APP接入googlepay 谷歌支付,以免在3月31日底,没接入的APP将会有被下架掉的风险,
整个接入流程,以及注意事项,以及踩坑记录,我都会写出来,希望对大家有所帮助,文章大概拆成2篇,来全方位记录跟概况
在这我先提前说这么几个名词
1.消耗
消耗是什么意思:消耗就相当于是订单确认,如果没有确认google会在3天后自动退款,同时这一笔物品就重新买不了
2.重试
重试是什么意思:就是重新去做处理,去查询谷歌这边,如果谷歌这边真的扣款成功,在去服务器这边查看是否成功)
3.依赖包版本选择
implementation 'com.android.billingclient:billing:3.0.0'
implementation 'com.android.billingclient:billing:4.0.0'
implementation 'com.google.android.gms:play-services-wallet:19.1.0'
这3个包都是谷歌支付有关的,3.0 跟4.0 最大区别是3.0不能重复购买,只能一个个的购买,4.0版本可以购买多件(根据产品的需求 不需要一次性购买多件,我这次选择3.0)
至于gms:play-services-wallet 没找到太多网上帖子,我就放弃了
## 1.申请一个google play开发者账号,这里我是有google play开发账号的,毕竟我们的APP是发谷歌市场的
## 2.提前准备好一个apk(不需要集成支付sdk,占位用),在google play控制台上传你的apk,这里你可以发封闭测试里面去,下面我会上图,不懂的看图
## 3.发布一个alpha或者beta的版本,发布之前需要点亮以下选项(提交商品详情内容)(确定内容分级)(选择发布范围)等,之后才能正常发布
## 4.添加测试人员,等应用审核通过之后,会得到一个地址,把地址发给对方,让对方点击同意加入测试即可
## 5.需要创建应用内商品(商品id,商品描述,定价),按提示填就可以了
## 6.在账户详细信息里面,添加许可测试的邮箱账号,许可测试响应改为 “RESPOND_NORMALLY/LICENSED”,点击保存,需要一两分钟生效,记得弄这一步,这个很坑,你不弄,你测试人员就一直不会出现测试卡测试的模式
## 7.检查你的包名和签名文件是否和Google Console 上面上传的apk包是否一致
## 8.检查版本号是否和Google console发布的apk版本是否一致
## 9.检查你是否可以购买,是否绑定了银行卡,手机支不支持Google支付,手机是否有Google服务
## 10.由于我是台湾上线APP,想测台币支付,我还得准备一个vpn,能选择线路台湾的
按图所示建立价格, 我这里有4个价格。具体建立很简单
建立产品:一个产品对应一个定价,比如我这里580台币对应406点。创建完后,如果没问题,一定要启用,不然app那边取不到数据,另外产品ID就是唯一,后面用在代码里取数据用的。关于产品id的设置,谷歌API中有这么一说,建议是按他要求的来,比较好
成功发布后,应用市场会出现
看完流程图后,我们可以简单的总结下步骤,这里业务部分我就不细说了,这东西你得根据自己业务来调整
1.进入商品选择列表界面,选择需要购买的物品
2.根据选择的物品id,创建订单
3.初始化google支付,如果google已经连接,查询这个商品ID得到商品详情;如果没连接googlepay,调用连接
4.购买操作
5.onPurchasesUpdated通过购买回调,判断是否购买成功
6。如果购买成功,拿到Google支付返回的相关信息,在服务器进行验证操作。
7.如果服务器拿到你上传的相关信息和Google支付进行交互验证,验证谷歌扣款成功,服务器收款成功,说明支付成功,成功后,要做一次消耗操作,(消耗是什么意思:消耗就相当于是订单确认,如果没有确认google会在3天后自动退款,同时这一笔物品就重新买不了8.如果服务器拿到你上传的相关信息和Google支付进行交互验证,验证谷歌扣款成功,服务器未收款到账,说明有问题,需要做重试操作,(重试是什么意思:就是重新去做处理,去查询谷歌这边,如果谷歌这边真的扣款成功,在去服务器这边查看是否成功)
注:这里第8步,我在这2个环境下,都做了对应的操作,增加了程序的友好体验,
第1:如果用户支付完成,就没管了,每次打开APP首页,我都会去帮他们做重试操作。这种用户是无感的,体验比较友好
第2: 如果用户支付完成,退出当前页面,然后再进来商品选择页面,点击同样的商品,这个时候由于还没成功,也没做消耗处理,是会有提示框弹窗,已拥有该商品,这个时候,我是会做重试的,如果成功,他下次在点击,就可以重新购买了
## 目前已经升级到V3、V4版本,AIDL的方法已经过时了,并且未来的版本会将之移除,推荐使用使用 Google Play 结算库
implementation 'com.android.billingclient:billing:3.0.0'<!--谷歌商店应用内购买结算需要的权限-->
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="com.farsitel.bazaar.permission.PAY_THROUGH_BAZAAR" />
//连接到GooglePay
private void connectGooglePay(String skuId) {
//请求连接到GooglePay
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
int code = billingResult.getResponseCode();
if (code != BillingClient.BillingResponseCode.OK) {
String msg = billingResult.getDebugMessage();
Log.e(TAG, "连接到GooglePay失败 code = " + code + " msg = " + msg);
onFail();
return;
}
Log.e(TAG, "连接到GooglePay成功");
checkSku(skuId);
}
//连接失败
@Override
public void onBillingServiceDisconnected() {
Log.e(TAG, "连接到GooglePay失败,请重试");
}
});
}
//查询商品
private void checkSku(String id) {
List<String> skuList = new ArrayList<>();
skuList.add(id);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder()
.setSkusList(skuList)
.setType(BillingClient.SkuType.INAPP);
billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(@NonNull BillingResult billingResult, @Nullable List<SkuDetails> list) {
int code = billingResult.getResponseCode();
if (code != BillingClient.BillingResponseCode.OK || list == null || list.isEmpty()) {
String msg = billingResult.getDebugMessage();
Log.e(TAG, "查询商品失败 code = " + code + " msg = " + msg);
onFail();
return;
}
Log.e(TAG, "查询商品成功");
buyIt(list.get(0));
}
});
}
//购买
private void buyIt(SkuDetails skuDetails) {
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build();
BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);
int code = billingResult.getResponseCode();
if (code != BillingClient.BillingResponseCode.OK) {
String msg = billingResult.getDebugMessage();
Log.e(TAG, "购买商品失败 code = " + code + " msg = " + msg);
onFail();
return;
}
Log.e(TAG, "购买商品" + skuDetails.toString());
}
@Override
public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> list) {
int code = billingResult.getResponseCode();
String msg = billingResult.getDebugMessage();
Log.e(TAG, "onPurchasesUpdated:code = " + code + " msg = " + msg);
if (list != null) {
for (Purchase purchase : list) {
Log.e(TAG, "onPurchasesUpdated:" + purchase.toString());
}
}
if (code == BillingClient.BillingResponseCode.OK && list != null) {
Log.e(TAG, "支付成功");
onSuccess(list);
} else if (code == BillingClient.BillingResponseCode.USER_CANCELED) {
Log.e(TAG, "支付取消");
onFail();
} else {
Log.e(TAG, "支付失败:code = " + code + " msg = " + msg);
onFail();
}
}
//消耗商品
private void consume(List<Purchase> list) {
if (list == null || list.isEmpty() || billingClient == null) {
return;
}
for (Purchase purchase : list) {
billingClient.consumeAsync(ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build(), new ConsumeResponseListener() {
@Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
Log.e(TAG, "onConsumeResponse code = " + billingResult.getResponseCode() + " , msg = " + billingResult.getDebugMessage() + " , purchaseToken = " + purchaseToken);
}
});
}
}
private void onFail() {
//自己写关于支付失败后的操作
}
private void onSuccess(List<Purchase> list) {
//自己写关于支付成功后的操作
}
重试操作这块
这里有2种场景,要注意
1.谷歌支付成功了,但是后端没成功
2.谷歌支付成功,后端成功,但是没来得及消耗,可能是断网了,重新消耗
/**
* 查询最近的购买交易
*
*/
private void queryHistory() {
Purchase.PurchasesResult mResult = billingClient.queryPurchases(BillingClient.SkuType.INAPP);
if (mResult != null) {
List<Purchase> list = new ArrayList();
if(mResult.getPurchasesList()!=null ) {
if (mResult.getPurchasesList().size() > 0) {
for (int i = 0; i < mResult.getPurchasesList().size(); i++) {
if (mResult.getPurchasesList().size() > 0)
if (mResult.getPurchasesList().get(i).isAcknowledged()) {
list.add(mResult.getPurchasesList().get(i));
consume(list);
} else {
if (null != json) {
}
}
}
}
}
}
}
1.GooglePay默认只能购买一次,如果你需要重复购买一个商品 请调用consume方法
文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态
文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境
文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn
文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker
文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机
文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk
文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入
文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。 Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。
文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动
文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计
文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;gt;Jni-&amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图
文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法