Introduction

3rd Party Virtual Goods in GameSparks relate specifically to the ability of a player to purchase an item with a given product Id or SKU from a 3rd party store like Apple or Google Play.

The transaction is validated by the backend in order for the Virtual Good to be delivered to the player safely and without interference from the client.

For GameSparks, the flow for delivery of Virtual Goods is as follows:

  1. First we need our player to be authenticated with the platform’s client SDK, for example GooglePlay or Apple.
  2. The player will initiate a transaction with that platform using the client SDK
  3. The SDK will return some form of receipt data. This can be some form of meta-data about the transaction, it can also be a hash or a token of some kind. This is what we need to validate the transaction.
  4. The client sends this data to the appropriate GameSparks request.
  5. Under-the-hood, GameSparks will send this data to some validation API specific to the platform. It will also use some of the Integration settings set through the portal like any appIds or secrets.
  6. The platform will return validation details, along with some unique identifier for the player.
  7. Using that identifier, we can deliver the Virtual Good or Item to the player.

This is the flow we will be trying to replicate in this topic.

In some cases, these platforms use a different feature for Virtual Goods and for IAPs so keep this in mind.

Note - Anywhere we show an example of custom validation with another platform, you may need to first make sure that player has been authenticated with that platform, so check out how to do that in this topic here.

Beamable

Beamable has an out-of-the-box IAP feature which is linked to the UnityIAP service. Processing purchases this way is very simple and can be done with a single line of code. However, it does have quite a bit of setup before you can get to that point.

In this topic we will go through the setup for Beamable but there is also a lot of setup to be done with your IAPs and the store you are using (Apple or Google) and also the Unity IAP service. We won't cover setting these components up but we can assume your IAPs are already working from your GameSparks implementation but if you have not been using the Unity IAP service, there is a guide on that here.

It is important to note that there are a lot of steps to the app-store and Unity IAP setup so there is a lot that you can miss or can go wrong which could lead you to getting errors when trying to test your purchases.

This guide will be the same for both Android and Apple IAPs, as Beamable and Unity IAP service have the same setup for both.

Note - Currently (July 2021) this flow only works for Unity IAP 2.2.2, if you are using a later version of the Unity IAP package you will need to revert to 2.2.2 in order for this to work.

Step 1 - Create an Item

In a previous topic here we discussed how you might replicate a GameSparks Virtual Good using a custom item content object. Check that tutorial out if you need some custom items. In this example we are going to use a regular Beamable item.

Go to the Content Manager and then Create -> Item.

Once you have created your item, remember to publish your changes.

Step 2 - Create SKU

The next thing we need to create is a new SKU. These content objects are going to be used to create a store listing. Even if you don't use a store in your game, you will still need these so your SKU code can be checked against the Beamable manifest when processing the purchase.

You will need to set the productId for your IAP as it appears in your Google-Dev or App-Store connect portal.

Once you have created your SKU, remember to publish your changes.

Step 3 - Create a Listing

Again, we will go to the Content Manager to create a new listing. There are some important parts here that you will need to get correct for your IAP to work.

In the Price section you need to set the Type to “sku” and the Symbol should be exactly the same as the Id of the SKU content you created in the last step.

You can then choose what you want to deliver when this IAP is validated. In this example we are going to deliver the item we created. The item Id needs to be complete in this field. In other words it would be “items.stabbysword” and not just “stabbysword”.

You can see some other options there for setting a purchase limit or requirements to purchase this item. We won't cover them here but you should take note in case they are useful to your game.

Once you have created your listing, remember to publish your changes.

Step 4 - Setup a Store

The next thing we need to do is set up a store and add the listing for our item. Again this is done through the Content Manager and there isn't too much to add here, you just need to add the listing to the store list.

Once you have created your store, remember to publish your changes.

There is one more step needed in order to set up the store. You will need to add this to the config of your Beamable project. To do this you will need to go to the Beamble Toolbox window and then click on “Config”.

We then need to add the store to the project config.

BeamableIAP API Now that you have everything set up you should be able to start with a test-purchase in Unity to validate your IAP is working.

As Beamable is using the Unity IAP service there isn't much code needed to process a purchase, we only need the Id of the listing content and the Id of the SDK content.

/// <summary>
/// Initiates and processes a purchase based on the listing and sku code given
/// </summary>
/// <param name="listingCode"></param>
/// <param name="skuCode"></param>
private async void StartPurchase(string listingId, string skuId)
{
   Debug.Log($"Listing-ID: {listingId}, Sku-ID: {skuId}...");
   Debug.Log("starting purchase...");
   // get the beamable IAP instance //
   var iap = await beamableAPI.BeamableIAP;
   CompletedTransaction transDetails = await iap.StartPurchase("listings."+listingId, "skus."+skuId);
   Debug.Log("TID: "+transDetails.Txid.ToString());
   Debug.Log("Recpt: "+transDetails.Receipt.ToString());
}

You will also need to include the following namespaces.

using Beamable;
using Beamable.Api.Payments;
using Beamable.Common.Api.Auth;
using UnityEngine.Purchasing;

Known Issues

If you don't have Unity IAP enabled you may get errors relating to some of the beamable purchasing namespaces not being found. This is due to the UNITY_PURCHASING script definition symbol not being set on your project when you import the Unity IAP package. This is rare but does sometimes happen. To solve this you can try re-import the plugin or you can add the symbol to your project settings directly.

Testing Purchases

If you have UnityIAP set up correctly you should see some details printed out in the console when you start the scene from within the Unity editor.

You can now test your purchase validation code. However, since you will be making test-purchases you will need to make sure you are working from the dev or staging environment. Test-purchases will not work from the prod environment. You can make this switch from the Beamable Toolbox window.

Now, if you process the purchase you should get a popup which would allow you to test the flow.

You will be able to see your transactionId returned if the process was successful.

Now we can check if this item actually was delivered by looking for that player’s account through the portal.

Note - Since we are testing through Unity, these purchases will not go through the Apple or Google store. You will need to build and deploy your app through those stores in order to test the full purchase flow. Bear in mind that there are a number of steps to set up Unity IAP and the items on your stores correctly to get these purchases working in through the stores.

AccelByte

While AccelByte does have other IAP options available that overlap with GameSparks (you can check those out here), they do not have functionality specifically for IOS and Google purchases.

However, as mentioned in the section on Cloud-Code here, there is always the option to build your own backend that can integrate with their Golang or JS SDKs. This would allow you to rebuild the GameSparks receipt validation routes for Apple and Google and validate purchases that way. Another option would be to engage with AccelByte directly and have them build a custom microservice for your payment processing requirements.

Nakama

Nakama does provide support for Google and Apple IAPs however there is some configuration needed to get these purchases working. Check out the guide on how to change configuration settings in the Cloud-Code tutorial here

Store Configuration Settings

As with social authentication, the first thing we need to do is add the configuration settings Nakama needs to perform validation.

Google

For Google purchases you will need to add two settings: iap.google.client_email and iap.google.private_key there may be some additional setup required through the Google Developer Console in order for you to get these settings. Nakama has a guide here on how to set this up.

Apple

For Apple purchases you need to add the iap.apple.shared_password setting. Nakama also has a guide on where to get this setting here.

Once you have this done you should be able to see these settings in the Developer Console.

Unity Example

We won't be covering how to set up IAPs in Unity as we are assuming this is something you already have configured. Something to note however is that these examples are using UnityIAP and therefore get the receipt object from the Unity Product class. This object has a number of fields but the one we need for Nakama to work with Google and Apple IAPs is the “Product” field.

We will need to get that field out of the object and pass it into the Nakama receipt validation methods.

Google

/// <summary>
/// Validates a receipt string from Google and delivers the IAP to tne Nakama player
/// </summary>
/// <param name="session"></param>
/// <param name="receipt"></param>
private async void NakamaGoogleIAPPurchaseValidation(ISession session, string receipt)
{
   Debug.Log("Validating Purchase Receipt...");
   // we need to get the "Payload" object out of the receipt string //
   Dictionary<string, string> receiptJSON = Nakama.TinyJson.JsonParser.FromJson<Dictionary<string, string>>(receipt);
   string payload = receiptJSON["Payload"];
   IApiValidatePurchaseResponse response = await nakamaClient.ValidatePurchaseGoogleAsync(session, payload);
   foreach (var validatedPurchase in response.ValidatedPurchases)
   {
       Debug.Log("Validated purchase: " + validatedPurchase);
       transactionValidTxt.text = "Purchase Valid...";
   }
}

Apple

/// <summary>
/// Validates a receipt string from Apple and delivers the IAP to tne Nakama player
/// </summary>
/// <param name="session"></param>
/// <param name="receipt"></param>
private async void NakamaAppleIAPPurchaseValidation(ISession session, string receipt)
{
   Debug.Log("Validating Purchase Receipt...");
   // we need to get the "Payload" object out of the receipt string //
   Dictionary<string, string> receiptJSON = Nakama.TinyJson.JsonParser.FromJson<Dictionary<string, string>>(receipt);
   string payload = receiptJSON["Payload"];
   IApiValidatePurchaseResponse response = await nakamaClient.ValidatePurchaseAppleAsync(session, payload);
   foreach (var validatedPurchase in response.ValidatedPurchases)
   {
       Debug.Log("Validated purchase: " + validatedPurchase);
       transactionValidTxt.text = "Purchase Valid...";
   }
}

We can't test this code in Unity because the app needs to be deployed on a device. Remember that the app won't be able to communicate with your local instance so in order to test this you will need to deploy your server somewhere.

Once you have tested on your device you can confirm purchases are working by going to the Account page of the Developer Console. You should see these IAPs under the Purchases tab.

Delivering Virtual Goods

At the moment we have IAP purchases working but they are not delivering any Virtual Goods. The next step is to link the Virtual Goods server code we created in the Virtual Goods topic to these IAP purchases. Nakama already has hooks for Google and Apple purchases so this is going to be easy for us.

First we will need to add the productId for these items to your stored Virtual Goods objects. You can edit these directly in the Nakama Developer Console for now, but it would be more efficient to add them when transitioning your Virtual Goods automatically as we showed in the previous topic on Virtual Goods.

Next we need to set up some hooks so that when an IAP is processed, we can grant the player some Virtual Goods based on the Virtual Goods APIs that we created in the previous tutorial.

We are going to need two functions: One which can find the right Virtual Good based on the productId of the item purchased, and the other will validate the purchase details and grant the item to the player.

The first one is simple. It will get your Virtual Goods from the storage engine and look for matching productIds.

/**
* Returns the VG details from the storage engine based on the productId
* @param productId {string}
* @param store {ValidatedPurchaseStore} - enum [0 = Apple, 1 = Google]
* @param nk
*/
function getVGFromProductId(productId: string, store: nkruntime.ValidatedPurchaseStore, nk: nkruntime.Nakama): any | null{
   let vgCursor: nkruntime.StorageObjectList = nk.storageList("00000000-0000-0000-0000-000000000000", "GSData_VirtualGood");
   if(vgCursor.objects && vgCursor.objects.length){
       let returnValue: any | null = null;
       vgCursor.objects.forEach(vgDoc => {
           switch(+store){
               case 0:
                   if(String(vgDoc.value.iosAppStoreProductId) === productId){
                       returnValue = vgDoc.value;
                       break;
                   }
               case 1:
                   if(String(vgDoc.value.googlePlayProductId) === productId){
                       returnValue = vgDoc.value;
                       break;
                   }
           }
       });
       return returnValue;
   }
   return null;
}

The runtime environment already has an enumerator called ValidatePurchaseStore which we can use to check which store the purchase was made on and therefore which productId field to check in the stored Virtual Good object.

You can see that we are returning the Virtual Good details using the any type, but we are also allowing the function to return null in case we don't find a Virtual Good.

The next function is going to be our hook so it will have a special field, ValidatePurchaseResponse.

/**
* Processes an IAP purchase for the player and delivers their VGs
* @param context
* @param logger
* @param nk
* @param purchaseResp
*/
function onIAPPurchase(context: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, purchaseResp: nkruntime.ValidatePurchaseResponse){
   logger.info("Processing Purchase...")
   // We need to get the productId out of the purchase response //
   if(purchaseResp.validatedPurchases){
       purchaseResp.validatedPurchases.forEach(function(purchaseDetails: nkruntime.ValidatedPurchase) {
           logger.info(`Processing [${purchaseDetails.productId}] on [${purchaseDetails.store?.toString()}], for player [${context.userId}]`);
           if(purchaseDetails.productId && purchaseDetails.store){
               // get the VG details for this productId //
               let vgDetails: any | null = getVGFromProductId(purchaseDetails.productId, purchaseDetails.store, nk);
               if(vgDetails !== null){
                   GSVirtualGoods.addVirtualGood(vgDetails.shortCode, context.userId, logger, nk);
               }
               else{
                   // handle error //
               }
           }
           else{
               // handle error //
           }
       });
    }
 }

Now, in the InitModule function of the main.ts script we can register these hooks.

initializer.registerAfterValidatePurchaseApple(onIAPPurchase);
initializer.registerAfterValidatePurchaseGoogle(onIAPPurchase);

It will be the same function for both Apple and Google purchases because the store enum will let us get the correct Virtual Good for either store.

We won't show an example of how to test here because you will need a device and an IAP already set up for your game. You can confirm Virtual Goods have been delivered by checking the player’s Storage tab in the Accounts section of the Developer Console.