如何在本地最好地保存InApp购买状态?

Ton*_*oni 34 security android in-app-purchase

我正处于完成我的第一个应用程序的边缘,剩下的最后一件事就是实现IAP计费,这就是为什么我目前正在阅读相关主题(包括加密,混淆和东西等安全问题).

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

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

mHelper.queryInventoryAsync(mGotInventoryListener);

我的第一个问题:这是否意味着用户始终需要在应用启动时拥有互联网/数据连接,才能切换到高级版本?如果用户没有互联网连接怎么办?他会选择我猜的精简版,我觉得这很烦人.

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

然而,一个人根本无法在某处保存"isPremium"标志,因为这太容易被发现.

所以我在考虑这样的事情:

  • 用户购买Premium
  • 应用程序获取IMEI/Device-ID,XOR使用硬编码的String键对其进行编码,并将其保存在应用程序数据库中.

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

  • App从数据库中获取编码的String,对其进行解码并检查decodeString == IMEI.如果是 - >溢价
  • 如果不是,则将调用正常的queryInventoryAsync以查看用户是否购买了premium.

您如何看待这种方法?我知道这不是超级固定,但对我而言,更重要的是用户不会烦恼(比如强制性的互联网连接),而不是应用程序将无法解决(无论如何这是不可能的).你有其他一些提示吗?

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

对这种方法的任何想法和评论都是受欢迎的,您认为这是一个很好的解决方案吗?或者我错过了什么/朝着错误的方向前进?

Ne0*_*Ne0 17

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

所以这就是我的所作所为

// 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 + ").");
       }
    }
};
Run Code Online (Sandbox Code Playgroud)

那么这里发生了什么?

startSetup成功完成IAB设置和呼叫(只要它已经通过互联网连接运行一次,并且设置正确,它将始终成功)我们打电话queryInventoryAsync找出已经购买的东西(如果已经购买的话)在线时调用它总是在离线时工作).

因此,如果购买成功完成(只能在网上完成),我们会打电话queryInventoryAsync确保在线时调用.

现在没有必要存储与购买有关的任何内容,并且使您的应用程序不那么容易破解.

我已经测试了很多方法,飞行模式,再次打开设备,唯一令人困惑的是清除手机上某些Google应用程序中的数据(不太可能发生!).

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

  • 好的,我知道了.不要调用mHelper.queryInventoryAsync(mGotInventoryListener).而是调用mHelper.queryInventoryAsync(false,mGotInventoryListener).如果调试IabHelper代码,您将看到将调用mService.getPurchases(),而如果将标志设置为false,则不会调用mService.getSkuDetails().如果省略该标志,则IabHelper将其设置为true.即使您的设备没有网络连接,甚至跨设备重新启动,getPurchases()也会成功查询所购商品的本地缓存. (8认同)
  • 似乎信息暂时缓存,但在设备重启时不会持续存在.你能证实这一点吗? (4认同)

Dis*_*Dev 5

我将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.");

                            }
                        }
                    });
               }
         }
    });
}
Run Code Online (Sandbox Code Playgroud)

如果用户购买了该项目,这将在重新启动和没有网络连接的情况下工作.


Dre*_*rew 4

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

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

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

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