Google Play Billing acknowledge implementation

At the end of the 2019 Google Play announced that starting from the beginning of 2021 new submissions of apps with Google Play Billing implementation based on AIDL would be rejected at review. All newer versions of apps should use Google Billing Library 2.0+ instead.

Here are my thoughts and notes about implementing Billing Library 2.0 support from the perspective of backend engineer.

Google Play have published AIDL to Billing Library migration guide and gave enough time for companies to get acquainted with it and then implement.
At the same times it made things messy for companies having apps which don’t force users to update it. Cause now backend processing of google play purchases should be implemented separately for clients with AIDL and with Billing Library.

Anyway new Billing Library has several amazing (at least for user) features implemented. My favourite is that acknowledgment became mandatory and if it was not done within 3 days – user’s purchase would be cancelled & refunded.
They even gave the developer place for making its own choice to acknowledge the purchase from native app with Billing Library or from the backend using server to server with Purchases API.

But for apps with huge DAO and lots of purchases I actually see no choice at all. Such apps need server processing of the purchases for single processing point of statistics, for activating services/products which must be available on all platforms and etc etc.
It’s hard to implement secure, consistent and resilient client acknowledge & backend call for internal processing at the same time. And very rare cases which are not considered valuable or even noticed at not so big apps, because the are happening with probability like 0.001% start happening all the time at large scale.


Client acknowledge & subsequent backend call.

Error could happen right after successful acknowledgement of purchase, securing the income for developer, and before sending request at backend. And it may be hard to retry it from client. And impossible to handle without customer support and engineer spending his time on such incident if app’s data would be removed (e.g. deinstall / install).
So user may not get a service he paid for at all. That’s a worst possible scenario – so this scheme doesn’t fit.


Backend call & subsequent client acknowledge.

On the other hand you can initiate backend processing first and upon receiving successful response the app calls Billing Library’s acknowledge. This way it may fail to acknowledge the purchase, e.g. due to network flaps, which could be easily fixed with retry policy in the app and resending all not yet acknowledged purchases associated with current Google Play Account.
But if there would be a bug in you backend (yeah yeah, sounds ridiculous! it’s impossible!) you may enable the service associated with the product but not able to provide the correct response to the app. And retrying backend calls from client, even if retry policy is implemented well, might not help if the bug is stable. Also such bug may be noticed after a huge delay, cause user are getting the service (or product) and most probably you wouldn’t receive complaints pointing on it. And if the acknowledge doesn’t happen it leads to refunding all purchases made before bug is fixed and new backend’s version is released to production.


So we have stuck with backend billing acknowledge. And here are some glitches you may experience with Google Play Purchases API products.acknowledge or subscriptions.acknowledge methods:

I. Developer payload

If you use acknowledge without passing developerPayload – be prepared to receive response with 204 No Content http status code and empty response body. It’s a valid state, cause calling product.get or subscription.get in such purchase will return "acknowledgementState": 1.
But still such behaviour is not described in API documentation.

Also note that product.get or subscription.get called after acknowledgement without payload would return JSON object with field developerPayload equals to an empty string, not null or absent but empty string.

{
...
"developerPayload": "",
...
}
II. Purchases stuck with 404 error


Once, for 5-7 days in a row, we received 404 http status code responses with body containing error “The purchase token was not found”. The failure occurred only for a small fraction of purchases during acknowledge method call.

{
  "error": {
    "code": 404,
    "message": "The purchase token was not found.",
    "errors": [
      {
        "message": "The purchase token was not found.",
        "domain": "global",
        "reason": "purchaseTokenNotFound",
        "location": "token",
        "locationType": "parameter"
      }
    ]
  }
 }

It was strange cause first of all backend checks the state of purchase using product.get or subscription.get method, so all these purchases definitely existed and API was returning valid objects. But acknowledge still failed with 404, dozens of retries didn’t help too.

At the end, such purchases, both subscriptions and IAP, were refunded by Google, cause acknowledge didn’t happen in a timely manner.
After 5-7 days these 404 errors were gone. Either this bug was fixed or such state were caused by series of temporal state corruptions.

Leave a Reply