package com.android.billing; 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. */ 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()