【问题标题】:How to properly restore an in-app purchase?如何正确恢复应用内购买?
【发布时间】:2015-07-06 13:24:31
【问题描述】:

我的一些用户抱怨他们购买的应用内“高级版”没有得到恢复。

我联系了其中一位用户,并发送了一个包含其他消息的 APK。这就是发生的事情:

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

这是购买的回调:

@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 是托管项目。我需要检查用户是否已经购买(我不想消费)。

【问题讨论】:

  • 你的意思是inventory.hasPurchase(SKU_PREMIUM)返回假吗?
  • 没错,仅在某些用户的设备上。当我在我的设备上测试时,它返回 true。

标签: android in-app-billing


【解决方案1】:

很遗憾,这是我的错误。

受影响的用户在非常旧的版本上购买了应用内产品,该版本具有同一 SKU 的不同开发人员有效负载,具体取决于购买是在应用中的哪个位置完成的。

这些不同的开发人员有效载荷已从最新版本中删除,这显然破坏了之前的购买。

【讨论】:

    【解决方案2】:

    如果您使用的是 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;
        }
    

    在此您将获得拥有的项目列表

    【讨论】:

    • 感谢您的回答!我正在使用 v3 API,此 SKU 是托管项目。我需要检查用户是否已经购买(我不想消费)。
    • 您还可以在 Playstore 上获取与您的 id 相关的拥有项目列表
    • 不是onQueryInventoryFinished响应吗?
    • BundleownedItems = mService.getPurchases(3, mContext.getPackageName(), itemType, continueToken); ArrayList mySkus、myPurchases、mySignatures; mySkus =ownedItems.getStringArrayList(RESPONSE_INAPP_ITEM_LIST); myPurchases =ownedItems.getStringArrayList(RESPONSE_INAPP_PURCHASE_DATA_LIST); mySignatures =ownedItems.getStringArrayList(RESPONSE_INAPP_PURCHASE_SIGNATURE_LIST);
    • IabHelper里面也有类似的代码吧?我昨天检查了一下,它返回了一个空的 ownItems 列表。我会再次检查你的代码!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多