android--GooglePay 谷歌支付内购接入(1)_谷歌支付 android-程序员宅基地

技术标签: 谷歌支付  android  

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,能选择线路台湾的

google play 后台配置:

1.设定定价,就是商品的定价:

按图所示建立价格, 我这里有4个价格。具体建立很简单

建立产品:一个产品对应一个定价,比如我这里580台币对应406点。创建完后,如果没问题,一定要启用,不然app那边取不到数据,另外产品ID就是唯一,后面用在代码里取数据用的。关于产品id的设置,谷歌API中有这么一说,建议是按他要求的来,比较好

 

2.在账户详细信息里面,添加许可测试的邮箱账号,许可测试响应改为 “RESPOND_NORMALLY/LICENSED”,点击保存,需要一两分钟生效,记得弄这一步,这个很坑,你不弄,你测试人员就一直不会出现测试卡测试的模式 

三.关于发一个占位包,跟设置测试人员

 成功发布后,应用市场会出现

二.整体流程

看完流程图后,我们可以简单的总结下步骤,这里业务部分我就不细说了,这东西你得根据自己业务来调整

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 结算库

1.添加依赖 跟 权限

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" />

2.部分代码

2.1创建连接

//连接到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失败,请重试");
            }
        });
    }

 

2.2查询

//查询商品
    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));
            }
        });
    }

 

2.3购买

 //购买
    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());
    }

 

2.4购买后的回调

@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();
        }
    }

 

2.5消耗

 //消耗商品    
    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);
                }
            });
        }
    }

 

2.6 成功跟失败的  

 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方法

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yangbin0513/article/details/123591922

智能推荐

NISP第一讲信息安全和网络空间安全_机密性保护需要考虑的问题-程序员宅基地

文章浏览阅读1.7k次,点赞2次,收藏2次。当今世界,随着互联网技术的不断发展,网络安全也受到了人们的重视。近年来,中国的许多大学校园里都在普及网络安全的知识,让大学生拥有一个良好的上网规范。从今天起,小编准备带大家好好学习一下有关于网络安全的知识,也算是一个普法吧!维护网络空间安全是我们每一位中华人民共和国公民应尽的义务。其实网络安全从个人的角度在于个人的隐私泄露,在于个人的一些银行卡密码、手机支付密码等等的经济财产损失;而对于国家来说,如果国家机关的一些重要部门的内网泄露出去将带来你无法想象的后果,所以维护国家的网络安全我们每个人都要贡献出.._机密性保护需要考虑的问题

Oracle TO_CHAR函数格式化数字(以及小数点前0丢失问题)_oracle to_char小数点前面没有0-程序员宅基地

文章浏览阅读1.5w次,点赞10次,收藏34次。针对数字的常用格式化符常用数字格式化符 参数 示例 说明 9 999 存在数字显示数字,不存在显示空格 0 000 存在数字显示数字,不存在显示0 . 99.99 指定位置返回小数点 , 999,999,999 指定位置返回逗号 FM FM999,999,99.00 如果是因为9带来的空格则删除 ..._oracle to_char小数点前面没有0

易语言软件乱码解决_易语言 乱码-程序员宅基地

文章浏览阅读346次。选择中文(简体,中国),取消勾选 Beta版:使用Unicode UTF-8 提供全球语言支持(U) 【一定要取消勾选】,然后点击 确定 重启电脑。按win+R键,然后输入命令 intl.cpl 打开控制面板。选择 管理 , 点击 更改系统区域设置。_易语言 乱码

我用 Python 写了一款炫酷音乐播放器,想听啥随便搜!-程序员宅基地

文章浏览阅读1.5k次,点赞6次,收藏33次。作者:Dragon少年版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。本文链接:https://blog.csdn.net/hhla..._class setui(object): """ 音乐弹框界面 """ def __init__(self, weight=1000, he

基于华为云ModelArts实现花卉识别_使用modelarts实现花卉图像分类 华为在线实验-程序员宅基地

文章浏览阅读938次,点赞24次,收藏21次。1.5点击下列链接进入数据集界面,点击“下载”,注意目标区域选择OBS一样的区域,目标位置是刚才创建的OBS的位置,点击确定后,等待数据下载完即可。由于北京一的训练作业只有新版的,没有旧版的选项,就只有先把区域调到北京四,点击到训练作业旧版然后再把区域切换到北京一,就可以了。1.3点击左侧“桶列表”,然后点击右上角创建桶,填入桶名称,其他默认即可,点击右下角创建即可(区域可以修改为自己想的区域)1.4点击刚才创建好的桶,在新的界面点击左侧的对象,然后点击“新建文件夹”,创建一个文件夹data来存放数据集。_使用modelarts实现花卉图像分类 华为在线实验

Sn=a+aa+aaa+aaaa+aaaaa的前5项之和--图解-程序员宅基地

文章浏览阅读274次。//求Sn=a+aa+aaa+aaaa+aaaaa的前5项之和,其中a是一个数字,例如:2 + 22 + 222 + 2222 + 22222#include<stdio.h>int main(){ int n, a; int b=10; int sum=0; int c = 0; printf("请输入n,a:"); scanf("%d%d", &n,&a); int i; printf("%d", a); sum = a; for (i

随便推点

C语言程序设计精髓习题总汇-程序员宅基地

文章浏览阅读2.4w次,点赞77次,收藏386次。目录认识变量和常量1.1 hello world!1.2 在屏幕上输出多行信息1.3 计算半圆弧长以及半圆的面积1.4 计算长方形体积计算2.1 输出逆序数2.2 计算总分和平均分2.3 存款利率计算器V1.02.4 数位拆分v1.02.5 求正/负余数2.6 身高预测2.7 求一元二次方程的根输入输出3.1 日期显示3.2 产品信息格式化3.3 计算..._c语言程序设计精髓习题总汇

RuoyiCloudPlus中使用分布式锁Lock4j-程序员宅基地

文章浏览阅读1.3k次。分布式锁的超时时间:发生网络抖动(释放锁的一瞬断网了)以及Redis宕机。通常Ression自带的看门狗功能会自动审视过期时间内程序是否执行完。Lock4j支持Redisson,RedisTemplate,zookeeper。acquire-timeout:获取分布式锁超时时间,默认为 3000 毫秒。expire: 分布式锁的超时时间,默认为 30 秒。获取锁超时:比如发生死锁时,防止其他线程长时间等待。_lock4j

K8S启动失败,kubelet.service: main process exited, code=exited, status=1/FAILURE-程序员宅基地

文章浏览阅读1.3w次,点赞4次,收藏14次。查看k8s运行状态systemctl status kubelet查看K8S运行日志journalctl -xefu kubeletdocker驱动与Kubelet的驱动程序不同导致1、查看docker驱动docker info|grep Driver2、查看kubelet驱动systemctl show --property=Environment kubelet |cat3、修改docker驱动,查看/etc/docker/daemon...._kubelet.service: main process exited, code=exited, status=1/failure

【亲测可行】SVN环境准备(服务端:SVNBucket,客户端:TortoiseSVN)_类似svnbucket的服务器-程序员宅基地

文章浏览阅读1k次。想使用SVN,就得安装好SVN的服务端和客户端【SVN的服务端】推荐使用:SVNBucket(中文名:SVN桶,是现在最好用的SVN服务)。这边使用SVNBucket。【SVN的客户端】Win系统推荐使用:TortoiseSVN,Mac系统推荐使用:Cornstone。这边使用TortoiseSVN。文章目录一、SVN服务端环境准备(SVNBucket)1.输入官网地址“svnbucket.com”,注册一个账号2.输入相关的注册信息,点击“注册”按钮,即可进入控制台(后面也可以自行输入用户名密码._类似svnbucket的服务器

51Nod【3259】-字符三角形_给定一个数字n,用它构造一个底边长n个字符,高n个字符的字符三角形。-程序员宅基地

文章浏览阅读907次。题目给定一个可见字符,以及高度n,用这个字符构造一个底边长n个字符,高n个字符的直角字符三角形。收起输入输入只有一行, 包含一个字符以及一个数字n(1 <= n <= 15)。输出该字符构成的直角三角形。输入样例* 3输出样例******ps:Java字符都快忘了怎么弄了。C:#include <stdio.h>#include <string.h>#include <stdlib.h>._给定一个数字n,用它构造一个底边长n个字符,高n个字符的字符三角形。

mysql数据迁移与同步常用解决方案总结_mysql数据连接被迁移-程序员宅基地

文章浏览阅读5.2k次,点赞77次,收藏71次。mysql数据迁移与同步常用解决方案总结_mysql数据连接被迁移

推荐文章

热门文章

相关标签