36

我即将完成我的第一个应用程序,最后剩下的一件事是实施 IAP 计费,这就是为什么我目前正在阅读很多关于该主题的内容(包括加密、混淆等安全问题)。

我的应用程序是免费版本,能够通过 IAP 升级到完整版本,因此只有一个托管购买项目“高级”。我对此有几个问题:

在 Google IAP API 示例(trivialdrivesample)中,MainActivity 中始终存在 IAP 检查以查看用户是否购买了高级版本,通过

mHelper.queryInventoryAsync(mGotInventoryListener);

我的第一个担忧:这确实意味着用户在应用程序启动时总是需要有互联网/数据连接,才能切换到高级版本,对吧?如果用户没有互联网连接怎么办?我猜他会选择精简版,我会觉得这很烦人。

所以我想到了如何在本地保存 isPremium 状态,无论是在 SharedPrefs 中还是在应用程序数据库中。现在,我知道无论如何你都无法阻止黑客对应用程序进行逆向工程,即使如此,因为我没有服务器来进行一些服务器端验证。

然而,人们根本无法在某处保存“isPremium”标志,因为这太容易被发现了。

所以我在想这样的事情:

  • 用户购买高级版
  • 应用程序获取 IMEI/设备 ID,并使用硬编码的字符串密钥对其进行异或编码,并将其保存在本地应用程序数据库中。

现在当用户再次启动应用程序时:

  • 应用程序从数据库中获取编码字符串,对其进行解码并检查 decodedString == IMEI。如果是 -> 溢价
  • 如果没有,那么将调用普通的 queryInventoryAsync 来查看用户是否购买了溢价。

您如何看待这种方法?我知道它不是超级安全的,但对我来说,更重要的是不要让用户感到恼火(比如强制互联网连接),而不是让应用程序无法破解(无论如何这是不可能的)。你还有其他一些技巧吗?

我目前不知道的另一件事是如何在用户卸载/重新安装应用程序时恢复交易状态。我知道 API 有一些机制,此外我的数据库可以通过应用程序导出和导入(因此编码的 isPremium 标志也可以导出/导入)。好的,我想这将是另一个问题,时机成熟;-)

欢迎对这种方法提出任何想法和意见,您认为这是一个好的解决方案吗?还是我错过了什么/朝着错误的方向前进?

4

4 回答 4

18

我也在做同样的调查,但在我的测试中,我发现你不需要存储它,因为谷歌做了你需要的所有缓存,我怀疑(虽然我没有调查过)他们这样做是安全的尽可能(认为这也符合他们的利益!)

所以这就是我所做的

// Done in onCreate
mHelper = new IabHelper(this, getPublicKey());

mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
      if (!result.isSuccess()) {
         // Oh noes, there was a problem.
         Log("Problem setting up In-app Billing: " + result);
      } else {
         Log("onIabSetupFinished " + result.getResponse());
         mHelper.queryInventoryAsync(mGotInventoryListener);
     }
    }
});

// Called by button press
private void buyProUpgrade() {
    mHelper.launchPurchaseFlow(this, "android.test.purchased", 10001,   
           mPurchaseFinishedListener, ((TelephonyManager)this.getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId());
}

// Get purchase response
private IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
    public void onIabPurchaseFinished(IabResult result, Purchase purchase) 
    {
       if (result.isFailure()) {
          Log("Error purchasing: " + result);
          return;
       }      
       else if (purchase.getSku().equals("android.test.purchased")) {
      Log("onIabPurchaseFinished GOT A RESPONSE.");
              mHelper.queryInventoryAsync(mGotInventoryListener);
      }
    }
};

// Get already purchased response
private IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
    public void onQueryInventoryFinished(IabResult result,
       Inventory inventory) {

       if (result.isFailure()) {
         // handle error here
           Log("Error checking inventory: " + result); 
       }
       else {
         // does the user have the premium upgrade?        
         mIsPremium = inventory.hasPurchase("android.test.purchased");        
         setTheme();

         Log("onQueryInventoryFinished GOT A RESPONSE (" + mIsPremium + ").");
       }
    }
};

那么这里会发生什么?

IAB 已设置并调用startSetup,在成功完成后(只要它已通过 Internet 连接运行一次并且设置正确,它将始终成功)我们调用queryInventoryAsync以找出已经购买的内容(再次,如果这已经在线时调用它总是在离线时工作)。

因此,如果购买成功完成(只能在在线时完成),我们会致电queryInventoryAsync以确保它已在在线时被调用。

现在无需存储与购买有关的任何内容,并使您的应用程序更不易被破解。

我已经测试了很多方法,飞行模式,再次关闭设备,唯一搞砸的就是清除手机上某些谷歌应用程序中的数据(不太可能发生!)。

如果您有不同的经验,请为此做出贡献,我的应用程序仍处于早期测试阶段。

于 2013-03-22T11:39:57.383 回答
7

我将 ne0 的答案重构为静态方法,包括来自 snark 的评论。

我在我的应用程序启动时调用此方法 - 您需要在TODO

/**
 * This is how you check with Google if the user previously purchased a non-consumable IAP
 * @param context App Context
 */
public static void queryPlayStoreForPurchases(Context context)
{
    final IabHelper helper = new IabHelper(context, getPublicKey());

    helper.startSetup(new IabHelper.OnIabSetupFinishedListener() 
    {
         public void onIabSetupFinished(IabResult result) 
         {
               if (!result.isSuccess()) 
               {
                   Log.d("InApp", "In-app Billing setup failed: " + result);
               } 
               else 
               {  
                    helper.queryInventoryAsync(false, new IabHelper.QueryInventoryFinishedListener()
                    {
                        public void onQueryInventoryFinished(IabResult result, Inventory inventory)
                        {
                            // If the user has IAP'd the Pro version, let 'em have it.
                            if (inventory.hasPurchase(PRO_VERSION_SKU))
                            {
                                //TODO: ENABLE YOUR PRO FEATURES!! 

                                Log.d("IAP Check", "IAP Feature enabled!");
                            }
                            else
                            {
                                Log.d("IAP Check", "User has not purchased Pro version, not enabling features.");

                            }
                        }
                    });
               }
         }
    });
}

如果用户购买了该项目,这将在重新启动且无需网络连接的情况下工作。

于 2015-08-10T23:28:50.253 回答
4

由于您已经知道使用此系统不可能使其无法破解,因此我建议不要尝试阻止黑客入侵。您提出的建议被称为“通过默默无闻的安全性”,通常是一个坏主意。

我的建议是先尝试queryInventoryAsync(),并且仅在没有互联网连接的情况下检查您的“isPremium”标志。

还有一些潜在的其他方法可以解决这个问题,例如拥有单独的免费和高级应用程序,而不是应用程序内购买。其他人如何处理这个问题以及谷歌提供的工具可能需要调查。

queryInventoryAsync将自动考虑卸载和重新安装,因为它会跟踪登录用户的购买。

于 2013-01-27T03:59:13.333 回答
0

是的,购买可以离线检索。此外,我正在考虑在显示计费 UI 之前计算用户打开应用程序的次数作为一种机制。

于 2021-04-26T19:30:54.973 回答