官方API:
本文档阐述了如何使用 Google Play 内容库将 Google Play 结算服务添加到我们的应用中。
接入前准备
- 申请一个google play开发者账号,需要支付25美金
- 提前准备好一个apk(不需要集成支付sdk,占位用),在google play控制台上传你的apk
- 发布一个alpha或者beta的版本,发布之前需要点亮以下选项(提交商品详情内容)(确定内容分级)(选择发布范围)等,之后才能正常发布
- 添加测试人员,等应用审核通过之后,会得到一个地址,把地址发给对方,让对方点击同意加入测试即可,测试地址; https://play.google.com/apps/testing/xxx xxx是你应用的包名
- 需要创建应用内商品(商品id,商品描述,定价),按提示填就可以了
- 从google play开发者获取公钥
接入开始
2019年上半年前是有两种接入方式:
1. 使用Google Play 结算库
2. Google Play 结算服务 AIDL
由于之前普遍用的是AIDL的方式来接入,升级到V3版本,但是偶然在官网看到说AIDL接入结算服务方式在未来会被移除,所以需要改变接入方式,替换为使用Google Play 结算库
官方DEMO参考:
一、添加依赖
将以下行添加到应用的 build.gradle 文件的依赖项部分:
dependencies {
...
implementation 'com.android.billingclient:billing:2.0.3'
}
如果是Eclipse来接入的话可用jar的方式,可以下载链接中的文件,放入自己的工程中:
billing-2.0.3
二、建立连接
建立与 Google Play 的连接,然后才能发送 Google Play 结算服务请求,建立连接的具体操作步骤如下:
mBillingClient = BillingClient.newBuilder(mActivity).setListener(this).enablePendingPurchases().build();
mBillingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
Log.d(TAG,"Setup finished. Response code: " + billingResult.getResponseCode());
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// BillingClient已经准备好了。在这里可以查询购买情况。
mIsServiceConnected = true;
}
mBillingClientResponseCode = billingResult.getResponseCode();
}
@Override
public void onBillingServiceDisconnected() {
mIsServiceConnected = false;
}
});
三、查询应用内商品详情
List<String> skuList = new ArrayList<>();
skuList.add(SKU_ID);//商品ID
//查询应用内商品详情
mBillingManager.querySkuDetailsAsync(BillingClient.SkuType.INAPP, skuList, new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) {
loggerUtil.printErrorLog("Unsuccessful query for type: %s . Error code: %s", billingType, billingResult.getResponseCode());
} else if (skuDetailsList != null && skuDetailsList.size() > 0) {
mSkuDetails.clear();
//成功地获得了sku详细
for (SkuDetails details : skuDetailsList) {
if (SKU_ID.equals(details.getSku())) {
mSkuDetails.add(details);//用一个集合缓存起来,调用支付时会用到
}
}
}
}
});
四、支付
if (mBillingClient!= null
&& mBillingClient.getBillingClientResponseCode()
> BILLING_MANAGER_NOT_INITIALIZED) {
//反射BillingClient(->BillingClientImpl)类中的 InAppBilling服务的接口,Hook getBuyIntent等API调用,然后填充developerPayload
HookUtil.doSetPayloadToBillingClient(mBillingClient,developerPayload);
BillingFlowParams.Builder builder = BillingFlowParams.newBuilder();
//skuDetails ->querySkuDetailsAsync获取
builder.setSkuDetails(skuDetails);
//发起支付操作
mBillingClient.launchBillingFlow(mActivity, builder.build());
}
五、支付回调
tip
还记得前面mBillingClient = BillingClient.newBuilder(this.mActivity).enablePendingPurchases().setListener(this)这个操作吗,这个this也是一个监听器
代表的是支付的回调:onPurchasesUpdated(BillingResult billingResult,List purchases)
/**
* 处理从帐单库更新购买的回调
*/
@Override
public void onPurchasesUpdated(BillingResult billingResult,List<Purchase> purchases) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
//购买成功
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
//。。。。此处可以做消耗库存操作
//mBillingUpdatesListener.onPurchasesUpdated(purchases);
//mBillingManager.consumeAsync(purchase);
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
//用户取消了购买流程
mLogger.printErrorLog("onPurchasesUpdated() - user cancelled the purchase flow - skipping");
} else {
//出现其他错误,具体查询返回的状态码
mLogger.printErrorLog("onPurchasesUpdated() got unknown resultCode: " + billingResult.getResponseCode());
}
}
六、消耗库存
tip
只有消费成功之后,才能真正到账,否则3天之后,会执行退款处理 测试阶段只有5分钟
mBillingClient.consumeAsync(ConsumeParams.newBuilder()
.setDeveloperPayload(purchase.getDeveloperPayload())
.setPurchaseToken(purchase.getPurchaseToken()).build(),
new ConsumeResponseListener() {
@Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
mLogger.printErrorLog("onConsumeResponse code = %s ,msg = %s ,purchaseToken = %s", billingResult.getResponseCode(), billingResult.getDebugMessage(), purchaseToken);
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// 消费成功 处理自己的流程,比如存入数据库
//去应用的业务后台进行订单验证及返回结果
} else {
// 消费失败,后面查询消费记录后再次消费,否则,就只能等待退款
}
}
});
以上就是Google Pay的接入流程,相对来说不会特别复杂,流程也比较清晰明了
关于developerPayload的设置
深入查看源码而知,底层还是通过走的AIDL的方式来进行进程间的通信,只是后面可能会有所改变,但是在这个版本中developerPayload的传值,固定为null, 没法主动填充该字段了
有两种解决方案
- 抽离billingclient代码并自己修改, 但后续升级需修改,比较复杂有维护成本。
- 利用java反射机制Hook InAppBilling服务的接口,Hook getBuyIntent等API调用,然后填充developerPayload。只要InAppBillingService接口不变动,不需要更新。
代码:HookUtil.java
import com.android.vending.billing.IInAppBillingService;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Date: 2019/10/16
* Author: anmi
* Desc:反射工具类
*/
public class HookUtil {
/**
* 反射BillingClient(->BillingClientImpl)类中的 InAppBilling服务的接口,
* Hook getBuyIntent等API调用,然后填充developerPayload。只要InAppBillingService接口不变动,不需要更新
* tip:不能混淆IInAppBillingService,BillingClient, BillingClientImpl等类
* @param billingClientObj BillingClient对象
* @param developerPayload 附加值
*/
public static void doSetPayloadToBillingClient(Object billingClientObj, final String developerPayload) {
try {
Class cls = billingClientObj.getClass();
Field field = cls.getDeclaredField("mService");
field.setAccessible(true);
final Object originObj = field.get(billingClientObj);
//此处用于开关标记,避免拿到旧的代理类,导致developerPayload一直拿到旧的
final boolean[] toggle = {true};
IInAppBillingService newService = (IInAppBillingService) Proxy.newProxyInstance(IInAppBillingService.class.getClassLoader(),
new Class[]{IInAppBillingService.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (toggle[0]) {
String methodName = method.getName();
//根据以下方法对应developerPayload的位置进行填充
switch (methodName) {
case "getBuyIntent":
//修改第5个参数
args[4] = developerPayload;
break;
case "getBuyIntentToReplaceSkus":
//修改第6个参数
args[5] = developerPayload;
break;
case "getBuyIntentExtraParams":
//修改第5个参数
args[4] = developerPayload;
break;
}
toggle[0] = false;
}
//将mService更改为我们的代理对象
return method.invoke(originObj, args);
}
});
field.set(billingClientObj, newService);
} catch (Exception e) {
e.printStackTrace();
}
}
}
MainActivity示例
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.android.billing.BillingManager;
import com.android.billing.LoggerUtil;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsResponseListener;
import org.json.JSONException;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
private static LoggerUtil loggerUtil;
public static final String TAG = MainActivity.class.getSimpleName();
static {
loggerUtil = new LoggerUtil();
loggerUtil.setTag(TAG);
}
public static final String SKU_ID = "com.sdk.new.1";
private BillingManager mBillingManager;
private List<SkuDetails> mSkuDetails = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBillingManager = new BillingManager(this, new BillingManager.BillingUpdatesListener() {
@Override
public void onBillingClientSetupFinished() {
List<String> skuList = new ArrayList<>();
skuList.add(SKU_ID);
handleManagerAndUiReady(BillingClient.SkuType.INAPP, skuList);
}
@Override
public void onConsumeFinished(BillingResult billingResult, String purchaseToken) {
loggerUtil.printDebugLog("Consumption finished. Purchase token: %s, result: %s", purchaseToken, billingResult.getResponseCode());
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// Successfully consumed, so we apply the effects of the item in our
// game world's logic, which in our case means filling the gas tank a bit
loggerUtil.printDebugLog("Consumption successful. Provisioning.");
} else {
loggerUtil.printDebugLog("Error while consuming: %1$s", billingResult.getResponseCode());
}
loggerUtil.printDebugLog("End consumption flow.");
}
@Override
public void onPurchasesUpdated(List<Purchase> purchases) {
for (Purchase purchase : purchases) {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
// Acknowledge purchase and grant the item to the user
} else if (purchase.getPurchaseState() == Purchase.PurchaseState.PENDING) {
// Here you can confirm to the user that they've started the pending
// purchase, and to complete it, they should follow instructions that
// are given to them. You can also choose to remind the user in the
// future to complete the purchase if you detect that it is still
// pending.
}
switch (purchase.getSku()) {
case SKU_ID:
loggerUtil.printDebugLog("We have gas. Consuming it.");
// We should consume the purchase and fill up the tank once it was consumed
mBillingManager.consumeAsync(purchase);
break;
}
}
}
});
}
@Override
protected void onResume() {
super.onResume();
if (mBillingManager != null
&& mBillingManager.getBillingClientResponseCode() == BillingClient.BillingResponseCode.OK) {
mBillingManager.queryPurchases();
}
}
@Override
public void onDestroy() {
loggerUtil.printDebugLog("Destroying helper.");
if (mBillingManager != null) {
mBillingManager.destroy();
}
super.onDestroy();
}
public void doPay(View view) {
if (mBillingManager != null
&& mBillingManager.getBillingClientResponseCode()
> BillingManager.BILLING_MANAGER_NOT_INITIALIZED) {
if (mSkuDetails != null && mSkuDetails.size() > 0) {
mBillingManager.initiatePurchaseFlow(mSkuDetails.get(0), "abcdefghijklmn");
}
}
}
private void handleManagerAndUiReady(@BillingClient.SkuType final String billingType, List<String> skuList) {
mBillingManager.querySkuDetailsAsync(billingType, skuList, new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) {
loggerUtil.printErrorLog("Unsuccessful query for type: %s . Error code: %s", billingType, billingResult.getResponseCode());
} else if (skuDetailsList != null && skuDetailsList.size() > 0) {
mSkuDetails.clear();
//成功地获得了sku详细
for (SkuDetails details : skuDetailsList) {
if (SKU_ID.equals(details.getSku())) {
mSkuDetails.add(details);
}
}
}
}
});
}
}
BillingManager 用于封装管理Google Play 结算库
import android.app.Activity;
import com.android.billing.util.HookUtil;
import com.android.billing.util.Security;
import com.android.billing.util.Utility;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ConsumeParams;
import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Date: 2019/10/15
* Author: anmi
* Desc:google 支付管理类
* 处理与Google Play Store的所有交互(通过计费库),维护与Google Play Store的连接
* 通过BillingClient缓存临时状态/数据
*/
public class BillingManager implements PurchasesUpdatedListener {
/**
* 在BillingManager还没有初始化之前,mBillingClientResponseCode的默认值
*/
public static final int BILLING_MANAGER_NOT_INITIALIZED = -1;
private static final String TAG = BillingManager.class.getSimpleName();
private final static LoggerUtil mLogger;
static {
mLogger = new LoggerUtil();
mLogger.setTag(TAG);
}
/**
* BillingClient 实例对象
**/
private BillingClient mBillingClient;
/**
* 服务连接状态
*/
private boolean mIsServiceConnected;
private final BillingUpdatesListener mBillingUpdatesListener;
private final Activity mActivity;
private final List<Purchase> mPurchases = new ArrayList<>();
private Set<String> mTokensToBeConsumed;
private int mBillingClientResponseCode = BILLING_MANAGER_NOT_INITIALIZED;
/* BASE_64_ENCODED_PUBLIC_KEY should be YOUR APPLICATION'S PUBLIC KEY
* (that you got from the Google Play developer console). This is not your
* developer public key, it's the *app-specific* public key.
*
* Instead of just storing the entire literal string here embedded in the
* program, construct the key at runtime from pieces or
* use bit manipulation (for example, XOR with some other string) to hide
* the actual key. The key itself is not secret information, but we don't
* want to make it easy for an attacker to replace the public key with one
* of their own and then fake messages from the server.
* CONSTRUCT_YOU=base 64公钥
*/
private static final String BASE_64_ENCODED_PUBLIC_KEY = "CONSTRUCT_YOUR";
/**
* 用于监听购买列表完成消费的更新
*/
public interface BillingUpdatesListener {
void onBillingClientSetupFinished();
void onConsumeFinished(BillingResult billingResult, String purchaseToken);
void onPurchasesUpdated(List<Purchase> purchases);
}
/**
* 用于监听 Google Play Store客户端的连接状态
*/
public interface ServiceConnectedListener {
void onServiceConnected(@BillingClient.BillingResponseCode int resultCode);
}
public BillingManager(Activity activity, final BillingUpdatesListener updatesListener) {
mLogger.printDebugLog("Creating Billing client.");
this.mActivity = activity;
this.mBillingUpdatesListener = updatesListener;
//构建BillingClient
mBillingClient = BillingClient.newBuilder(mActivity).setListener(this).enablePendingPurchases().build();
mLogger.printDebugLog("Starting setup.");
/**
*异步启动服务连接Google Play Store客户端
Start setup. This is asynchronous and the specified listener will be called
once setup completes.
It also starts to report all the new purchases through onPurchasesUpdated() callback.
*/
startServiceConnection(new Runnable() {
@Override
public void run() {
//通知购买客户端监听器 通信已就绪
mBillingUpdatesListener.onBillingClientSetupFinished();
//IAB已经建立完成,进行查询我们的库存
mLogger.printDebugLog("Setup successful. Querying inventory.");
queryPurchases();
}
});
}
/**
* 跨应用查询购买信息,并通过监听器返回结果
*/
public void queryPurchases() {
//创建查询任务
Runnable queryToExecute = new Runnable() {
@Override
public void run() {
long time = System.currentTimeMillis();
Purchase.PurchasesResult purchasesResult = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
mLogger.printDebugLog("Querying purchases elapsed time:%s ms", (System.currentTimeMillis() - time));
//如果支持订阅,将添加订阅处理
if (areSubscriptionsSupported()) {
//查询订阅
Purchase.PurchasesResult subscriptionResult
= mBillingClient.queryPurchases(BillingClient.SkuType.SUBS);
mLogger.printDebugLog("Querying purchases and subscriptions elapsed time: %s ms", (System.currentTimeMillis() - time));
mLogger.printDebugLog("Querying subscriptions result code: %s Purchases size: %s", subscriptionResult.getResponseCode(), subscriptionResult.getPurchasesList().size());
if (subscriptionResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
purchasesResult.getPurchasesList().addAll(subscriptionResult.getPurchasesList());
} else {
mLogger.printErrorLog("Got an error response trying to query subscription purchases");
}
} else if (purchasesResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
//跳过订阅购买查询,因为它不受支持
mLogger.printDebugLog("Skipped subscription purchases query since they are not supported");
} else {
mLogger.printDebugLog("queryPurchases() got an error response code: " + purchasesResult.getResponseCode());
}
onQueryPurchasesFinished(purchasesResult);
}
};
//执行查询任务的请求
executeServiceRequest(queryToExecute);
}
/**
* 处理查询购买的结果,并通过监听器通知更新后的列表
*/
private void onQueryPurchasesFinished(Purchase.PurchasesResult result) {
// Have we been disposed of in the meantime? If so, or bad result code, then quit
if (mBillingClient == null || result.getResponseCode() != BillingClient.BillingResponseCode.OK) {
mLogger.printErrorLog("Billing client was null or result code (%s)was bad - quitting", result.getResponseCode());
return;
}
mLogger.printDebugLog("Query inventory was successful.");
// Update the UI and purchases inventory with new list of purchases
mPurchases.clear();
onPurchasesUpdated(result.getBillingResult(), result.getPurchasesList());
}
/**
* 检查当前客户端是否支持订阅
* 注意:此方法不会自动重试RESULT_SERVICE_DISCONNECTED。
* 它只用于queryPurchase执行后, 实现了一个回退机制。
*/
public boolean areSubscriptionsSupported() {
BillingResult featureSupported = mBillingClient.isFeatureSupported(BillingClient.FeatureType.SUBSCRIPTIONS);
if (featureSupported.getResponseCode() != BillingClient.BillingResponseCode.OK) {
mLogger.printErrorLog("areSubscriptionsSupported() got an error response: " + featureSupported.getResponseCode());
}
return featureSupported.getResponseCode() == BillingClient.BillingResponseCode.OK;
}
/**
* 异步启动服务连接Google Play Store客户端(底层会走AIDL的方式)
*
* @param executeOnSuccess
*/
public void startServiceConnection(final Runnable executeOnSuccess) {
mBillingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
mLogger.printDebugLog("Setup finished. Response code: " + billingResult.getResponseCode());
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// BillingClient已经准备好了。在这里可以查询购买情况。
mIsServiceConnected = true;
if (executeOnSuccess != null) {
executeOnSuccess.run();
}
}
mBillingClientResponseCode = billingResult.getResponseCode();
}
@Override
public void onBillingServiceDisconnected() {
mIsServiceConnected = false;
}
});
}
/**
* 处理从帐单库更新购买的回调
*/
@Override
public void onPurchasesUpdated(BillingResult billingResult,List<Purchase> purchases) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
//购买成功
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
mBillingUpdatesListener.onPurchasesUpdated(purchases);
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
//用户取消了购买流程
mLogger.printErrorLog("onPurchasesUpdated() - user cancelled the purchase flow - skipping");
} else {
//出现其他错误,具体查询返回的状态码
mLogger.printErrorLog("onPurchasesUpdated() got unknown resultCode: " + billingResult.getResponseCode());
}
}
/**
* 处理采购
* <p>注意:对于每次购买,都要检查签名在客户端是否有效。
* 建议将此检查移到后端。
* 参见{@link Security#verifyPurchase(String, String, String)}
* </p>
*
* @param purchase 待处理的购买信息
*/
private void handlePurchase(Purchase purchase) {
if (!verifyValidSignature(purchase.getOriginalJson(), purchase.getSignature())) {
mLogger.printErrorLog("Got a purchase: %s : but signature is bad. Skipping...", purchase);
return;
}
mLogger.printDebugLog("Got a verified purchase: " + purchase);
mPurchases.add(purchase);
}
/**
* 验证应用 的BASE_64_ENCODED_PUBLIC_KEY是否签属了购买
* <p>
* 强烈建议以下的判断放至业务后台进行处理判断,因为黑客可以进行反编译提取到BASE_64_ENCODED_PUBLIC_KEY,并重建应用,用“constant true”替换这个方法
*/
private boolean verifyValidSignature(String signedData, String signature) {
// Some sanity checks to see if the developer (that's you!) really followed the
// instructions to run this sample (don't put these checks on your app!)
if (BASE_64_ENCODED_PUBLIC_KEY.contains("CONSTRUCT_YOUR")) {
throw new RuntimeException("Please update your app's public key at: "
+ "BASE_64_ENCODED_PUBLIC_KEY");
}
try {
return Security.verifyPurchase(BASE_64_ENCODED_PUBLIC_KEY, signedData, signature);
} catch (IOException e) {
mLogger.printErrorLog("Got an exception trying to validate a purchase: " + e);
return false;
}
}
/**
* 启动购买或订阅流程
*/
public void initiatePurchaseFlow(final SkuDetails skuDetails, final String developerPayload) {
Runnable purchaseFlowRequest = new Runnable() {
@Override
public void run() {
HookUtil.doSetPayloadToBillingClient(mBillingClient,developerPayload);
BillingFlowParams.Builder builder = BillingFlowParams.newBuilder();
builder.setSkuDetails(skuDetails);
mBillingClient.launchBillingFlow(mActivity, builder.build());
}
};
executeServiceRequest(purchaseFlowRequest);
}
/**
* 异步查询商品信息
*
* @param itemType
* @param skuList
* @param listener
*/
public void querySkuDetailsAsync(@BillingClient.SkuType final String itemType, final List<String> skuList,
final SkuDetailsResponseListener listener) {
// Creating a runnable from the request to use it inside our connection retry policy below
Runnable queryRequest = new Runnable() {
@Override
public void run() {
// Query the purchase async
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(itemType);
mBillingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> list) {
listener.onSkuDetailsResponse(billingResult, list);
}
});
}
};
executeServiceRequest(queryRequest);
}
/**
* 异步消耗商品
* 只有消费成功之后,才能真正到账,否则3天之后,会执行退款处理 测试阶段只有5分钟
*
* @param purchase
*/
public void consumeAsync(final Purchase purchase) {
// If we've already scheduled to consume this token - no action is needed (this could happen
// if you received the token when querying purchases inside onReceive() and later from
// onActivityResult()
if (mTokensToBeConsumed == null) {
mTokensToBeConsumed = new HashSet<>();
} else if (mTokensToBeConsumed.contains(purchase.getPurchaseToken())) {
mLogger.printErrorLog("Token was already scheduled to be consumed - skipping...");
return;
}
mTokensToBeConsumed.add(purchase.getPurchaseToken());
// Generating Consume Response listener
final ConsumeResponseListener onConsumeListener = new ConsumeResponseListener() {
@Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
mLogger.printErrorLog("onConsumeResponse code = %s ,msg = %s ,purchaseToken = %s", billingResult.getResponseCode(), billingResult.getDebugMessage(), purchaseToken);
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
//消耗成功
} else {
// 消费失败,后面查询消费记录后再次消费,否则,就只能等待退款
}
mBillingUpdatesListener.onConsumeFinished(billingResult, purchaseToken);
}
};
// Creating a runnable from the request to use it inside our connection retry policy below
Runnable consumeRequest = new Runnable() {
@Override
public void run() {
// Consume the purchase async
ConsumeParams consumeParams = ConsumeParams.newBuilder()
.setDeveloperPayload(purchase.getDeveloperPayload())
.setPurchaseToken(purchase.getPurchaseToken()).build();
mBillingClient.consumeAsync(consumeParams, onConsumeListener);
}
};
executeServiceRequest(consumeRequest);
}
/**
* Returns the value Billing client response code or BILLING_MANAGER_NOT_INITIALIZED if the
* clien connection response was not received yet.
*/
public int getBillingClientResponseCode() {
return mBillingClientResponseCode;
}
/**
* 执行任务并判断是否需要重连服务
* @param runnable
*/
private void executeServiceRequest(Runnable runnable) {
if (mIsServiceConnected) {
runnable.run();
} else {
// 如果账单服务被断开,我们尝试重新连接一次.
// (feel free to introduce your retry policy here).
startServiceConnection(runnable);
}
}
/**
* Clear the resources
* activity -onDestroy调用
*/
public void destroy() {
mLogger.printDebugLog("Destroying the manager.");
if (mBillingClient != null && mBillingClient.isReady()) {
mBillingClient.endConnection();
mBillingClient = null;
}
}
}