Android_Google Play结算库(应用内支付)billing 3.0接入实战

 

一、接入摘要
时间:2021-04-19
版本:billing 3.0
语言:java
内容:一次性消耗型商品
老版本比较:当前客户端接入版本对比aidl方式区别巨大,支付透传字段也被废弃,需要开发者做好订单和google订单关联;服务端支付验证也改了流程,开发者需要做更多的操作。
关于google 支付新版本,了解到好多开发者还是用的老版本,为什么不更新?哈哈,因为太坑了。不过现在强制更新,给了具体的时间限制


二、接入流程
Google Play开发者后台创建应用(Google Play)
开发者后台对应项目配置相关信息
安卓端接入(结算库接入文档)
后端商品验证
三、客户端接入
集成依赖库
module的 build.gradle 添加下面代码

dependencies {
...
implementation "com.android.billingclient:billing:3.0.3"
 

dependencies {
    ...
    implementation "com.android.billingclient:billing:3.0.3"
    ...
}

 


支付流程
初始化 BillingClient
与 Google Play 建立连接
自己服务端生成订单再调起 google 购买操作
购买成功拿到相关信息去服务端验证购买合法性
服务端验证商品后进行发货并返回客户端信息,客户端消耗商品
初始化 BillingClient
/**
 

/**
 * 初始化billingClient
 */
private void initBillingClient() {
    mBillingClient = BillingClient.newBuilder(this)
            .setListener(new PurchasesUpdatedListener() {
                @Override
                public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> purchases) {
                    //交易更新将会在这里回调
                    int responseCode = billingResult.getResponseCode();
                    if (responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
                        for (Purchase purchase : purchases) {
                            String googlePayOrderId = purchase.getOrderId();
                            String purchaseToken = purchase.getPurchaseToken();
                            //服务器验证
                            verifyPayment(orderId, googlePayOrderId, productId, purchaseToken);
                        }
                    } else if (responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
                        //取消支付
                    } else if (responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
                        //已存在这个未完成订单,查询订单验证然后消耗掉
                        queryPurchases();
                    } else {
                        //还有很多其他状态,判断进行相应处理
                    }
                }
            })
            .enablePendingPurchases()
            .build();

}
 

 

与 Google Play 建立连接
/**
* 与Google Play建立连接
*/

private void startConnection() {
mBillingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
//链接成功最好去查询订单,做掉单处理
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
queryPurchases();
}
}

 

@Override
public void onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
//建议断开时重连或在使用时判断连接状态,没有连接就手动再调一次 startConnection,确保在执行任何方法时都与 BillingClient 保持连接。
}
});

 

发起购买
先展示商品再发起购买

/**
* 购买商品
*/

private void purchase() {
//先展示商品
List<String> skuList = new ArrayList<>();
skuList.add(productId);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);//INAPP应用内支付
mBillingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList != null) {
for (SkuDetails skuDetails : skuDetailsList) {
String sku = skuDetails.getSku();
if (productId.equals(sku)) {
//启动购买
BillingFlowParams purchaseParams =
BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build();
mBillingClient.launchBillingFlow(TestBilling.this, purchaseParams);
//购买状态将在PurchasesUpdatedListener.onPurchasesUpdated返回
}
}
}
}
});
//
}

 

服务端验证
这一步骤具体逻辑在服务端,接入是跟服务端人员沟通配合完成

/**
* 验证支付,需要后端处理
*
* @param orderId 我们自己的订单号,一般客户端调起支付前会在自己服务端下单
* @param gpOrderId google商品订单号
* @param productId 商品id
* @param purchaseToken 商品token
*/
private void verifyPayment(String orderId, String gpOrderId, String productId, String purchaseToken) {
//这一步操作就是写个网络请求,把支付相关信息传到后端进行验证合法性,后端验证返回客户端,验证成功将消耗商品

}
 
查询购买
/**
* 查询购买交易,以确保所有购买交易都得到成功处理,如购买未发货,或者未消耗
*/
private void queryPurchases() {
if (mBillingClient != null && mBillingClient.isReady()) {
Purchase.PurchasesResult result = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
List<Purchase> purchasesList = result.getPurchasesList();
if (purchasesList != null) {
for (int i = 0; i < purchasesList.size(); i++) {
if (purchasesList.get(i).isAcknowledged()) {
//已确认/已验证,消耗即可
consume(purchasesList.get(i).getPurchaseToken());
} else {
//TODO 因google支付新版没有透传字段,所以我们的订单号需要手动关联,
// 数据库查询gp订单对应的我方订单号或者服务端进行订单关联
// 关于这一块后续看是否google有新的改动优化
Purchase purchase = purchasesList.get(i);
String gpOrderId = purchase.getOrderId();
String orderId = "";//我们自己的订单号,如果是程序刚启动进来补单的,那么我们的订单就拿不到,因为google不携带透传,自己做处理吧
String sku = purchase.getSku();
String purchaseToken = purchase.getPurchaseToken();
verifyPayment(orderId, gpOrderId, sku, purchaseToken);
}
}
}

}
}
 
消耗商品
/**
* 消耗商品
*
* @param purchaseToken 商品token
*/
private void consume(String purchaseToken) {
if (mBillingClient != null && mBillingClient.isReady()) {
ConsumeParams consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchaseToken)
.build();
ConsumeResponseListener listener = new ConsumeResponseListener() {
@Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// Handle the success of the consume operation.
}
}
};
mBillingClient.consumeAsync(consumeParams, listener);
}
}
 
断开连接
@Override
public void onDestroy(Activity activity) {
if (mBillingClient!= null && mBillingClient.isReady()) {
mBillingClient.endConnection();
}
}
 

四、服务端验证流程
初次接入新版本也是相当麻烦,如不懂流程在官方文档上会摸不着头脑
总结有几个步骤

1、创建 API 项目
Google Play 开发者后台对应项目新建API项目(Google Play Developer API)并启动API服务和关联到Google Play 项目

参考地址:https://developers.google.cn/android-publisher/getting_started

2、创建 OAuth 客户端
API 项目下新建OAuth 客户端,应用类型选择网页应用,其他的看情况填写,确认创建之后获取到客户端ID(client_id)和客户端秘钥(client_secret)还有client_secret_xxx.json文件,内容如下:

{
"web":{
"client_id":"916683014888-479gobska5u85hho2hnb03296lcr9pii.apps.googleusercontent.com",
"project_id":"api-8972885819834575872-739232",
"auth_uri":"https://accounts.google.com/o/oauth2/auth",
"token_uri":"https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs",
"client_secret":"5J8jjMVx4o5bvvkqzku1DzMl",
"redirect_uris":[
"xxx"
],
"javascript_origins":[
"xxx"
]
}
}
 
以上参数都是用在服务端调用google api,客户端ID和客户端秘钥并非Android客户端参数,服务端这里的所以参数都跟Android端没有啥关系

3、获取code
这一步的操作是需要开发者账号登录网页,然后在网页打开这个链接(https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher&response_type=code&access_type=offline&redirect_uri=xxx&client_id=xxx)进行授权,redirect_uri就是创建 OAuth 客户端填写的重定向链接。授权成功后会把code通过redirect_uri返回。如果redirect_uri是随便填的,访问出现404页面或者无法访问的提示,这时候请将地址栏中的链接地址复制出来,把code=4/xxx的值取出来,这里就获得了code的值4/wtedvcqw-yui5CNNb-m2iI83KQx1d.yp6198ti5Zc7dJ3UXOl0T3aRLxWrtgbn

4、 通过 code 获取 refresh_token
重要的事情说三遍 保存 保存 保存 第一次授权的时候才会返回refresh_token(长令牌,一般情况下永久有效) ,请妥善保存。
不过在调试阶段没保存的小伙伴也不用太过担心,可以在 OAuth 客户端选择重置秘钥或者新建 OAuth 客户端,记得服务端把参数替换哦。这时候再走一遍流程就可以再次授权获得refresh_token

POST请求到https://accounts.google.com/o/oauth2/token

请求参数:

grant_type:authorization_code
code:步骤 3 获取到的code
client_id:客户端id
client_secret:客户端秘钥
redirect_uri:重定向地址

结果返回:

{
"access_token":"xxx",
"expires_in":3599,
"refresh_token":"1//xxx",
"scope":"https://www.googleapis.com/auth/androidpublisher",
"token_type":"Bearer",
"created":16193255555
}
 
5、通过 refresh_token 获取 access_token
POST方式调用https://accounts.google.com/o/oauth2/token

请求参数:

grant_type:refresh_token
client_id:客户端id
client_secret:客户端秘钥
refresh_token:步骤 4 获取的refresh_token值

结果返回:

{
"access_token":"xxx",
"token_type":"Bearer",
"expires_in":3600
}
 
6、查询订单信息
其实就是所谓的验证
一次性消耗型商品参考官网地址:https://developers.google.com/android-publisher/api-ref/rest/v3/purchases.products/get

GET方法调用以下接口: https://www.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/products/{productId}/tokens/{token}?access_token=access_token

packageName:该应用的包名, 如com.google.demo

productId:商品ID

token:Android端充值获取的token值

access_token:步骤 5 获取的access_token

如果订单有效会返回相关信息

{
"kind": “androidpublisher#productPurchase”,
"purchaseTimeMillis": “支付时间”,
"purchaseState": 0,// 是否付费: 0 已支付, 1 取消
"consumptionState": 0, // 是否被消费: 0 未消费, 1 已消费,
"developerPayload": "",//透传字段,新版这个客户端没法传了
"orderId": “GPA-XXX”,//google的订单号
"purchaseType": 0, // 支付类型: 0 测试, 1 真实
"acknowledgementState": 0,//商品的确认状态, 0 尚未被确认, 1 确认
"purchaseToken": "token",//购买令牌,即客户端支付商品token
"productId": “sss”,//商品id
"quantity": 1,//数量
"obfuscatedExternalAccountId": “”,
"obfuscatedExternalProfileId": “”,
"regionCode": “”
}

 
可根据返回的信息作相应的验证

如果订单无效会返回400错误

如果返回403错误
1、检查api项目启动状态
2、检查api项目关联状态
3、OAuth 客户端参数与服务端使用是否一致
4、谷歌服务的 bug,这时候只要在该应用的商店内,随意增加一个内购商品,再试一次看看是否可行,新增的内购商品可以删除。

{
"error": {
"errors": [{
"domain": "androidpublisher",
"reason": "projectNotLinked",
"message": "The project id used to call the Google Play Developer API has not been linked in the Google Play Developer Console."
}],
"code": 403,
"message": "The project id used to call the Google Play Developer API has not been linked in the Google Play Developer Console."
}
}
 
五、收工
————————————————
版权声明:本文为CSDN博主「kincai」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hqiong208/article/details/116162203

posted @ 2021-11-17 17:29  心雨星空  阅读(1676)  评论(0编辑  收藏  举报