Android Google Play Billing(二) 接入應(yīng)用內(nèi)結(jié)算服務(wù)

Google Play 區(qū)分

在接入Google Pay吹害,查閱 Api 的時(shí)候,發(fā)現(xiàn)集成方式有2種

  • GooglePay:個(gè)人理解的是銷售實(shí)體商品之類的內(nèi)容(理解有誤歡迎拍磚)
  • Google Pay Billing:應(yīng)用內(nèi)結(jié)算(進(jìn)行虛擬的商品交易)货葬,下面文字主要說明的是 Billing 接入過程
    Google Play Billing 結(jié)算服務(wù)概覽 财搁, 可以用于銷售以下應(yīng)用內(nèi)商品:
  • 一次性商品:需要一次性(非定期)向用戶收取相關(guān)費(fèi)用(通過用戶提供的付款方式)的應(yīng)用內(nèi)商品个束。額外游戲關(guān)卡慕购、高級(jí)戰(zhàn)利品盒和媒體文件都屬于一次性商品。Google Play 管理中心將一次性商品稱為“受管理的商品”茬底,Google Play 結(jié)算庫將其稱為“INAPP”沪悲。
  • 獎(jiǎng)勵(lì)產(chǎn)品:需要用戶觀看視頻廣告才能獲得的應(yīng)用內(nèi)商品。額外的生命阱表、游戲代幣和定時(shí)任務(wù)快速通關(guān)等都屬于獎(jiǎng)勵(lì)產(chǎn)品殿如。Google Play 管理中心將獎(jiǎng)勵(lì)的產(chǎn)品稱為“獎(jiǎng)勵(lì)產(chǎn)品”,Google Play 結(jié)算庫則將其稱為“INAPP”最爬。
  • 訂閱:需要定期向用戶收取相關(guān)費(fèi)用(通過用戶提供的付款方式)的應(yīng)用內(nèi)商品涉馁。在線雜志和音樂在線播放服務(wù)等都屬于訂閱。Google Play 結(jié)算庫將這些訂閱內(nèi)容稱為“SUBS”烂叔。
應(yīng)用內(nèi)購買結(jié)算功能.png
方式1.png

Google Play Billing 結(jié)算服務(wù)接入

過程大致分為 : 項(xiàng)目集成谨胞、創(chuàng)建商品固歪、應(yīng)用內(nèi)購買蒜鸡、商品消費(fèi)胯努、訂單確認(rèn)
一、項(xiàng)目集成步驟
先上官方demo地址:

官方示例 TrivialDrive_v2
使用 Google Play 結(jié)算庫

集成到 Android Studio 步驟:

  • 將以下行添加到應(yīng)用的build.gradle 文件的依賴項(xiàng)部分:
dependencies {
    ...
    implementation 'com.android.billingclient:billing:2.0.1'
}
  • AndroidManifest中添加下面的權(quán)限
<!-- Google Play 內(nèi)購權(quán)限 -->
    <uses-permission android:name="com.android.vending.BILLING" />

經(jīng)過上面的2個(gè)步驟逢防,其實(shí)已經(jīng)集成好了叶沛,后續(xù)就是書寫邏輯了(具體代碼可以參考 官方示例 TrivialDrive_v2,后續(xù)也會(huì)介紹到自己使用過程中遇到的坑忘朝。)

注:現(xiàn)在網(wǎng)上好多文章都是使用的AIDL步驟集成的灰署,不過現(xiàn)在Google好像不推薦了,AIDL使用GooglePay結(jié)算服務(wù)

舊的集成方式.png

Android studio 接入的過程比較簡單局嘁,到這里已經(jīng)基本ok了溉箕。

二、商品創(chuàng)建
這里默認(rèn)悦昵,你在Google Pay console 已經(jīng)創(chuàng)建好應(yīng)用了肴茄!商品創(chuàng)建步驟如下:

登錄Google Pay Console ----> 選擇自己的應(yīng)用---->商店發(fā)布---->應(yīng)用內(nèi)商品(如下圖)

GooglePayConsole創(chuàng)建商品.png

比如,創(chuàng)建一個(gè) 受管理的商品

創(chuàng)建受管理的商品.png

填寫相應(yīng)的信息即可但指,比較重要的有:

  • 商品ID:不可重復(fù)寡痰,應(yīng)用使用最多的也就是這個(gè)id
  • 狀態(tài):注意選成 有效
  • 價(jià)格:這里注意國家地區(qū)的選擇

注:這里創(chuàng)建的商品,都可以根據(jù) Google Api - 查詢應(yīng)用內(nèi)商品接口 進(jìn)行查詢棋凳,后面的文章也會(huì)詳細(xì)介紹(測試階段,可以直接寫死一個(gè))拦坠。

三、應(yīng)用內(nèi)購買剩岳、商品消費(fèi)
這兩個(gè)步驟都是在App應(yīng)用內(nèi)贞滨,調(diào)用相應(yīng)Api來操作,對(duì)官方的Api進(jìn)行了簡單的封裝并且添加了相應(yīng)注釋(如果有誤歡迎拍磚)拍棕,附上代碼:

/**
 * 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() 向用戶顯示折扣價(jià)疲迂,又使用 getOriginalPrice() 顯示商品的原價(jià)。
 * 2,getOriginalPriceAmountMicros() - 返回未設(shè)置格式的折扣前 SKU 原價(jià)莫湘。
 * 3,getOriginalPrice() - 返回采用其他貨幣格式設(shè)置的原價(jià)逛绵。
 * <p>
 * 商品類型 :  {@link com.android.billingclient.api.BillingClient.FeatureType}
 * <p>
 * 購買注意事項(xiàng):如果商品購買成功,系統(tǒng)還會(huì)生成購買令牌芽偏,它是一個(gè)唯一標(biāo)識(shí)符隙疚,表示用戶及其所購應(yīng)用內(nèi)商品的商品 ID。
 * 您的應(yīng)用可以在用戶設(shè)備上存儲(chǔ)購買令牌忙芒,理想情況下示弓,也可以將購買令牌傳遞到安全的后端服務(wù)器,
 * 以便用于驗(yàn)證購買交易及防范欺詐行為呵萨。購買令牌對(duì)于一次性商品的每筆購買交易和每個(gè)獎(jiǎng)勵(lì)產(chǎn)品都是唯一的奏属。
 * 不過,由于訂閱是一次性購買并按固定的結(jié)算周期自動(dòng)續(xù)訂潮峦,因此訂閱的購買令牌在各個(gè)結(jié)算周期內(nèi)保持不變囱皿。
 * <p>
 * 用戶還會(huì)收到包含交易收據(jù)的電子郵件勇婴,其中包含訂單 ID 或交易的唯一 ID。用戶每次購買一次性商品時(shí)嘱腥,
 * 都會(huì)收到包含唯一訂單 ID 的電子郵件耕渴。此外,用戶最初購買訂閱時(shí)以及后續(xù)定期自動(dòng)續(xù)訂時(shí)齿兔,也會(huì)收到這樣的電子郵件橱脸。
 * 您可以在 Google Play 管理中心內(nèi)使用訂單 ID 來管理退款。有關(guān)詳情分苇,請(qǐng)參閱查看應(yīng)用的訂單和訂閱及辦理退款添诉。
 */
public class BillingClientManager2 {
    private static final String TAG = BillingClientManager2.class.getName();

    private static BillingClient billingClient;
    private static Activity mActivity;

    //注意,每次請(qǐng)求前最好判斷是否已經(jīng)連接
    private static boolean mIsServiceConnected = false;

    /**
     * @param activity
     * @param listener 這里自己寫了一個(gè)回調(diào)医寿,處理常見的問題
     *                 <p>
     *                 在調(diào)用購買方法后吻商,Google Play 會(huì)調(diào)用 {@link PurchasesUpdatedListener#onPurchasesUpdated(BillingResult, List)}方法,
     *                 將購買操作的結(jié)果傳遞給實(shí)現(xiàn) PurchasesUpdatedListener 接口的監(jiān)聽器
     */
    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.
                    //處理由用戶取消購買流程引起的錯(cuò)誤
                    listener.onUserCancel();
                } else {
                    // Handle any other error codes.
                    listener.responseCode(billingResult.getResponseCode());
                }
            }
        }).enablePendingPurchases().build();


        mActivity = activity;

        //連接服務(wù)
        connectionService();
    }

    /**
     * 連接服務(wù)
     */
    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;
            }
        });
    }


    /**
     * 要異步查詢商品詳情糟红,應(yīng)用會(huì)使用您在 Google Play 管理中心內(nèi)配置商品時(shí)定義的商品 ID艾帐。
     *
     * @param itemType 針對(duì)一次性商品或獎(jiǎng)勵(lì)產(chǎn)品:SkuType.INAPP  ; 訂閱:SkuType.SUBS
     * @param skuList  指定產(chǎn)品 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))//轉(zhuǎn)換成數(shù)組
                .build();
        billingClient.querySkuDetailsAsync(skuDetailsParams, listener);
    }

    /**
     * 啟動(dòng)應(yīng)用內(nèi)商品的購買
     *
     * @param skuDetails 這里商品詳情盆偿,從 querySkuDetailsAsync(); 查詢
     * @return 方法會(huì)返回 BillingClient.BillingResponse 中列出的幾個(gè)響應(yīng)代碼之一
     */
    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 手機(jī)安裝的 Google Play 商店應(yīng)用可能是舊版的柒爸,不支持訂閱等商品類型。
     * 因此事扭,在應(yīng)用進(jìn)入結(jié)算流程之前捎稚,請(qǐng)調(diào)用 isFeatureSupported()
     * 以檢查設(shè)備是否支持您要銷售的商品。如需查看商品類型的列表求橄,請(qǐng)參閱 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;
        }
    }

    /**
     * 確認(rèn)、消費(fèi)一次性商品交易
     * <p>
     * 警告罐农! 所有購買都需要確認(rèn)条霜。 未能確認(rèn)購買將導(dǎo)致購買退款。 對(duì)于一次性產(chǎn)品涵亏,請(qǐng)確保使用此方法作為隱式確認(rèn)宰睡,
     * 或者您可以通過{@link BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)} 明確確認(rèn)購買。
     * 對(duì)于訂閱气筋,請(qǐng)使用{@link #acknowledgePurchase)拆内。
     * 有關(guān)詳細(xì)信息,請(qǐng)參閱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 對(duì)象包含 isAcknowledged() 方法麸恍,該方法會(huì)指示購買交易是否已得到確認(rèn)
        if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED
                && !purchase.isAcknowledged()) {

            ConsumeParams consumeParams = ConsumeParams
                    .newBuilder()
//                    .setDeveloperPayload(purchase.getDeveloperPayload())//指定開發(fā)人員有效負(fù)載與購買信息一起發(fā)回。
                    .setPurchaseToken(purchase.getPurchaseToken())//指定標(biāo)識(shí)要使用的購買的標(biāo)記搀矫。
                    .build();
            billingClient.consumeAsync(consumeParams, listener);
        }

    }

    /**
     * 確認(rèn)“訂閱商品”交易
     * <p>
     * <p>
     * 消費(fèi)“費(fèi)消耗品” 還可以使用服務(wù)器 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 對(duì)象包含 isAcknowledged() 方法刻肄,該方法會(huì)指示購買交易是否已得到確認(rèn)
        if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED
                && !purchase.isAcknowledged()) {
            AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams
                    .newBuilder()
//                    .setDeveloperPayload(purchase.getDeveloperPayload())//這個(gè)參數(shù)不確定是否需要
                    .setPurchaseToken(purchase.getPurchaseToken())
                    .build();

            billingClient.acknowledgePurchase(acknowledgePurchaseParams, listener);
        }
    }

    /**
     * 用戶通過應(yīng)用發(fā)起的購買交易的相關(guān)信息(使用 Google Play 商店應(yīng)用的緩存)
     *
     * @param type 購買類型(SkuType.INAPP 或 SkuType.SUBS)
     * @return 注意:
     * 在每次啟動(dòng)您的應(yīng)用時(shí)都調(diào)用 queryPurchases(),
     * 以便您可以恢復(fù)用戶自應(yīng)用上次停止以來發(fā)起的任何購買交易采够。
     * 在 onResume() 方法中調(diào)用 queryPurchases()肄方,
     * 因?yàn)楫?dāng)您的應(yīng)用在后臺(tái)時(shí)冰垄,用戶可能會(huì)發(fā)起購買交易(例如蹬癌,在 Google Play 商店應(yīng)用中兌換促銷代碼)。
     * <p>
     * queryPurchases() 方法使用 Google Play 商店應(yīng)用的緩存虹茶,而不發(fā)起網(wǎng)絡(luò)請(qǐng)求逝薪。
     * <p>
     * 如果您需要查看用戶對(duì)每個(gè)商品 ID 發(fā)起的最近一筆購買交易,您可以使用 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;
    }

    /**
     * 查詢最近的購買交易(網(wǎng)絡(luò))
     *
     * @param type
     * @param listener 注意: 該方法走網(wǎng)絡(luò)
     *                 如果使用 queryPurchaseHistoryAsync()蝴罪,您也可以將其與刷新按鈕結(jié)合使用董济,使用戶能更新其購買交易列表。
     */
    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);
    }

}

上面的代碼要门,對(duì)Api進(jìn)行簡單的封裝虏肾,使用前先調(diào)用 init 方法,具體使用例如:查詢應(yīng)用內(nèi)商品詳情欢搜,調(diào)用這個(gè)方法即可封豪,最后不定長參數(shù)傳入上面的 商品id 即可

/**
     * 要異步查詢商品詳情,應(yīng)用會(huì)使用您在 Google Play 管理中心內(nèi)配置商品時(shí)定義的商品 ID炒瘟。
     *
     * @param itemType 針對(duì)一次性商品或獎(jiǎng)勵(lì)產(chǎn)品:SkuType.INAPP  吹埠; 訂閱:SkuType.SUBS
     * @param skuList  指定產(chǎn)品 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))//轉(zhuǎn)換成數(shù)組
                .build();
        billingClient.querySkuDetailsAsync(skuDetailsParams, listener);
    }

在使用過程中,發(fā)現(xiàn)一個(gè)比較好用的三方庫:https://github.com/anjlab/android-inapp-billing-v3 疮装, 對(duì)這個(gè)庫相應(yīng)的 Api 也進(jìn)行了相應(yīng)的封裝缘琅,附上代碼:

/**
 * Date: 2019/8/13
 * create by cuishuxiang
 * description: 管理類
 *
 * tp: 1,需要調(diào)用初始化方法{@link BillingManager#init(Activity, String, BillingProcessor.IBillingHandler)}
 *     2,具體的操作,需要使用到{@link BillingProcessor},這里也提供了{(lán)@link BillingManager#getBillingProcessor()}來獲取實(shí)例
 *
 * 注意:下面的信息可能會(huì)用到
 *  1, 處理已取消的訂閱
 *      調(diào)用bp.getSubscriptionTransactionDetails(...)并檢查purchaseInfo.purchaseData.autoRenewing標(biāo)志廓推。
 *      訂閱取消后刷袍,它將設(shè)置為False。
 *      另請(qǐng)注意樊展,您需要定期調(diào)用bp.loadOwnedPurchasesFromGoogle()方法才能更新訂閱信息
 *  2, 促銷代碼支持
 *     您可以使用促銷代碼和此庫做个。 促銷代碼可以在購買對(duì)話框或Google Play應(yīng)用中輸入。
 *     網(wǎng)址https://play.google.com/redeem?code=YOUR_PROMO_CODE將啟動(dòng)已添加促銷代碼的Google Play應(yīng)用滚局。
 *     如果您想讓用戶選擇在您的應(yīng)用中輸入促銷代碼居暖,這可能會(huì)派上用場。
 *
 *  3,錯(cuò)誤碼查閱參考:{@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;

    /**
     * 初始化方法藤肢,需要先調(diào)用這個(gè)方法
     *
     * @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后臺(tái)--->設(shè)置---->付款設(shè)置(注意太闺,商家id需要放置到安全的地方)
//        bp = BillingProcessor.newBillingProcessor(activity, licenseKey, "商戶id", iBillingHandler);
        bp.initialize();
    }

    public static BillingProcessor getBillingProcessor() {
        return bp;
    }

    /**
     * 在使用之前,最好檢查應(yīng)用內(nèi)的結(jié)算服務(wù)可用性嘁圈。 在某些舊設(shè)備或中國設(shè)備中省骂,可能會(huì)出現(xiàn)Play Market不可用或已棄用且不支持應(yīng)用內(nèi)結(jié)算的情況蟀淮。
     *
     * @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);
    }

    /**
     * 請(qǐng)注意钞澳,調(diào)用BillingProcessor.isIabServiceAvailable()(僅檢查是否安裝了Play Market應(yīng)用程序)是不夠的怠惶,
     * 因?yàn)榭赡艽嬖诜祷豻rue但仍然無法付款的情況。
     * <p>
     * 因此轧粟,最好在初始化BillingProcessor之后調(diào)用isOneTimePurchaseSupported():
     *
     * @return 最好true的時(shí)候策治,調(diào)用支付流程
     */
    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!");

        //檢測服務(wù)是否可用
        if (!isPayServiceAvailable()) {
            Log.e(TAG, "purchase: 服務(wù)不可用,請(qǐng)檢查!");
            return false;
        }

        //檢測是否支持購買
        if (!isPurchaseSupported()) {
            Log.e(TAG, "purchase: 不支持購買兰吟,請(qǐng)檢查!");
            return false;
        }

        return bp.purchase(mActivity, purchaseId);
    }

    /**
     * 暫時(shí)不知道 "developerPayload" 什么意思
     * <p>
     * 重要信息:當(dāng)您提供有效負(fù)載時(shí)通惫,庫內(nèi)部會(huì)為您的負(fù)載預(yù)先添加一個(gè)字符串。
     * 對(duì)于訂閱混蔼,它前綴為“subs:\ <productId \>:”履腋,
     * 對(duì)于產(chǎn)品,它預(yù)先添加“inapp:\ <productId \>:\ <UUID \>:”惭嚣。
     * <p>
     * 了解您是否對(duì)成功購買后從Google Play返回的有效內(nèi)容進(jìn)行了任何驗(yàn)證遵湖,這一點(diǎn)非常重要。
     *
     * @param productId
     * @param developerPayload
     * @param extraParams      放置參數(shù)如下所示
     * @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!");
        //檢測服務(wù)是否可用
        if (!isPayServiceAvailable()) {
            Log.e(TAG, "purchase: 服務(wù)不可用晚吞,請(qǐng)檢查!");
            return false;
        }

        //檢測是否支持購買
        if (!isPurchaseSupported()) {
            Log.e(TAG, "purchase: 不支持購買延旧,請(qǐng)檢查!");
            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!");

        //檢測服務(wù)是否可用
        if (!isPayServiceAvailable()) {
            Log.e(TAG, "subscribe: 服務(wù)不可用,請(qǐng)檢查!");
            return false;
        }

        //檢測是否支持購買
        if(!isPurchaseSupported()){
            Log.e(TAG, "subscribe: 不支持購買载矿,請(qǐng)檢查!");
            return false;
        }

        return bp.subscribe(mActivity, purchaseId);
        //注意:還有一個(gè) 三個(gè)參數(shù)的負(fù)載垄潮,暫時(shí)不知道什么意思
//        bp.subscribe(mActivity, purchaseId, "");
    }


    /**
     * 訂閱方法 (developerPayload 負(fù)載,暫時(shí)不知道什么意思)
     * <p>
     * 重要信息:當(dāng)您提供有效負(fù)載時(shí)闷盔,庫內(nèi)部會(huì)為您的負(fù)載預(yù)先添加一個(gè)字符串弯洗。
     * 對(duì)于訂閱,它前綴為“subs:\ <productId \>:”逢勾,
     * 對(duì)于產(chǎn)品牡整,它預(yù)先添加“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!");

        //檢測服務(wù)是否可用
        if (!isPayServiceAvailable()) {
            Log.e(TAG, "subscribe: 服務(wù)不可用溺拱,請(qǐng)檢查!");
            return false;
        }

        //檢測是否支持購買
        if(!isPurchaseSupported()){
            Log.e(TAG, "subscribe: 不支持購買,請(qǐng)檢查!");
            return false;
        }

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

    /**
     * 消費(fèi)購買的商品
     *
     * 注:您可以調(diào)用該方法沐扳,隨時(shí)消費(fèi)購買并允許多次購買相同的產(chǎn)品
     * @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);
    }

    /**
     * 獲得 應(yīng)用內(nèi)商品 購買歷史記錄
     * @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 {

            //請(qǐng)注意句占,此API需要版本6或更高版本的結(jié)算API,因此您應(yīng)該事先檢查是否支持它:
            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 {
            //請(qǐng)注意杨拐,此API需要版本6或更高版本的結(jié)算API祈餐,因此您應(yīng)該事先檢查是否支持它:
            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;
        }
    }

    /**
     * 恢復(fù)購買和訂閱
     * @return
     */
    public static boolean restore() {
        if ( bp == null)
            throw new IllegalArgumentException("restore() error . Call the init method first , Please check it!");
        return bp.loadOwnedPurchasesFromGoogle();
    }

    /**
     * 檢查交易有效性 (調(diào)用該方法,在初始化 BillingProcessor 需要設(shè)置商家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);
    }


    /**
     * 用于接收回調(diào)帆阳,在Activity可以這樣調(diào)用屋吨,示例:
     *      @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 調(diào)用該方法
     */
    public static void release() {
        if (bp != null) {
            bp.release();
            bp = null;
        }
    }
}

上面有兩個(gè)封裝离赫,一個(gè)基于三方庫的塌碌、一個(gè)是對(duì)Google原生Api封裝台妆,已經(jīng)親測,都可以使用看需挑選吧G欣濉懊缺!如果要是哪里有問題鹃两,歡迎拍磚!途蒋!

后續(xù)的訂單驗(yàn)證步驟馋记,可以參考我的另一篇:Google Play 訂單驗(yàn)證
需要調(diào)用Google Api 坑有點(diǎn)多L菪选H紫啊!!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市嗡综,隨后出現(xiàn)的幾起案子极景,更是在濱河造成了極大的恐慌,老刑警劉巖氢卡,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件译秦,死亡現(xiàn)場離奇詭異击碗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)阁吝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門突勇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坷虑,“玉大人猖吴,你說我怎么就攤上這事」睬” “怎么了拗引?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵矾削,是天一觀的道長。 經(jīng)常有香客問我欲间,道長猎贴,這世上最難降的妖魔是什么蝴光? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任蔑祟,我火速辦了婚禮疆虚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘著拭。我一直安慰自己,他們只是感情好暗赶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布蹂随。 她就那樣靜靜地躺著因惭,像睡著了一般蹦魔。 火紅的嫁衣襯著肌膚如雪勿决。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天嘉冒,我揣著相機(jī)與錄音讳推,去河邊找鬼银觅。 笑死,一個(gè)胖子當(dāng)著我的面吹牛慨仿,可吹牛的內(nèi)容都是我干的镰吆。 我是一名探鬼主播跑慕,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼牢硅,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼芝雪!你這毒婦竟也來了惩系?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎晤柄,沒想到半個(gè)月后芥颈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浇借,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡妇垢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吼和。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骑素。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡末捣,死狀恐怖创橄,靈堂內(nèi)的尸體忽然破棺而出妥畏,到底是詐尸還是另有隱情,我是刑警寧澤燃辖,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布黔龟,位于F島的核電站捌锭,受9級(jí)特大地震影響罗捎,放射性物質(zhì)發(fā)生泄漏桨菜。R本人自食惡果不足惜倒得,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一霞掺、第九天 我趴在偏房一處隱蔽的房頂上張望菩彬。 院中可真熱鬧,春花似錦惨恭、人聲如沸耙旦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽业舍。三九已至,卻和暖如春态罪,著一層夾襖步出監(jiān)牢的瞬間复颈,已是汗流浹背沥割。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國打工帜讲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留椒拗,地道東北人蚀苛。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓堵未,卻偏偏與公主長得像渗蟹,于是被迫代替她去往敵國和親赞辩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子诗宣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容