Testing In-App Billing on Android

I’ve spent the last few weeks extending Wearable Widgets with in-app purchasing (variously known as IAP, in-app billing, or IAB). Honestly, there’s no good reason why this should have taken weeks. I can immediately think of a couple of reasons why it has, though.

The first is security. Because IAB is a commercial transaction, and because some lowlife will always be looking to scam you out of two bucks, you need to apply various layers of security to it – and these add time and effort.

A paradoxical aspect of this is that you’re specifically advised not to use any code examples you might find online, especially those in the documentation, because the scum who crack these apps will have seen all those code structures before – and will probably have scripts ready to break it.

But on the other hand, it seems to me that there’s a real downside to asking myriad developers like me to roll their own IAB solutions. Most of us are developers with no significant security experience – meaning that our homebrewed solutions are probably riddled with security holes and rookie mistakes, making them much less secure than code that’s been developed by security professionals. It’s a bit of a programming paradox, really.

Further, Google also includes something they call an IabHelper, a pre-written wrapper around all of the IAB routines, and many of their examples are based around this helper class. But it seems to me that this flies in the face of the roll-yer-own security recommendation; I have to believe that any two-bit script-kiddie app-cracker knows right where IabHelper lives, exactly how to recognize it (even in obfuscated code), and how to exploit it. Seriously, why would Google even provide this?

But that’s not really what I want to write about here.

The topic I want to cover, the other area which has cost me a lot of time in my IAB implementation, is testing. Part of that is simply because it’s a complicated beast – OK, fair enough. But a big part of it is because the documentation for testing this stuff is about as clear as mud, if not actually misleading in some places. So I thought I’d share my experience in testing Google’s IAB as of mid-2014. Be warned, from here on in this entry’s going to be pitched at Android devs who are elbow-deep in IAB; I’m not going to explain every term along the way. If you’re not a developer, you may or may not find it worth your while to read on. But if you are in the target demographic, hopefully what follows will save you some anguish.

Google provides a couple of doc pages about IAB testing, and you should start by reading those. They lay the groundwork, and nothing they tell you is really wrong – it’s just not quite the whole story. But nonetheless, you should start there, and get some understanding of the lay of the land.

Now, I’m going to tell you the real story. Here’s the first gotcha: that first link above says that there are two ways to test your IAB app, test purchases and real purchases. And the page no sooner describes those two before it goes on to discuss a third way, static responses. So the main thing I’d like to do here is to lay out the three ways to test IAB, with the things you need to know about each. I’ll present them in the order you should do them; complete your testing with one before you move on to the next.

Stage 1: Static Responses

This is where you should start: write your first pass at your IAB implementation, unit-test all the routines involved, and send the transactions off to Google Play with an SKU of android.test.purchased. [You can also use .canceled, .refunded, and .item_unavailable to test unsuccessful responses. But let’s go for success right now.] These are the easiest tests, because you don’t need to upload or sign your APK in any way (other than the normal debug signature that the IDE automatically applies). These tests can also be run by any user – although apparently you’ll get incomplete information returned if you don’t test using your developer account, so I recommend sticking with a device registered to the same Google ID as your Play developer console. When you issue an IAB request with one of the static-response SKUs, the process will look something like this:

stage_1_1
  1. The purchase dialog Google Play shows will be completely dummied-up (see above).
  2. Assuming you’re using android.test.purchased, the transaction always succeeds.
  3. It returns to the calling activity’s onActivityResult() handler with a resultCode of -1, and a blank INAPP_DATA_SIGNATURE, but otherwise real values matching what you sent in the purchase request.

One important point I didn’t see anywhere in the docs: after you use a static-response SKU once, that SKU is now “owned” by the user that the device is registered to. And it will show as such in any subsequent IAB calls, which makes retesting a hassle. Apparently, it will reset by itself in a day or so, though I didn’t test this. I didn’t have the patience to wait a day between each debugging run of my app. But it turns out that you can also consume it in your app, with code like this:

IInAppBillingService.consumePurchase(
    3, context.getPackageName(), purchaseToken);

where purchaseToken is returned from the purchase request you sent. In my experience with static responses, the purchaseToken was a simple construct like “inapp:my.package.name:android.test.purchased”, but YMMV.

And that’s about it for static responses. When you’ve done all the testing you can with them, you’re ready to move on to…

Stage 2: Real SKU, test user

Update 10 May 2015:

Since I wrote this post 11 months ago, Google has completely overhauled their  “test purchase” procedure. At the least:

    • Apps can no longer be in draft on Google Play for non-static purchases; they must be published to some channel (alpha, beta, or production).
    • IAB transactions for apps published to alpha channels do not cost whitelisted users real money. This was how I expected things to work last year (see below); now, they actually do.
    • With the advent of Android Studio, it’s now possible to debug an app signed with a production cert. So handy!

I may do a new writeup on it at some point, but until I do, be aware that much of whatI did write in this section is no longer correct – and that Google’s docs are actually pretty reasonable now.

This is the case that Google calls test purchases, and it involves sending your real item SKUs but in a test mode. Everything is like a real purchase, except that no money actually changes hands. It sounds so simple in the documentation… but there are a couple of serious pitfalls.

First, you’ll need to get things set up on your Google Play developer console. You’ll also need to wait several hours after doing these changes for them to roll out through Google’s server network.

  • You’ll need to have created your IAB items (on the In-app Products tab of your app in the console)
  • The user ID(s) you’re going to test with must be whitelisted for IAB testing: Settings > Account Details > License Testing. Note that real SKUs can’t be purchased by the user who owns the Google Play account, even in test mode.
  • You need to upload a signed APK, with a higher versionCode (in the manifest) than anything you have published on Play.

When you actually do your testing, it will also need to be with a signed APK, and one that has the same versionCode as the uploaded-but-unpublished APK in the last step above.

The bad news here is that there’s no using the debugger at this stage: logcat is your friend. But the good news is that it doesn’t actually have to be the same APK as you’ve uploaded to the dev console; it just needs to have the same versionCode. This requirement – for an uploaded but unpublished version of your APK – is never really, explicitly covered in the docs. It’s mentioned obliquely, but nowhere near as directly as it needs to be.

If your test version hasn’t been uploaded to the APK tab on the console, the IAB call will fail. OTOH, if it is published at all – even in the alpha or beta channel – your test users will really get billed for the IAP.

This one took me days to work out: silly me, I thought the alpha channel was specifically for this kind of testing, and as long as I had the user’s Gmail address on the whitelist, the transaction wouldn’t post. Wrong. The whitelist is ignored for published APK versionCodes.

In fact, it’s even a bit worse than that – the whitelist is ignored for all versionCodes less than or equal to anything published. Let me illustrate that with a concrete example:

  1. The production versionCode of my app is 9 – that’s my baseline, before I started implementing IAB.
  2. After I had a first cut of IAB done – with versionCode 10 – I published it to my alpha-test group and had a whitelisted user try it. He got charged, because 10 wasn’t unpublished.
  3. Learning from these mistakes, I uploaded a later version (13) to Google Play, but did not publish it. I then emailed the APK to some whitelisted testers, who successfully used the IAB feature without being charged.
  4. Moving closer to release, I built versionCode 14 and published it to beta. I fully expect users of this version to be charged, and they are.
  5. However: the next day, one of my whitelisted test group from step 3 – still on versionCode 13 – tried out the IAB and got charged for it. My best explanation for this is that, even though 13 is still unpublished, the whitelist is ignored, because 14 is published.

The bottom line is, upload to Google Play but don’t publish anything at this stage. But once you have all those pieces in place, and have given them time to take effect, here’s how it’ll go:

stage_2_1
  1. The purchase dialog will be real, but with the extra “test order” line shown above.
  2. The purchase always succeeds, AFAICT
  3. The return values are all real (token, payload, etc)
  4. The transaction appears in your Google Wallet Merchant console with a “Test:” prefix

As with static responses, this SKU is now owned by the user, causing a similar headache for any retesting you need to do. You’ll need to cancel it from your Merchant console (or alternatively, it will apparently expire in a week or two).

Be aware that, in addition to cancelling the purchase, you’ll also need to clear the cache for Google Play Store and Play Services on the device – and probably reboot, and maybe wait an hour or so – before the SKU will stop showing up as owned.

Stage 3: Real Purchases

This isn’t really “testing” IAB any more; everything is live, from the APK down to the financial transaction. But Google includes it on their testing page, so I’ll discuss it briefly here as well. Basically, this is the scenario your user will be in whenever the versionCode of the APK they’re running matches one that’s published on Google Play, in any channel (Alpha, Beta, or Production). It’s also the way the purchase will play out if the user isn’t on your IAB, or the whitelist entry hasn’t percolated through the Googlenet.

As confirmation of any of these situations, any time Google Play shows a purchase dialog without the “test order” verbage (compare the above and below screenshots), the user will get charged for the purchase.

 stage_3_1

I’m not going to go through the whole process here, because it’s a live transaction, and everything happens accordingly. If you do need to retest in this case, its like Stage 2: clear the cache for Google Play Store and Services, reboot the phone, wait an hour.

Time Is Money

One recurring theme you might notice from the above is all of the waiting that goes on. From hours to weeks, there are many, many aspects of IAB testing which require you to just twiddle your thumbs (or go read Engadget) for extended periods. Which is why testing this has taken so damn long. As of today, I’m really not sure that IAB was worth doing, especially for (what should have been) a dead-simple implementation like mine: I have exactly one product, essentially an unlock key to turn the “trial” version of my app into the “full” version. There are several other tried-and-true ways to implement this sort of freemium model, and I’m not at all convinced that IAB was the right one to choose. Sure, it may be slightly lower-friction for the users – but enough so to justify weeks of extra developer time? Will it actually gain that many more conversions? It’s impossible to know.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s