新版本Google 支付集成 V3.0(Google Play结算库( V2.0.3 )并解决新版本developerPayload的传值)

本文详细介绍如何使用Google Play结算库将结算服务整合至应用,包括申请开发者账号、上传APK、添加测试人员、创建应用内商品等步骤,以及如何添加依赖、建立连接、查询商品详情、支付、支付回调和消耗库存的完整流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

官方API:

本文档阐述了如何使用 Google Play 内容库将 Google Play 结算服务添加到我们的应用中。

接入前准备

  1. 申请一个google play开发者账号,需要支付25美金
  2. 提前准备好一个apk(不需要集成支付sdk,占位用),在google play控制台上传你的apk
  3. 发布一个alpha或者beta的版本,发布之前需要点亮以下选项(提交商品详情内容)(确定内容分级)(选择发布范围)等,之后才能正常发布
  4. 添加测试人员,等应用审核通过之后,会得到一个地址,把地址发给对方,让对方点击同意加入测试即可,测试地址; https://play.google.com/apps/testing/xxx xxx是你应用的包名
  5. 需要创建应用内商品(商品id,商品描述,定价),按提示填就可以了
  6. 从google play开发者获取公钥

接入开始

2019年上半年前是有两种接入方式

1. 使用Google Play 结算库
2. Google Play 结算服务 AIDL

由于之前普遍用的是AIDL的方式来接入,升级到V3版本,但是偶然在官网看到说AIDL接入结算服务方式在未来会被移除,所以需要改变接入方式,替换为使用Google Play 结算库
在这里插入图片描述

官方DEMO参考:

TrivialDrive v2 示例应用

一、添加依赖

将以下行添加到应用的 build.gradle 文件的依赖项部分:

dependencies {
    ...
    implementation 'com.android.billingclient:billing:2.0.3'
}

如果是Eclipse来接入的话可用jar的方式,可以下载链接中的文件,放入自己的工程中:
billing-2.0.3

点我下载Demo在这里插入图片描述

二、建立连接

建立与 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, 没法主动填充该字段了
在这里插入图片描述
有两种解决方案

  1. 抽离billingclient代码并自己修改, 但后续升级需修改,比较复杂有维护成本。
  2. 利用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;
        }
    }

}
评论 29
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值