In-app Purchase Validation

The spectrum of monetisation models and tools is extremely varied. From ad-supported, microtransactions, freemium, one-off purchases, and everything in between. A key tool in many of these solutions is the In-App Purchase, which enables single purchases for unlocks, in-game consumables, subscriptions for premium access, and more.

There are a number of readily available attacks against the most common in-app purchase implementations.

These are usually focused around:

  • Feeding the client fake purchase responses which indicate success.
  • Replaying a valid purchase response multiple times.
  • Sharing a purchase response with another client, so multiple players can receive the reward from a single purchase.
  • ...and more, with new vulnerabilities emerging occasionally.

For in-app purchases, a trusted source of truth is required. Nakama checks and tracks purchases and purchase history, solving a significant set of possible vulnerabilities and pain points.

In-App Purchase Validation is available for Apple and Google purchases, regardless of platform. Both single product and subscription purchases are supported.

Fake Purchases

Nakama directly connects to Apple and Google services to check the validity of all incoming payments. This verification is completely outside the client's code, and cannot be intercepted and tampered with.

Every transaction is verified, every time, and invalid ones are rejected.

Replay Attacks

All transactions are logged, preventing multiple submissions of the same purchase token or receipt.

Receipt Sharing

Successful transactions are bound to the account that submits them. Different users cannot submit the same transaction, even a valid one, in an attempt to receive the associated reward.

Product Mismatches

The transaction is checked to ensure the correct reward is tied to each purchase. This prevents attacks that attempt to use a valid (cheap) purchase to unlock a different (expensive) reward.

Subscription Expiry

Nakama checks subscriptions to see if they've expired, and rejects the transaction as needed.

Purchase Cancellation

Valid receipts that link to cancelled purchases are flagged and rejected.

Single Source of Truth

While Nakama maintains an internal record of all transactions, the remote payment provider is always given priority. Valid purchases that have been checked, logged, then subsequently cancelled, will be rejected appropriately.

Apple

Nakama supports validating purchases made for products and subscription in iOS.

Apple purchase receipts are sent to Apple for validation. As suggested by Apple, both Production and Sandbox servers are used to validate receipts depending on the priority setup in the Nakama configuration.

Setup

To validate receipts against the App Store, Nakama requires your app's shared secret. You can setup a shared secret in iTunes Connect.

Apple iTunes Connect

Make a record of your shared secret:

Apple iTunes Connect Shared Secret

You'll need to set the value of purchase.apple.password to the value of the Shared Secret above. For more info, take a look at the configuration page.

If your app is in production, you'll need to set the value of purchase.apple.production to true to give priority Apple's Production servers.

Validate Purchase

Nakama only supports validating iOS 7+ receipts. In addition, Nakama only validates the first item in the receipt as Apple receipts can contain more than one in-app purchase item.

var productId = "com.yourcompany.product";
var receiptData = "...some-base64-encoded-data...";

var message = NPurchaseValidateMessage.Apple(productId, receiptData);
client.Send(message, (INPurchaseRecord record) =>
{
  if (!record.Success) {
    Debug.Log("Purchase was not validation. Reason: {0}.", record.Message);
  } else {
    if (record.SeenBefore) {
      // This is useful for recovering previous purchases
      Debug.Log("This is a valid purchase but the purchase item was redeemed once before.");
    } else {
      Debug.Log("New purchase was validated");
    }
  }
}, (INError e) => {
  Debug.LogErrorFormat("Error: code '{0}' with '{1}'.", err.Code, err.Message);
});
Param Type Description
receipt_data string Base-64 encoded receipt data returned by the purchase operation itself.
product_id string The product, item, or subscription package ID the purchase relates to.

Google

Nakama supports validating purchases made for products and subscription on Android.

Setup

To validate receipts against the Play Store, Nakama requires your app's package name, as well as a service file. You can setup a service account and download the service file on Google Play Developer Console.

Firstly, you'll need to setup a Service Account in the Google API Console.

Create Service Account

Once a service account is created, you'll need to create a key:

Create Key

Download the key as a JSON file. You'll need to put this file somewhere that Nakama server can access.

Create Key

Once the key is created, navigate back to Google Play Developer Console and navigate to Settings > API Access.

The service account you created in the previous steps should be listed above. You'll need to grant access to the service account to access the API:

Create API Access

Make sure that you give the service account access to Visibility, View Financial Data, and Manage Orders. These permissions are required for Nakama to validate receipts against Google Play.

Grand Access

Navigate to Users & Permissions to check that the service account is setup correctly.

List users with access

Lastly, you'll need to update Nakama's configuration with the following information:

  • purchase.google.package_name: Package name for your Android app, as you've listed in Google Play.

  • purchase.google.service_key_file: Path of the JSON file you download in previous steps. This file contains authentication information that allows Nakama to communicate with Google Play on your behalf. Make sure that the file is kept safe and is only accessible by Nakama and other authorized parties.

Validate Purchase

var productId = "com.yourcompany.product";
var purchaseType = "product";
var purchaseToken = "some-token-from-google";

var message = NPurchaseValidateMessage.Google(productId, purchaseType, purchaseToken);
client.Send(message, (INPurchaseRecord record) =>
{
  if (!record.Success) {
    Debug.Log("Purchase was not validation. Reason: {0}.", record.Message);
  } else {
    if (record.SeenBefore) {
      // This is useful for recovering previous purchases
      Debug.Log("This is a valid purchase but the purchase item was redeemed once before.");
    } else {
      Debug.Log("New purchase was validated");
    }
  }
}, (INError e) => {
  Debug.LogErrorFormat("Error: code '{0}' with '{1}'.", err.Code, err.Message);
});
Param Type Description
product_type string Whether the purchase is for a product or a subscription
purchase_token string The token returned in the purchase operation response, acts as a transaction identifier.
product_id string The identifier of the product or subscription being purchased.

Interpreting Responses

Responses contain the following information:

  • success - Whether or not the transaction is valid and all the information matches.
  • seen_before - If this is a new transaction or if Nakama has a log of it.
  • purchase_provider_reachable - Indicates whether or not Nakama was able to reach the remote purchase service.
  • message - A string indicating why the purchase verification failed, if appropriate.
  • data - The complete response Nakama received from the remote service.

Note

If purchase_provider_reachable is false it indicates that Nakama was unable to query the remote purchase service. In this situation the client should use its discretion to decide if the purchase should be accepted, and must queue up the verification request for a later retry.

Each response contains all the information needed to take the appropriate action. Below is a quick reference for interpreting the key fields:

seen_before = true seen_before = false
success = true Valid, but Nakama has an existing record of it. Valid and new.
success = false Rejected, check message field for reason. Rejected, check message field for reason.

Recovering Purchases

When users change devices, it's common to offer an option (or fully automated process) to re-apply the benefits of any previous purchases to their new client installation.

Clients should always refer to the platform purchase provider for a list of purchases, then verify each one with Nakama. In this case clients should accommodate responses where the seen_before indicator is true and act accordingly.