Android Google Play Billing(二) 接入应用内结算服务

Google Play 区分

在接入Google Pay,查阅 Api 的时候,发现集成方式有2种

  • GooglePay:个人理解的是销售实体商品之类的内容(理解有误欢迎拍砖)
  • Google Pay Billing:应用内结算(进行虚拟的商品交易),下面文字主要说明的是 Billing 接入过程
    Google Play Billing 结算服务概览 , 可以用于销售以下应用内商品:
  • 一次性商品:需要一次性(非定期)向用户收取相关费用(通过用户提供的付款方式)的应用内商品。额外游戏关卡、高级战利品盒和媒体文件都属于一次性商品。Google Play 管理中心将一次性商品称为“受管理的商品”,Google Play 结算库将其称为“INAPP”。
  • 奖励产品:需要用户观看视频广告才能获得的应用内商品。额外的生命、游戏代币和定时任务快速通关等都属于奖励产品。Google Play 管理中心将奖励的产品称为“奖励产品”,Google Play 结算库则将其称为“INAPP”。
  • 订阅:需要定期向用户收取相关费用(通过用户提供的付款方式)的应用内商品。在线杂志和音乐在线播放服务等都属于订阅。Google Play 结算库将这些订阅内容称为“SUBS”。
应用内购买结算功能.png
方式1.png

Google Play Billing 结算服务接入

过程大致分为 : 项目集成、创建商品、应用内购买、商品消费、订单确认
一、项目集成步骤
先上官方demo地址:

官方示例 TrivialDrive_v2
使用 Google Play 结算库

集成到 Android Studio 步骤:

  • 将以下行添加到应用的build.gradle 文件的依赖项部分:
dependencies {
    ...
    implementation 'com.android.billingclient:billing:2.0.1'
}
  • AndroidManifest中添加下面的权限
<!-- Google Play 内购权限 -->
    <uses-permission android:name="com.android.vending.BILLING" />

经过上面的2个步骤,其实已经集成好了,后续就是书写逻辑了(具体代码可以参考 官方示例 TrivialDrive_v2,后续也会介绍到自己使用过程中遇到的坑。)

注:现在网上好多文章都是使用的AIDL步骤集成的,不过现在Google好像不推荐了,AIDL使用GooglePay结算服务

旧的集成方式.png

Android studio 接入的过程比较简单,到这里已经基本ok了。

二、商品创建
这里默认,你在Google Pay console 已经创建好应用了!商品创建步骤如下:

登录Google Pay Console ----> 选择自己的应用---->商店发布---->应用内商品(如下图)

GooglePayConsole创建商品.png

比如,创建一个 受管理的商品

创建受管理的商品.png

填写相应的信息即可,比较重要的有:

  • 商品ID:不可重复,应用使用最多的也就是这个id
  • 状态:注意选成 有效
  • 价格:这里注意国家地区的选择

注:这里创建的商品,都可以根据 Google Api - 查询应用内商品接口 进行查询,后面的文章也会详细介绍(测试阶段,可以直接写死一个)。

三、应用内购买、商品消费
这两个步骤都是在App应用内,调用相应Api来操作,对官方的Api进行了简单的封装并且添加了相应注释(如果有误欢迎拍砖),附上代码:

/**
 * Date: 2019/8/13
 * create by cuishuxiang
 * description:
 * <p>
 * Google Pay 返回code:{@link com.android.billingclient.api.BillingClient.BillingResponseCode}
 * <p>
 * Google Pay 商品信息:{@link SkuDetails}
 * 1,getPrice() 向用户显示折扣价,又使用 getOriginalPrice() 显示商品的原价。
 * 2,getOriginalPriceAmountMicros() - 返回未设置格式的折扣前 SKU 原价。
 * 3,getOriginalPrice() - 返回采用其他货币格式设置的原价。
 * <p>
 * 商品类型 :  {@link com.android.billingclient.api.BillingClient.FeatureType}
 * <p>
 * 购买注意事项:如果商品购买成功,系统还会生成购买令牌,它是一个唯一标识符,表示用户及其所购应用内商品的商品 ID。
 * 您的应用可以在用户设备上存储购买令牌,理想情况下,也可以将购买令牌传递到安全的后端服务器,
 * 以便用于验证购买交易及防范欺诈行为。购买令牌对于一次性商品的每笔购买交易和每个奖励产品都是唯一的。
 * 不过,由于订阅是一次性购买并按固定的结算周期自动续订,因此订阅的购买令牌在各个结算周期内保持不变。
 * <p>
 * 用户还会收到包含交易收据的电子邮件,其中包含订单 ID 或交易的唯一 ID。用户每次购买一次性商品时,
 * 都会收到包含唯一订单 ID 的电子邮件。此外,用户最初购买订阅时以及后续定期自动续订时,也会收到这样的电子邮件。
 * 您可以在 Google Play 管理中心内使用订单 ID 来管理退款。有关详情,请参阅查看应用的订单和订阅及办理退款。
 */
public class BillingClientManager2 {
    private static final String TAG = BillingClientManager2.class.getName();

    private static BillingClient billingClient;
    private static Activity mActivity;

    //注意,每次请求前最好判断是否已经连接
    private static boolean mIsServiceConnected = false;

    /**
     * @param activity
     * @param listener 这里自己写了一个回调,处理常见的问题
     *                 <p>
     *                 在调用购买方法后,Google Play 会调用 {@link PurchasesUpdatedListener#onPurchasesUpdated(BillingResult, List)}方法,
     *                 将购买操作的结果传递给实现 PurchasesUpdatedListener 接口的监听器
     */
    public static void init(Activity activity, @NonNull final OnPurchaseCallBack listener) {
        billingClient = BillingClient.newBuilder(activity).setListener(new PurchasesUpdatedListener() {
            @Override
            public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
                Log.d(TAG, "onPurchasesUpdated: ");
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
                    /**
                     * 购买成功
                     */
                    listener.onPaySuccess(purchases);
                } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
                    // Handle an error caused by a user cancelling the purchase flow.
                    //处理由用户取消购买流程引起的错误
                    listener.onUserCancel();
                } else {
                    // Handle any other error codes.
                    listener.responseCode(billingResult.getResponseCode());
                }
            }
        }).enablePendingPurchases().build();


        mActivity = activity;

        //连接服务
        connectionService();
    }

    /**
     * 连接服务
     */
    public static void connectionService() {
        if (billingClient == null)
            throw new IllegalArgumentException("Please call init(); first!");

        billingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingSetupFinished(BillingResult billingResult) {
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    // The BillingClient is ready. You can query purchases here.
                    mIsServiceConnected = true;
                }
            }

            @Override
            public void onBillingServiceDisconnected() {
                // Try to restart the connection on the next request to
                // Google Play by calling the startConnection() method.
                mIsServiceConnected = false;
            }
        });
    }


    /**
     * 要异步查询商品详情,应用会使用您在 Google Play 管理中心内配置商品时定义的商品 ID。
     *
     * @param itemType 针对一次性商品或奖励产品:SkuType.INAPP  ; 订阅:SkuType.SUBS
     * @param skuList  指定产品 ID 字符串列表
     * @param listener
     */
    public static void querySkuDetailsAsync(@BillingClient.SkuType String itemType,
                                            SkuDetailsResponseListener listener, @NonNull String... skuList) {
        if (billingClient == null)
            throw new IllegalArgumentException("querySkuDetailsAsync(); error . Please call init(); first!");

        //判断是否连接
        if (!mIsServiceConnected) connectionService();

        SkuDetailsParams skuDetailsParams = SkuDetailsParams.newBuilder()
                .setType(itemType)
                .setSkusList(Arrays.asList(skuList))//转换成数组
                .build();
        billingClient.querySkuDetailsAsync(skuDetailsParams, listener);
    }

    /**
     * 启动应用内商品的购买
     *
     * @param skuDetails 这里商品详情,从 querySkuDetailsAsync(); 查询
     * @return 方法会返回 BillingClient.BillingResponse 中列出的几个响应代码之一
     */
    public static BillingResult launchBillingFlow(@NonNull SkuDetails skuDetails) {
        if (mActivity == null || billingClient == null)
            throw new IllegalArgumentException("launchBillingFlow(); error . Please call init(); first!");

        //判断是否连接
        if (!mIsServiceConnected) connectionService();

        BillingFlowParams flowParams = BillingFlowParams.newBuilder()
                .setSkuDetails(skuDetails)
                .build();

        return billingClient.launchBillingFlow(mActivity, flowParams);
    }

    /**
     * Android 手机安装的 Google Play 商店应用可能是旧版的,不支持订阅等商品类型。
     * 因此,在应用进入结算流程之前,请调用 isFeatureSupported()
     * 以检查设备是否支持您要销售的商品。如需查看商品类型的列表,请参阅 BillingClient.FeatureType。
     *
     * @param feature
     * @return
     */
    public static boolean isFeatureSupported(@BillingClient.FeatureType String feature) {
        if (billingClient == null)
            throw new IllegalArgumentException("isFeatureSupported(); error . Please call init(); first!");

        //判断是否连接
        if (!mIsServiceConnected) connectionService();

        BillingResult result = billingClient.isFeatureSupported(feature);

        if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) {
            return true;
        } else {
            Log.e(TAG, "isFeatureSupported: isFeatureSupported = false , errorMsg : " + result.getDebugMessage());
            return false;
        }
    }

    /**
     * 确认、消费一次性商品交易
     * <p>
     * 警告! 所有购买都需要确认。 未能确认购买将导致购买退款。 对于一次性产品,请确保使用此方法作为隐式确认,
     * 或者您可以通过{@link BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)} 明确确认购买。
     * 对于订阅,请使用{@link #acknowledgePurchase)。
     * 有关详细信息,请参阅https://developer.android.com/google/play/billing/billing_library_overview#acknowledge。
     */
    public static void consumeAsync(@NonNull Purchase purchase, ConsumeResponseListener listener) {
        if (billingClient == null)
            throw new IllegalArgumentException("consumeAsync(); error . Please call init(); first!");

        //判断是否连接
        if (!mIsServiceConnected) connectionService();

        //Purchase 对象包含 isAcknowledged() 方法,该方法会指示购买交易是否已得到确认
        if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED
                && !purchase.isAcknowledged()) {

            ConsumeParams consumeParams = ConsumeParams
                    .newBuilder()
//                    .setDeveloperPayload(purchase.getDeveloperPayload())//指定开发人员有效负载与购买信息一起发回。
                    .setPurchaseToken(purchase.getPurchaseToken())//指定标识要使用的购买的标记。
                    .build();
            billingClient.consumeAsync(consumeParams, listener);
        }

    }

    /**
     * 确认“订阅商品”交易
     * <p>
     * <p>
     * 消费“费消耗品” 还可以使用服务器 API 中新增的 acknowledge() 方法。
     */
    public static void acknowledgePurchase(@NonNull Purchase purchase, AcknowledgePurchaseResponseListener listener) {
        if (billingClient == null)
            throw new IllegalArgumentException("acknowledgePurchase(); error . Please call init(); first!");

        //判断是否连接
        if (!mIsServiceConnected) connectionService();

        //Purchase 对象包含 isAcknowledged() 方法,该方法会指示购买交易是否已得到确认
        if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED
                && !purchase.isAcknowledged()) {
            AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams
                    .newBuilder()
//                    .setDeveloperPayload(purchase.getDeveloperPayload())//这个参数不确定是否需要
                    .setPurchaseToken(purchase.getPurchaseToken())
                    .build();

            billingClient.acknowledgePurchase(acknowledgePurchaseParams, listener);
        }
    }

    /**
     * 用户通过应用发起的购买交易的相关信息(使用 Google Play 商店应用的缓存)
     *
     * @param type 购买类型(SkuType.INAPP 或 SkuType.SUBS)
     * @return 注意:
     * 在每次启动您的应用时都调用 queryPurchases(),
     * 以便您可以恢复用户自应用上次停止以来发起的任何购买交易。
     * 在 onResume() 方法中调用 queryPurchases(),
     * 因为当您的应用在后台时,用户可能会发起购买交易(例如,在 Google Play 商店应用中兑换促销代码)。
     * <p>
     * queryPurchases() 方法使用 Google Play 商店应用的缓存,而不发起网络请求。
     * <p>
     * 如果您需要查看用户对每个商品 ID 发起的最近一笔购买交易,您可以使用 queryPurchaseHistoryAsync()
     */
    public static List<Purchase> queryPurchases(@BillingClient.SkuType String type) {
        if (billingClient == null)
            throw new IllegalArgumentException("acknowledgePurchase(); error . Please call init(); first!");

        Purchase.PurchasesResult result = billingClient.queryPurchases(type);

        if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) {

//            BillingResult billingResult = result.getBillingResult();

            return result.getPurchasesList();
        }

        return null;
    }

    /**
     * 查询最近的购买交易(网络)
     *
     * @param type
     * @param listener 注意: 该方法走网络
     *                 如果使用 queryPurchaseHistoryAsync(),您也可以将其与刷新按钮结合使用,使用户能更新其购买交易列表。
     */
    public static void queryPurchaseHistoryAsync(@BillingClient.SkuType String type, PurchaseHistoryResponseListener listener) {

        if (billingClient == null)
            throw new IllegalArgumentException("acknowledgePurchase(); error . Please call init(); first!");
        final List<PurchaseHistoryRecord> purchaseHistoryRecords = new ArrayList<>();

        billingClient.queryPurchaseHistoryAsync(type, listener);
    }

}

上面的代码,对Api进行简单的封装,使用前先调用 init 方法,具体使用例如:查询应用内商品详情,调用这个方法即可,最后不定长参数传入上面的 商品id 即可

/**
     * 要异步查询商品详情,应用会使用您在 Google Play 管理中心内配置商品时定义的商品 ID。
     *
     * @param itemType 针对一次性商品或奖励产品:SkuType.INAPP  ; 订阅:SkuType.SUBS
     * @param skuList  指定产品 ID 字符串列表
     * @param listener
     */
    public static void querySkuDetailsAsync(@BillingClient.SkuType String itemType,
                                            SkuDetailsResponseListener listener, @NonNull String... skuList) {
        if (billingClient == null)
            throw new IllegalArgumentException("querySkuDetailsAsync(); error . Please call init(); first!");

        //判断是否连接
        if (!mIsServiceConnected) connectionService();

        SkuDetailsParams skuDetailsParams = SkuDetailsParams.newBuilder()
                .setType(itemType)
                .setSkusList(Arrays.asList(skuList))//转换成数组
                .build();
        billingClient.querySkuDetailsAsync(skuDetailsParams, listener);
    }

在使用过程中,发现一个比较好用的三方库:https://github.com/anjlab/android-inapp-billing-v3 , 对这个库相应的 Api 也进行了相应的封装,附上代码:

/**
 * Date: 2019/8/13
 * create by cuishuxiang
 * description: 管理类
 *
 * tp: 1,需要调用初始化方法{@link BillingManager#init(Activity, String, BillingProcessor.IBillingHandler)}
 *     2,具体的操作,需要使用到{@link BillingProcessor},这里也提供了{@link BillingManager#getBillingProcessor()}来获取实例
 *
 * 注意:下面的信息可能会用到
 *  1, 处理已取消的订阅
 *      调用bp.getSubscriptionTransactionDetails(...)并检查purchaseInfo.purchaseData.autoRenewing标志。
 *      订阅取消后,它将设置为False。
 *      另请注意,您需要定期调用bp.loadOwnedPurchasesFromGoogle()方法才能更新订阅信息
 *  2, 促销代码支持
 *     您可以使用促销代码和此库。 促销代码可以在购买对话框或Google Play应用中输入。
 *     网址https://play.google.com/redeem?code=YOUR_PROMO_CODE将启动已添加促销代码的Google Play应用。
 *     如果您想让用户选择在您的应用中输入促销代码,这可能会派上用场。
 *
 *  3,错误码查阅参考:{@link Constants}
 *
 * 参考:https://github.com/anjlab/android-inapp-billing-v3/blob/master/README.md
 */
public class BillingManager {
    private static final String TAG = "BillingManager";

    private static BillingProcessor bp;

    private static Activity mActivity;
    private static String mLicenseKey;

    /**
     * 初始化方法,需要先调用这个方法
     *
     * @param activity
     * @param licenseKey
     */
    public static void init(Activity activity, String licenseKey, BillingProcessor.IBillingHandler iBillingHandler) {
        mActivity = activity;
        mLicenseKey = licenseKey;

        bp = BillingProcessor.newBillingProcessor(activity, licenseKey, iBillingHandler);
        //商户id获得:GooglePayConsole后台--->设置---->付款设置(注意,商家id需要放置到安全的地方)
//        bp = BillingProcessor.newBillingProcessor(activity, licenseKey, "商户id", iBillingHandler);
        bp.initialize();
    }

    public static BillingProcessor getBillingProcessor() {
        return bp;
    }

    /**
     * 在使用之前,最好检查应用内的结算服务可用性。 在某些旧设备或中国设备中,可能会出现Play Market不可用或已弃用且不支持应用内结算的情况。
     *
     * @return true 可用; false:不可用
     */
    public static boolean isPayServiceAvailable() {
        if (mActivity == null)
            throw new IllegalArgumentException("isPayServiceAvailable() error. Call the init method first , Please check it!");
        return BillingProcessor.isIabServiceAvailable(mActivity);
    }

    /**
     * 请注意,调用BillingProcessor.isIabServiceAvailable()(仅检查是否安装了Play Market应用程序)是不够的,
     * 因为可能存在返回true但仍然无法付款的情况。
     * <p>
     * 因此,最好在初始化BillingProcessor之后调用isOneTimePurchaseSupported():
     *
     * @return 最好true的时候,调用支付流程
     */
    public static boolean isPurchaseSupported() {
        if (bp == null)
            throw new IllegalArgumentException("isPurchaseSupported() error . Call the init method first , Please check it!");

        return bp.isOneTimePurchaseSupported();
    }

    /**
     * 检测是否支持订阅
     *
     * @return
     */
    public static boolean isSubscribeUpdateSupported() {
        if (bp == null)
            throw new IllegalArgumentException("isSubscriptionUpdateSupported() error . Call the init method first , Please check it!");
        return bp.isSubscriptionUpdateSupported();
    }

    /**
     * 购买方法
     *
     * @param purchaseId id
     */
    public static boolean purchase(String purchaseId) {
        if (mActivity == null || bp == null)
            throw new IllegalArgumentException("Call the init method first , Please check it!");

        //检测服务是否可用
        if (!isPayServiceAvailable()) {
            Log.e(TAG, "purchase: 服务不可用,请检查!");
            return false;
        }

        //检测是否支持购买
        if (!isPurchaseSupported()) {
            Log.e(TAG, "purchase: 不支持购买,请检查!");
            return false;
        }

        return bp.purchase(mActivity, purchaseId);
    }

    /**
     * 暂时不知道 "developerPayload" 什么意思
     * <p>
     * 重要信息:当您提供有效负载时,库内部会为您的负载预先添加一个字符串。
     * 对于订阅,它前缀为“subs:\ <productId \>:”,
     * 对于产品,它预先添加“inapp:\ <productId \>:\ <UUID \>:”。
     * <p>
     * 了解您是否对成功购买后从Google Play返回的有效内容进行了任何验证,这一点非常重要。
     *
     * @param productId
     * @param developerPayload
     * @param extraParams      放置参数如下所示
     * @return
     */
    public static boolean purchase(String productId, String developerPayload, Bundle extraParams) {
        /**
         * Bundle extraParams = new Bundle()
         * extraParams.putString("accountId", "MY_ACCOUNT_ID");
         */
        if (mActivity == null || bp == null)
            throw new IllegalArgumentException("purchase() error . Call the init method first , Please check it!");
        //检测服务是否可用
        if (!isPayServiceAvailable()) {
            Log.e(TAG, "purchase: 服务不可用,请检查!");
            return false;
        }

        //检测是否支持购买
        if (!isPurchaseSupported()) {
            Log.e(TAG, "purchase: 不支持购买,请检查!");
            return false;
        }


        return bp.subscribe(mActivity, productId, developerPayload, extraParams);
    }

    /**
     * 订阅方法
     *
     * @param purchaseId
     */
    public static boolean subscribe(String purchaseId) {
        if (mActivity == null || bp == null)
            throw new IllegalArgumentException("subscribe() error . Call the init method first , Please check it!");

        //检测服务是否可用
        if (!isPayServiceAvailable()) {
            Log.e(TAG, "subscribe: 服务不可用,请检查!");
            return false;
        }

        //检测是否支持购买
        if(!isPurchaseSupported()){
            Log.e(TAG, "subscribe: 不支持购买,请检查!");
            return false;
        }

        return bp.subscribe(mActivity, purchaseId);
        //注意:还有一个 三个参数的负载,暂时不知道什么意思
//        bp.subscribe(mActivity, purchaseId, "");
    }


    /**
     * 订阅方法 (developerPayload 负载,暂时不知道什么意思)
     * <p>
     * 重要信息:当您提供有效负载时,库内部会为您的负载预先添加一个字符串。
     * 对于订阅,它前缀为“subs:\ <productId \>:”,
     * 对于产品,它预先添加“inapp:\ <productId \>:\ <UUID \>:”。
     *
     * @param purchaseId
     * @param developerPayload
     * @param extraParams
     * @return
     */
    public static boolean subscribe(String purchaseId, String developerPayload, Bundle extraParams) {
        /**
         * Bundle extraParams = new Bundle()
         * extraParams.putString("accountId", "MY_ACCOUNT_ID");
         */
        if (mActivity == null || bp == null)
            throw new IllegalArgumentException("subscribe() error . Call the init method first , Please check it!");

        //检测服务是否可用
        if (!isPayServiceAvailable()) {
            Log.e(TAG, "subscribe: 服务不可用,请检查!");
            return false;
        }

        //检测是否支持购买
        if(!isPurchaseSupported()){
            Log.e(TAG, "subscribe: 不支持购买,请检查!");
            return false;
        }

        return bp.subscribe(mActivity, purchaseId, developerPayload, extraParams);
    }

    /**
     * 消费购买的商品
     *
     * 注:您可以调用该方法,随时消费购买并允许多次购买相同的产品
     * @param purchaseId
     * @return
     */
    public static boolean consumePurchase(String purchaseId) {
        if ( bp == null)
            throw new IllegalArgumentException("consumePurchase() error . Call the init method first , Please check it!");

        return bp.consumePurchase(purchaseId);
    }

    /**
     * 商品详情
     * @param productId
     * @return
     */
    public static SkuDetails getSkuDetail(String productId) {
        if ( bp == null)
            throw new IllegalArgumentException("getSkuDetail() error . Call the init method first , Please check it!");


       return bp.getPurchaseListingDetails(productId);
    }

    public static List<SkuDetails> getSkuDetailList(ArrayList<String> productIdList) {
        if ( bp == null)
            throw new IllegalArgumentException("getSkuDetail() error . Call the init method first , Please check it!");


        return bp.getPurchaseListingDetails(productIdList);
    }

    /**
     * 获得交易详情(商品)
     * @param productId
     * @return
     */
    public static TransactionDetails getPurchaseTransactionDetails(String productId) {
        if ( bp == null)
            throw new IllegalArgumentException("getPurchaseTransactionDetails() error . Call the init method first , Please check it!");

        return bp.getPurchaseTransactionDetails(productId);
    }
    /**
     * 获得交易详情(订阅)
     * @param productId
     * @return
     */
    public static TransactionDetails getSubscribeTransactionDetails(String productId) {
        if ( bp == null)
            throw new IllegalArgumentException("getSubscribeTransactionDetails() error . Call the init method first , Please check it!");

        return bp.getSubscriptionTransactionDetails(productId);
    }

    /**
     * 获得 应用内商品 购买历史记录
     * @param extraParams
     * @return
     */
    public static List<BillingHistoryRecord> getInappHistory(Bundle extraParams) {
        if ( bp == null)
            throw new IllegalArgumentException("getInappHistory() error . Call the init method first , Please check it!");
        try {

            //请注意,此API需要版本6或更高版本的结算API,因此您应该事先检查是否支持它:
            if (!bp.isRequestBillingHistorySupported(Constants.PRODUCT_TYPE_MANAGED)) {
                Log.e(TAG, "getInappHistory: isRequestBillingHistorySupported 版本不支持:");
                return null;
            }

            return bp.getPurchaseHistory(Constants.PRODUCT_TYPE_MANAGED, extraParams);

        } catch (BillingCommunicationException e) {
            e.printStackTrace();
            Log.e(TAG, "getInappHistory: 异常:" + e);
            return null;
        }
    }


    /**
     * 获得 订阅商品 购买历史记录
     * @param extraParams
     * @return
     */
    public static List<BillingHistoryRecord> getSubscribeHistory(Bundle extraParams) {
        if ( bp == null)
            throw new IllegalArgumentException("getSubscribeHistory() error . Call the init method first , Please check it!");

        try {
            //请注意,此API需要版本6或更高版本的结算API,因此您应该事先检查是否支持它:
            if (!bp.isRequestBillingHistorySupported(Constants.PRODUCT_TYPE_SUBSCRIPTION)) {
                Log.e(TAG, "getInappHistory: getSubscribeHistory 版本不支持:");
                return null;
            }

            return bp.getPurchaseHistory(Constants.PRODUCT_TYPE_SUBSCRIPTION, extraParams);

        } catch (BillingCommunicationException e) {
            e.printStackTrace();
            Log.e(TAG, "getSubscribeHistory: 异常:" + e);
            return null;
        }
    }

    /**
     * 恢复购买和订阅
     * @return
     */
    public static boolean restore() {
        if ( bp == null)
            throw new IllegalArgumentException("restore() error . Call the init method first , Please check it!");
        return bp.loadOwnedPurchasesFromGoogle();
    }

    /**
     * 检查交易有效性 (调用该方法,在初始化 BillingProcessor 需要设置商家id)
     * @param transactionDetails
     * @return
     *
     *
     * 安全性 参考:https://developer.android.com/google/play/billing/billing_best_practices.html#validating-purchase-device
     */
    public static boolean isValid(TransactionDetails transactionDetails) {
        if ( bp == null)
            throw new IllegalArgumentException("isValid() error . Call the init method first , Please check it!");

       return bp.isValidTransactionDetails(transactionDetails);
    }


    /**
     * 用于接收回调,在Activity可以这样调用,示例:
     *      @Override
     *     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
     *         if (!bp.handleActivityResult(requestCode, resultCode, data))
     *             super.onActivityResult(requestCode, resultCode, data);
     *     }
     */
    public static boolean handleActivityResult(int requestCode, int resultCode, Intent data){
        return bp.handleActivityResult(requestCode, resultCode, data);
    }

    /**
     * 在Activity onDestroy 调用该方法
     */
    public static void release() {
        if (bp != null) {
            bp.release();
            bp = null;
        }
    }
}

上面有两个封装,一个基于三方库的、一个是对Google原生Api封装,已经亲测,都可以使用看需挑选吧!!如果要是哪里有问题,欢迎拍砖!!

后续的订单验证步骤,可以参考我的另一篇:Google Play 订单验证
需要调用Google Api 坑有点多!!!!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容