一、接入摘要
时间: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