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

时间:2013-01-09 09:21:23

标签: android security in-app-purchase

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

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

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

完成
  

mHelper.queryInventoryAsync(mGotInventoryListener);

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

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

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

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

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

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

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

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

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

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

4 个答案:

答案 0 :(得分:17)

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

所以这就是我做的事情

// 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(只要已通过互联网连接运行一次并且设置正确,它将始终成功)我们致电queryInventoryAsync找出已经购买的东西(再次,如果在线时调用它,它总是在离线时工作)。

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

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

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

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

答案 1 :(得分:5)

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

我的应用开始时调用此方法 - 您需要在@Path("/{manageElement}") @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Object update(@Context SecurityContext context, @PathParam(MANAGED_ELEMENET) String manageElement, @QueryParam("prettify") boolean prettyOutput, String content) { Request request = new Request(); request.setActionType(ActionType.UPDATE_ME); request.setRequestBody(content); request.setManageElement(manageElement); request.setRequester(GENERIC_USER); if(prettyOutput) { Object response = service.service(request); String resp = getPrettyGson().toJson(response); return Response.status(Response.Status.OK).entity(resp).build(); } else { Object response = service.service(request); String resp = getGson().toJson(response); return Response.status(Response.Status.OK).entity(resp).build(); } } private Gson getPrettyGson() { return new GsonBuilder() .setPrettyPrinting() .setDateFormat("yyyy-MM-dd HH:mm:ss") .create(); } private Gson getGson() { return new GsonBuilder() .setDateFormat("yyyy-MM-dd HH:mm:ss") .create(); }

启用您的功能
TODO

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

答案 2 :(得分:3)

由于你已经知道使用这个系统不可能让它无法解决,我建议不要试图阻止黑客入侵。你提出的建议被称为“通过默默无闻的安全”,通常是一个坏主意。

我的建议是首先尝试queryInventoryAsync(),如果没有互联网连接,只检查'isPremium'标志。

还有一些其他可能的解决方法,例如使用单独的免费和高级应用,而不是应用内购买。其他人如何处理此问题以及Google提供的工具可能需要进行调查。

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

答案 3 :(得分:0)

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