Unprocessed Payments
Ivan worked for a mobile games company that mass-produced "freemium" games. These are the kinds of games you download and play for free, but you can pay for in-game items or perks to make the game easier-or in some cases, possible to beat once you get about halfway through the game.
Since that entire genre is dependent upon microtransactions, you'd think developers would have rock-solid payment code that rarely failed. Code that worked almost all the time, that was nearly unhackable, and would provide a steady stream of microtransactions to pay everybody's salary. But who am I kidding? This is The Daily WTF! Of course reliable in-app payment code for a company completely dependent on microtransactions isn't going to happen!
Ivan found himself debugging the company's core payment library, originally written by a developer named Hanlon who happened to have a PhD in Astrophysics. Hanlon still worked for the company but had moved on to other roles. However, he reported a bug that Ivan had to fix. The payment library would never report PaymentValidationResult.Success, only PaymentValidationResult.Error. In-app purchases were completely broken- across their entire library of released freemium games- and this was obviously unacceptable.
Ivan went spelunking but quickly found that the web service payment did indeed return PaymentValidationResult.Success when a microtransaction was successful. So what was the client-side library doing wrong? He opened that project up and found this:
PaymentResponse response = _networking.SendRequest(request); PaymentValidationResult result = PaymentValidationResult.Success; if (response.StatusCode.Is(400)) { result = PaymentValidationResult.Failure; } else if (response.HasError()) { result = PaymentValidationResult.Error; } return result;
It assumes from the start that the payment was successful, and only returns an error if the server response explicitely says something bad happened. In the case of timeouts, corruption, or other network errors when a server response is not received, this library would actually report to the application that the microtransaction succeeded, and the player could get free stuff!
But this doesn't explain why it always failed.
He decided to peek at the response.HasError() function.
public bool HasError() { bool hasError = true; if (BodyString != null) { var body = ParseBody(); if (body != null) { hasError = body["error_message"] != null; } } return hasError; }
This function kind of does the opposite. It assumes an error has always occurred, and then validates and changes its mind if it finds proof that no error occurred. For this to happen, the response it receives from the server must contain a JSON document that does not have an "error_message" key.
Upon re-examining the server-side code, Ivan found that the server simply returns an HTTP status code to indicate payment status, with values of 400 or more indicating error (e.g. 401 = "validation info missing from request", 402 = "no information retrieved from app store", 200 = "all good!"). It doesn't issue any kind of JSON document. Ever. The client code always failed because BodyString was always null.
That's right, the core business-critical microtransaction library used in all of the company's freemium games was incapable of actually handling a microtransaction!
Ivan rewrote the PaymentResponse.HasError() function to look like this:
public bool HasError() { int responseCode = (int)StatusCode; return responseCode >= 400; }
They pushed this fix out to the entire company and all its products, issued rounds of updates to the various mobile app stores, and the company started making money, presumably for the first time ever.
[Advertisement] Application Release Automation - build complex release pipelines all managed from one central dashboard, accessibility for the whole team. Download and learn more today!