In-App Purchases for Instant Games

Instant Games In-App Purchases (IAP) provide a way for developers to leverage micro-transactions in their Instant Games on Facebook.com. Developers will be able to immediately test IAP in their games. Once a game has passed through In App Purchase review, players will be able to make purchases on Android and Facebook.com.

In App Purchases are supported on web and Android 6 and above - not on the native Facebook iOS app.

Note: Do not show any payments functionality on the native Facebook iOS app. Avoid any reference to their availability on other platforms too.

Getting Started

The first step of enabling In App Purchases is to configure the products that you would like to make available to players and configure payout information. This can be done on the In App Purchase tab under the Instant Games product.

Select or create a company that will the designated recipient of payouts from Instant Games in-app purchases. You can find more information here.

Integrating on Web

Best Practices

The following is the expected flow per game session:

  • Check whether payments is available for the session; see section below for more details.
  • As a best practice, to ensure proper handling of consumables in the event of a crash during the IAP flow, call getPurchasesAsync() to obtain a list of unconsumed purchases. Then call consumePurchase(<purchase_token>) on any consumables, awarding the user the corresponding in-game item or virtual currency.
  • Call getCatalogAsync() to obtain the list of products to display to the user. Use this data to dynamically populate the store UI.
  • If the user interacts with the UI to make a purchase, using the productID of the item from the catalog, call purchaseAsync(<purchase_config>).
  • At this point, you can optionally send the signedRequest to your servers to fully verify the purchase.
  • If the item is a consumable, consume the purchase via consumePurchase(<purchase_token>), passing in the purchaseToken, and award the user the corresponding in-game item or virtual currency.

Detecting whether payments is available

Payments in Instant Games are only supported on Facebook.com (not Messenger.com) and on Android once you have passed review. Your code should ensure that payments are available on the player's device before showing any payments functionality.

As with other Instant Games APIs, use getSupportedAPIs and look for payments.purchaseAsync.

If available, subscribe to payments.onReady with a callback as follows.

FBInstant.payments.onReady(function () {
  console.log('Payments Ready!');
});

If getSupportedAPIs does not contain payments.purchaseAsync, then payments is not supported on the device.

If the callback set in payments.onReady is never called, payments is also not supported for this session and no payment related functionality should be used.

Rendering the store

Use the getCatalogAsync method to get the list of available products. Make sure to use the proper local currency (priceCurrencyCode) and price (price) for each product when displaying your store.

FBInstant.payments.getCatalogAsync().then(function (catalog) {
  console.log(catalog); // [{productID: '12345', ...}, ...]
});

Handling a purchase

When a player shows intent to purchase an item, you can trigger the purchase confirmation dialog using purchaseAsync.

FBInstant.payments.purchaseAsync({
  productID: '12345',
  developerPayload: 'foobar',
}).then(function (purchase) {
  console.log(purchase);
  // {productID: '12345', purchaseToken: '54321', developerPayload: 'foobar', ...}
});

As within the signed request, the purchasePrice and paymentActionType fields will appear on web and Facebook for Android v323 or later.

Verifying a Purchase

It is possible to verify that a payment has occurred on a backend system by parsing the signedRequest value that is returned with the payment object.

The signedRequest consists of two parts separated by the . character. The first part is a Base64 encoded SHA256 hash of the payment information, while the second part is the same payment information encoded directly in Base64.

Never embed your app secret into your game. The signedRequest should only be validated in server-side code.

You can validate that the payment information is authentic by using the following code samples:

Javascript sample:

import hmacSHA256 from "crypto-js/hmac-sha256";
import Base64 from "crypto-js/enc-base64";
import utf8 from "crypto-js/enc-utf8";
import JSONbig from "json-bigint";

const signedRequest = "<SIGNED_REQUEST>";
const firstPart = signedRequest.split(".")[0].replace(/-/g, "+").replace(/_/g, "/");
const secondPart = signedRequest.split(".")[1];

const signature = Base64.parse(firstPart).toString();
const dataHash = hmacSHA256(secondPart, "<APP_SECRET>").toString();

const isValid = signature === dataHash;
const json = Base64.parse(secondPart).toString(utf8);
/*
  NOTE: The purchase_token field will exceed the bounds of Number.MAX_SAFE_INTEGER, 
  so JSON.parse(...) is unsafe. This will be changed soon, but until then,
  please avoid using. Alternatively, pass the string directly from the 
  purchase API response instead.
*/
const data = JSONbig({ storeAsString: true }).parse(json);

console.log(isValid); // this will be true if the payment is verified as coming from the game
console.log(data); // a JSON object as follows:

/*
{
  algorithm: "HMAC-SHA256"
  app_id: 772899436149321
  is_consumed: false
  issued_at: 1628530124
  payment_action_type: "charge"
  payment_id: 2373285299469015
  product_id: "sample_product"
  purchase_price: Object // {"amount":"0.01", "currency":"USD"},
  purchase_time: 1628171348
  purchase_token: 10102867843382867
}
*/

/*
Starting 8/25/21:
{
  algorithm: "HMAC-SHA256"
  app_id: "772899436149321"
  is_consumed: false
  issued_at: 1628530124
  payment_action_type: "charge"
  payment_id: "2373285299469015"
  product_id: "sample_product"
  purchase_price: Object // {"amount":"0.01", "currency":"USD"},
  purchase_time: 1628171348
  purchase_token: "10102867843382867"
}
*/

Java sample:

import java.util.Base64;
import java.util.Arrays;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

...

String signedRequest = "<SIGNED_REQUEST>";

String[] parts = signedRequest.split("\\.",2);
String firstPart = parts[0].trim().replaceAll("-","+").replaceAll("_","/");
String secondPart = parts[1];

Base64.Decoder decoder = Base64.getDecoder();
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");

// Handle first part of signed request
byte[] signature = decoder.decode(firstPart);

// Handle second part of signed request
SecretKeySpec secret_key = new SecretKeySpec("<APP_SECRET>".getBytes(), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] datahash = sha256_HMAC.doFinal(secondPart.getBytes());

// SignedRequest is valid if both matches
boolean isValid = Arrays.equals(signature, datahash);

Getting a player's purchased items

Use getPurchasesAsync to get the player's unconsumed purchases.

FBInstant.payments.getPurchasesAsync().then(function (purchases) {
  console.log(purchases); // [{productID: '12345', ...}, ...]
});

Consuming a purchased item

When a player decides to use a purchased item, call consumePurchaseAsync and award the effect of the item. Once called, the item will no longer be returned by getPurchasesAsync. Until this is done, the player will be unable to purchase the same item in the future.

If the purchase of consumable products are not handled properly via this consumption mechanism, subsequent purchaseAsync(<purchase_config>) calls for the same item will reject with a INVALID_PARAM error with the message "Product not purchaseable".

If the item is a non-consumable product, don't call consumePurchaseAsync. This purchase will always be listed in getPurchasesAsync.

FBInstant.payments.consumePurchaseAsync('54321').then(function () {
  // Purchase successfully consumed!
  // Game should now provision the product to the player
}).catch(function(error) {
  // Handle error
});

Testing Transactions

Users added to the Testers section of the Set Up pane on the In App Purchase tab will not be charged for items in your game. This can be used to test purchase functionality without performing real transactions.

For Facebook Pay, please use one of the following test credit cards.

NumberBrandDateCVCZipcodeBilling Country

5555555555554444

Master Card

Any future date

Any 3 Digits

Any valid

United States

5454543112674402

Master Card

Any future date

Any 3 Digits

Any valid

United States

4444448713788708

Visa

Any future date

Any 3 Digits

Any valid

United States

4242424242424242

Visa

Any future date

Any 3 Digits

Any valid

United States

6011111111111117

Discover

Any future date

Any 3 Digits

Any valid

United States

When users are added as an IAP tester, they CANNOT use a real credit card and MUST use one of the above.

The FB Pay checkout dialog should look like the following:

Sending for Review

Before In App Purchases will work across all platforms, there are several checks that must be made.

The app must pass a functional In App Purchase review. Instant Games can be submitted for In App Purchases on the Review tab.

On the In App Purchase tab, statuses are shown to help you determine which of these activities need to be completed.

Analytics

In the In App Purchase tab, please follow the link to directly see the purchase metrics in FBS Insights.

Purchases will also be automatically logged in Events Manager.

Payout

Our primary goal is to build IAP in a way so that our developer partners can sustain and grow: for purchases made on web (Facebook.com), the revenue share model is 70% for developers - as it has been historically for games on the Facebook platform.

On mobile, Instant Games follow in-app billing terms from each platform. For purchases made in games on Google Play, developers will retain the full amount after the standard mobile platform revenue share. This means developers take home $0.70 of every dollar earned with $0.30 fee to Google.

Payout should be received in 30 day windows.