71

我已经尝试了几天来解决这个问题,使用SDK附带的Dungeons演示代码。我试图用谷歌搜索答案,但找不到。

  • 在地下城演示中,我从开发控制台传递了我的公钥。
  • 签署apk并上传到控制台而不发布。
  • 测试android.test.purchased在控制台上创建并发布订阅的产品列表(我想要的应用程序的主要功能)。

但是我仍然得到一个错误,Signature verification failed然后签名与数据不匹配。我该如何解决这个问题?

public static ArrayList<VerifiedPurchase> verifyPurchase(String signedData, String signature)
{
    if (signedData == null) {
        Log.e(TAG, "data is null");
        return null;
    }
    if (Consts.DEBUG) {
        Log.i(TAG, "signedData: " + signedData);
    }
    boolean verified = false;
    if (!TextUtils.isEmpty(signature)) {

        String base64EncodedPublicKey = "MIIBIjA....AQAB";
        PublicKey key = Security.generatePublicKey(base64EncodedPublicKey);
        verified = Security.verify(key, signedData, signature);
        if (!verified) {
            Log.w(TAG, "signature does not match data.");
            return null;
        }
    }
}

public static boolean verify(PublicKey publicKey, String signedData, String signature)
{
    if (Consts.DEBUG) {
        Log.i(TAG, "signature: " + signature);
    }
    Signature sig;
    try {
        sig = Signature.getInstance(SIGNATURE_ALGORITHM);
        sig.initVerify(publicKey);
        sig.update(signedData.getBytes());
        if (!sig.verify(Base64.decode(signature))) {
            Log.e(TAG, "Signature verification failed.");
            return false;
        }
        return true;
    } catch (NoSuchAlgorithmException e) {
        Log.e(TAG, "NoSuchAlgorithmException.");
    } catch (InvalidKeyException e) {
        Log.e(TAG, "Invalid key specification.");
    } catch (SignatureException e) {
        Log.e(TAG, "Signature exception.");
    } catch (Base64DecoderException e) {
        Log.e(TAG, "Base64 decoding failed.");
    }
    return false;
}
4

14 回答 14

150

在当前的 Google 计费版本中,这个问题仍然存在。基本上android.test.purchased坏了;购买 android.test.purchased 后, Security.java中的verifyPurchase函数将始终失败,并且QueryInventoryFinishedListener将在if (result.isFailure())行停止;这是因为android.test.purchased项目总是无法通过 Security.java 中的TextUtils.isEmpty(signature)检查,因为它不是真正的项目并且服务器没有返回签名。

我的建议(由于缺乏任何其他解决方案)是永远不要使用“android.test.purchased”。网上有各种代码调整,但没有一个是 100% 工作的。

如果您使用了 android.test.purchased 那么摆脱错误的一种方法是执行以下操作:-

  1. 编辑 Security.java 并将 verifyPurchase 中的“return false”行更改为“return true” - 这是暂时的,我们将在一分钟内将其放回原处。
  2. 在您的 QueryInventoryFinishedListener 中,在“if (result.isFailure()) {...}”行之后添加以下内容以消耗并摆脱您永无止境的 android.test.purchased 项目:

    if (inventory.hasPurchase(SKU_ANDROID_TEST_PURCHASE_GOOD)) {  
       mHelper.consumeAsync(inventory.getPurchase(SKU_ANDROID_TEST_PURCHASE_GOOD),null);
       }
    
  3. 运行您的应用程序,以便 consunmeAsync 发生,这将摆脱服务器上的“android.test.purchased”项。

  4. 删除 consumeAsync 代码(或将其注释掉)。
  5. 回到 Security.java,将“return true”改回“return false”。

您的 QueryInventoryFinishedListener 将不再在验证时出错,一切都恢复“正常”(如果您可以这样称呼它)。记住 - 不要再使用 android.test.purchased 了,因为它只会再次导致这个错误......它坏了!测试您购买它的唯一真正方法是上传 APK,等待它出现,然后在启用日志记录的设备上测试它(相同的 APK)。

于 2014-02-28T07:18:57.593 回答
50

是的,问题仍然存在。在我购买了 android.test.purchased 之后,我开始在查询库存时遇到错误。只需清除 Google Play Store 应用程序的数据并运行 Google Play 一次即可修复您的手机。当您清除 Google Play 的数据时,它会忘记您购买了 android.test.purchased

于 2015-02-14T12:00:20.523 回答
23

请检查是否base64EncodedPublicKeyPlay 开发者控制台中的相同。在Developer Console中重新上传 APK 后,公钥可能会更改,如果是这样,请更新您的base64EncodedPublicKey.

于 2013-02-01T13:11:44.403 回答
9

您可以跳过那些“android.test.*”产品 ID 的验证过程。如果您使用的是 TrivialDrive 示例中的示例代码,请打开 IabHelper.java,找到以下行代码,将其更改为

   if (Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) { ... }

进入

   boolean verifySignature = !sku.startsWith("android.test."); // or inplace the condition in the following line
   if (verifySignature && !Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) { ... }

即使您忘记回滚代码,它也是无害的。因此,您可以继续测试进一步的工作流程步骤。

于 2016-06-01T12:35:10.573 回答
8

根据 GMTDev 的回答,我这样做是为了在以最简单的方式消费产品时解决测试问题。在 Security.java 中,将 verifyPurchase() 方法替换为:

public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
    if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
            TextUtils.isEmpty(signature)) {
        Log.e(TAG, "Purchase verification failed: missing data.");
        return BuildConfig.DEBUG; // Line modified by Cristian. Original line was: return false;
    }

    PublicKey key = Security.generatePublicKey(base64PublicKey);
    return Security.verify(key, signedData, signature);
}

我只修改了一行(见评论),这样你就可以保留这样的代码进行调试,并且仍然安全地发布你的发布版本。

于 2014-10-07T15:05:41.670 回答
5

该错误是由于错误的许可证密钥引起的。也许许可证密钥可能来自您的另一个应用程序。

解决方案是使用正确的许可证密钥:

Play 控制台 > 应用程序 > 开发工具 > 许可和应用内计费

于 2017-10-10T07:18:22.990 回答
3

在使用 In-app Billing v3 和包含的实用程序类时,对我有用的是在返回的 onActivityResult 调用中消耗测试购买。

无需对 IabHelper、Security 或任何应用内计费实用程序类进行更改,以避免在未来的测试购买中发生这种情况。

如果您已经尝试购买测试产品并且现在卡在购买签名验证失败错误中,您可能是因为您正在查找此错误的答案,那么您应该:

  1. 进行 GMTDev 建议的更改
  2. 运行应用程序以确保它使用产品
  3. 删除/撤消 GMTDev 的更改
  4. 在 onActivityResult 中实现下面的代码。

这不仅允许购买测试过程流畅,而且还应该避免与 iab在尝试重新购买测试产品时返回项目已拥有”错误的任何冲突问题。

如果这是从片段中调用的并且您的片段的 onActivityResult 没有被调用,那么请确保在必要时从您的父 ActivityFragment 调用 YourFragmentName.onActivityResult(requestCode, resultCode, data) 。这在Calling startIntentSenderForResult from Fragment (Android Billing v3)中有更详细的解释

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_PURCHASE) {

        //this ensures that the mHelper.flagEndAsync() gets called 
        //prior to starting a new async request.
        mHelper.handleActivityResult(requestCode, resultCode, data);

        //get needed data from Intent extra to recreate product object
        int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
        String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
        String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");

        // Strip out getActivity() if not being used within a fragment
        if (resultCode == getActivity().RESULT_OK) {
            try {
                JSONObject jo = new JSONObject(purchaseData);
                String sku = jo.getString("productId");

                //only auto consume the android.test.purchased product
                if (sku.equals("android.test.purchased")) {
                    //build the purchase object from the response data
                    Purchase purchase = new Purchase("inapp", purchaseData, dataSignature);
                    //consume android.test.purchased
                    mHelper.consumeAsync(purchase,null);
                }
            } catch (JSONException je) {
                //failed to parse the purchase data
                je.printStackTrace();
            } catch (IllegalStateException ise) {
                //most likely either disposed, not setup, or 
                //another billing async process is already running
                ise.printStackTrace();
            } catch (Exception e) {
                //unexpected error
                e.printStackTrace();
            }
        }
    }
}

只有当它的 sku 是“android.test.purchased”时它才会删除购买,所以它应该可以安全使用。

于 2015-03-30T14:04:56.273 回答
2

这个解决方案对我有用。我将购买类中的新 verifyPurchase 方法更改为旧方法。

于 2014-04-11T08:57:40.157 回答
1

仅默认测试产品的签名验证失败。快速修复:

  • 转到 IabHelper 类。
  • 反转 的 if 条件Security.verifyPurchase

而已!

当测试产品被实际产品替换时,请记住还原更改

于 2016-01-06T17:54:39.910 回答
1

今天(2018 年 10 月 30 日)遇到了同样的问题(签名验证和取消测试购买)。

签名问题可能是由于这些测试 sku 并不是您的应用程序的一部分,因此没有您的应用程序的签名。我确实向谷歌开了一张票,但不确定他们是否能解决这个问题。正如其他人指出的那样,解决方法是替换代码

if (verifyValidSignature(purchase.getOriginalJson(), purchase.getSignature())) {

if (verifyValidSignature(purchase.getOriginalJson(), purchase.getSignature()) ||
                        (purchase.getSku().startsWith("android.test.")) ) { 

关于“如何摆脱购买 android.test.purchased SKU”,我发现只需简单地重启设备,然后等待一分钟左右和/或重新启动您的应用程序几次即可修复它对我来说(即我不必通过代码“消费”购买)。我猜需要等待,以便 Play 商店完成与 Google 服务器的同步。(不确定这是否会在未来继续以这种方式工作,但如果它现在对你有用,这可能会帮助你继续前进。)

于 2018-10-30T21:58:20.730 回答
0

检查这个答案

您的测试设备上的主帐户是否与您的 Google Play 开发者帐户相同?

如果不是,您将不会在 android.test.* 静态响应上获得签名,除非该应用之前已在 Play 上发布。

请参阅http://developer.android.com/guide/market/billing/billing_testing.html#static-responses-table中的表格 了解完整的条件。

它的评论:

我认为静态 ID 不再返回签名。见 https://groups.google.com/d/topic/android-developers/PCbCJdOl480/discussion

此外,以前来自 Google Play 计费库的示例代码(由许多大型应用程序使用)允许空签名。这就是为什么静态购买在那里起作用的原因。
但这是一个安全漏洞,所以当它发布时,谷歌提交了更新

于 2013-12-06T11:00:10.087 回答
0

我有同样的问题,并按照 @Deadolus 所说的基于https://www.gaffga.de/implementing-in-app-billing-for-android/

关键是即使库存查询结果失败,我们也需要使 SKU 是可消耗的。下面是我如何做到这一点的示例。

IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
        public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
            Log.d(TAG, "Query inventory finished.");

            // Have we been disposed of in the meantime? If so, quit.
            if (mHelper == null) return;

            // Is it a failure?
            if (result.isFailure()) {
                try {
                    Purchase purchase = new Purchase("inapp", "{\"packageName\":\"PACKAGE_NAME\","+
                            "\"orderId\":\"transactionId.android.test.purchased\","+
                            "\"productId\":\"android.test.purchased\",\"developerPayload\":\"\",\"purchaseTime\":0,"+
                            "\"purchaseState\":0,\"purchaseToken\":\"inapp:PACKAGE_NAME :android.test.purchased\"}",
                            "");
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                mHelper.consumeAsync(purchase, null);
                complain("Failed to query inventory: " + result);
                return;
            }

            Log.d(TAG, "Query inventory was successful.");

            /*
             * Check for items we own. Notice that for each purchase, we check
             * the developer payload to see if it's correct! See
             * verifyDeveloperPayload().
             */                   
        }
    };

将上面代码中的 PACKAGE_NAME 替换为您的应用程序的包名称。

于 2015-05-10T09:54:18.350 回答
0

这对我有用:

  1. 调用 BillingClient.querySkuDetailsAsync 以查询项目是否可用
  2. 等待 SkuDetailsResponseListener.onSkuDetailsResponse
  3. 再等 500 毫秒
  4. 使用 BillingClient.launchBillingFlow 开始购买...

第 3 步应该不是必需的,因为当我收到 onSkuDetailsResponse 时它应该没问题,但它不是,必须稍等片刻。购买成功后,不再出现“项目不可用错误”。这就是我测试它的方式:

  1. 清除我的应用数据
  2. 清除 Google Play 数据
  3. 运行应用
  4. 购买 android.test.purchased
  5. 尝试购买我的物品(它因物品不可用而失败)
  6. 使用我上面的解决方案,它有效
于 2018-06-29T09:30:24.897 回答
-1

对于 Cordova 和 Hybrid 应用程序,您需要使用this.iap.subscribe(this.productId)方法来订阅 InAppPurchase。

以下是对我来说工作正常的代码:

 getProdutIAP() {
        this.navCtrl.push('subscribeDialogPage');
        this.iap
            .getProducts(['productID1']).then((products: any) => {
                this.buy(products);
            })
            .catch((err) => {
                console.log(JSON.stringify(err));
                alert('Finished Purchase' + JSON.stringify(err));
                console.log(err);
            });
    }

    buy(products: any) {
        // this.getProdutIAP();
        // alert(products[0].productId);
        this.iap.subscribe(products[0].productId).then((buydata: any) => {
            alert('buy Purchase' + JSON.stringify(buydata));
            // this.sub();
        }).catch((err) => {
            // this.navCtrl.push('subscribeDialogPage');
            alert('buyError' + JSON.stringify(err));
        });
    }

    sub() {
        this.platform.ready().then(() => {
            this.iap
                .subscribe(this.productId)
                .then((data) => {
                    console.log('subscribe Purchase' + JSON.stringify(data));
                    alert('subscribe Purchase' + JSON.stringify(data));
                    this.getReceipt();
                }).catch((err) => {
                    this.getReceipt();
                    alert('subscribeError' + JSON.stringify(err));
                    console.log(err);
                });
        })
    }
于 2019-01-31T10:01:51.377 回答