I'm on the edge of finishing my first app, and one last remaining thing is to implement IAP billing, so that's why I am currently reading quite a lot about the topic (including security concerns like encryption, obfuscation and stuff).
My app is a free version, with the ability to upgrade to full verison via IAP, so there would be just one managed purchase item "premium". I have a few questions about this:
In the Google IAP API example (trivialdrivesample), there's always the IAP check in MainActivity to see if the user bought the premium version, done via
mHelper.queryInventoryAsync(mGotInventoryListener);
My first concern: This does mean that the user always needs to have an internet/data connection at app-startup, to be able switch to the premium version right? What if the user doesn't have an internet connection? He would go with the lite version I guess, which I would find annoying.
So I thought about how to save the isPremium status locally, either in the SharedPrefs or in the app database. Now, I know you can't stop a hacker to reverse engineer the app, no matter what, even so because I don't own a server to do some server-side validation.
Nevertheless, one simply can't save an "isPremium" flag somewhere, since that would be too easy to spot.
So I was thinking about something like this:
- User buys Premium
- App gets the IMEI/Device-ID and XOR encodes it with a hardcoded String key, saves that locally in the app database.
Now when the user starts the app again:
- App gets encoded String from database, decodes it and checks if decodedString == IMEI. If yes -> premium
- If no, then the normal queryInventoryAsync will be called to see if the user bought premium.
What do you think about that approach? I know it's not supersecure, but for me it's more important that the user isn't annoyed (like with mandatory internet connection), than that the app will be unhackable (which is impossible anyway). Do you have some other tips?
Another thing, which I currently don't have a clue about, is how to restore the transaction status when the user uninstalls/reinstalls the app. I know the API has some mechanism for that, and aditionally my database can be exported and imported through the app (so the encoded isPremium flag would be exportable/importable as well). Ok, I guess that would be another question, when the time is right ;-)
Any thoughts and comments to this approach are welcome, do you think that's a good solution? Or am I missing something/heading into some wrong direction?
Any purchase you make within an app–rather than in Google Play itself–is an in-app purchase. Google Play tracks these in-app purchases. Some are permanent and can be recovered on a new device, but others are used up after you buy them. This only applies to purchases you make within apps.
Tap on your profile icon on the top-right corner of the screen, then select Settings in the pop-up menu. 3. On the Settings page, tap Authentication, and then tap Require authentication for purchases. 4.
I too was making the same investigations, but during my testing I figured out that you do not need to store it, as Google do all the caching you need and I suspect (though I have not investigated it) that they are doing so as securely as possible (seeing as it in their interest too!)
So here is what i do
// 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 + ")."); } } };
So what happens here?
The IAB is set up and calls startSetup
, on a successful completion (as long as it has been run once with an internet connection and is set up correctly it will always succeed) we call queryInventoryAsync
to find out what is already purchased (again if this has been called while online it always works while offline).
So if a purchase is completed successfully (as can only be done while online) we call queryInventoryAsync
to ensure that it has been called while online.
Now there is no need to store anything to do with purchases and makes your app a lot less hackable.
I have tested this many ways, flight mode, turning the devices off an on again and the only thing that messes it up is clearing data in some of the Google apps on the phone (Not likely to happen!).
Please contribute to this if you have different experiences, my app is still in early testing stage.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With