如何正确恢复应用内购买?

时间:2015-07-06 13:24:31

标签: android in-app-billing

我的一些用户抱怨他们购买的应用内“高级版”无法恢复。

我联系了其中一个用户,然后我发送了一个包含其他消息的APK。这就是:

  1. 当用户再次尝试购买该商品时,会收到消息Unable to buy item (response: 7: Item already owned)。这是预期的消息,因为他已经买了它。
  2. 但是当用户尝试使用相同的SKU恢复购买时,它会返回null / false。
  3. 这是购买的回调:

    @Override
    public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
        if (result.isFailure()) {
            Log.d("debug", "failed - " + result.mMessage);
            return;
        }
    
        Log.d("debug", "success");
    
        // continue with the purchase validation...
    }
    

    这是恢复购买的回调:

    @Override
    public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
        if (result.isFailure()) { 
            Log.d("debug", "inventory: failed (" + result.mMessage + ")");
            return;
        }
    
        if (inventory.hasPurchase(SKU_PREMIUM)) {
            Log.d("debug", "success - purchase restored");
        }
        else {
            Log.d("debug", "failure - no purchase found for this user");
        }
    }
    

    请注意,这种情况只发生在少数用户身上,我经过多次测试,在测试后,我在查询广告资源后收到success - purchase restored消息。

    为了清楚起见,我正在使用v3 API,这个SKU是一个托管项目。我需要检查用户是否已经购买了它(我不想消费它)。

2 个答案:

答案 0 :(得分:1)

不幸的是,这是我的错误。

受影响的用户在非常旧的版本上购买了应用内商品,该版本具有相同SKU的不同的开发者有效负载,具体取决于购买的应用位置。

这些不同的开发者有效负载已从最新版本中删除,但它显然打破了以前的购买。

答案 1 :(得分:0)

如果您使用的是应用内购买的v3版本,则版本3 API支持托管的应用内商品和订阅。

托管应用内商品是Google Play跟踪和管理所有权信息的商品。当用户购买托管的应用内商品时,Google Play会按用户存储每个商品的购买信息。这使您可以在以后随时查询Google Play以恢复特定用户购买的商品的状态。即使用户卸载了应用程序或更改了设备,此信息也会在Google Play服务器上保持不变。

如果您使用的是版本3 API,则还可以在应用程序中使用托管项。您通常会对可以多次购买的物品(例如游戏中的货币,燃料或魔法咒语)实施消费。购买后,在您使用该商品之前,通过向Google Play发送消费请求,无法再次购买被管理商品。

要使用项目,请参阅以下方法:

IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
        public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
            Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase);

            // if we were disposed of in the meantime, quit.
            if (mHelper == null) return;

            if (result.isFailure()) {
                complain("Error purchasing: " + result);
                return;
            }
            if (!verifyDeveloperPayload(purchase)) {
                complain("Error purchasing. Authenticity verification failed.");
                return;
            }
            Log.d(TAG, "Purchase successful.");

            if (purchase.getSku().equals(SKU_PREMIUM)) {
                //consumeItem();
                Constant.showProgressDialog(getActivity());
                mHelper.consumeAsync(purchase, mConsumeFinishedListener);
            }


        }
    };

同时将此方法添加到您的活动中

IabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() {
        public void onConsumeFinished(Purchase purchase, IabResult result) {
            Log.d(TAG, "Consumption finished. Purchase: " + purchase + ", result: " + result);

            // if we were disposed of in the meantime, quit.
            if (mHelper == null) return;

            // We know this is the "gas" sku because it's the only one we consume,
            // so we don't check which sku was consumed. If you have more than one
            // sku, you probably should check...
            if (result.isSuccess()) {
                // 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
                Log.d(TAG, "Consumption successful. Provisioning.");

            }
            else {
                complain("Error while consuming: " + result);
            }

            Log.d(TAG, "End consumption flow.");
        }
    };

在你的IabHelper.java类中有以下方法:

int queryPurchases(Inventory inv, String itemType) throws JSONException, RemoteException {
        // Query purchases
        logDebug("Querying owned items, item type: " + itemType);
        logDebug("Package name: " + mContext.getPackageName());
        boolean verificationFailed = false;
        String continueToken = null;

        do {
            logDebug("Calling getPurchases with continuation token: " + continueToken);
            Bundle ownedItems = mService.getPurchases(3, mContext.getPackageName(),
                    itemType, continueToken);

            int response = getResponseCodeFromBundle(ownedItems);
            logDebug("Owned items response: " + String.valueOf(response));
            if (response != BILLING_RESPONSE_RESULT_OK) {
                logDebug("getPurchases() failed: " + getResponseDesc(response));
                return response;
            }
            if (!ownedItems.containsKey(RESPONSE_INAPP_ITEM_LIST)
                    || !ownedItems.containsKey(RESPONSE_INAPP_PURCHASE_DATA_LIST)
                    || !ownedItems.containsKey(RESPONSE_INAPP_SIGNATURE_LIST)) {
                logError("Bundle returned from getPurchases() doesn't contain required fields.");
                return IABHELPER_BAD_RESPONSE;
            }

            ArrayList<String> ownedSkus = ownedItems.getStringArrayList(
                        RESPONSE_INAPP_ITEM_LIST);
            ArrayList<String> purchaseDataList = ownedItems.getStringArrayList(
                        RESPONSE_INAPP_PURCHASE_DATA_LIST);
            ArrayList<String> signatureList = ownedItems.getStringArrayList(
                        RESPONSE_INAPP_SIGNATURE_LIST);

            for (int i = 0; i < purchaseDataList.size(); ++i) {
                String purchaseData = purchaseDataList.get(i);
                String signature = signatureList.get(i);
                String sku = ownedSkus.get(i);
                if (Security.verifyPurchase(mSignatureBase64, purchaseData, signature)) {
                    logDebug("Sku is owned: " + sku);
                    Purchase purchase = new Purchase(itemType, purchaseData, signature);

                    if (TextUtils.isEmpty(purchase.getToken())) {
                        logWarn("BUG: empty/null token!");
                        logDebug("Purchase data: " + purchaseData);
                    }

                    // Record ownership and token
                    inv.addPurchase(purchase);
                }
                else {
                    logWarn("Purchase signature verification **FAILED**. Not adding item.");
                    logDebug("   Purchase data: " + purchaseData);
                    logDebug("   Signature: " + signature);
                    verificationFailed = true;
                }
            }

            continueToken = ownedItems.getString(INAPP_CONTINUATION_TOKEN);
            logDebug("Continuation token: " + continueToken);
        } while (!TextUtils.isEmpty(continueToken));

        return verificationFailed ? IABHELPER_VERIFICATION_FAILED : BILLING_RESPONSE_RESULT_OK;
    }

在此处您将获得拥有物品清单