Advanced use cases

If you followed perviost articles you already know that The Ultimate Mobile InApps API allows you to use native InApp services through a simplified cross-platform interface. But there is no obligation to use it, you can also use native plugins API directly for each platfrom.

But what if you just want one small feature from the native platfrom? It would be very inconvenient if you will have to reimplement your whole InApps implementation for that platfrom. So in this article, I will share some tips and tricks on how you still can get a lot of native only features but still using the cross platfrom implementation.

Products Info

Once you connected to the payment service you can now have product info provided by payment service as opose to defined in yout settings. Generally speaking, you may set the wrong value while setting products inside the plugin UI, but once you connected to the payment service your product gets overridden by the info provided from that service.   So it's a good practice to fill up your store UI after the successful connection to the payment service. 

So let's say you've connected to the payment services:

using SA.CrossPlatform.InApp;
 ...
 
 UM_InAppService.Client.Connect((connectionResult) => {
            if(connectionResult.IsSucceeded) {
        //You are now connected to the payment service.
        //Also all product's info are updated at this point from according to server values.
    } else {
        //Connection failed.
        Debug.Log("Connection failed: " + connectionResult.Error.FullMessage);
    }
});

You can now print product data thar comes from the server side:

using SA.CrossPlatform.InApp;
...

foreach(UM_iProduct product in UM_InAppService.Client.Products) {
    Debug.Log("product.Id: " + product.Id);
    Debug.Log("product.Price: " + product.Price);
    Debug.Log("product.PriceInMicros: " + product.PriceInMicros);
    Debug.Log("product.Title: " + product.Title);
    Debug.Log("product.Description: " + product.Description);
    Debug.Log("product.PriceCurrencyCode: " + product.PriceCurrencyCode);
    Debug.Log("product.Icon: " + product.Icon);
    Debug.Log("product.Type: " + product.Type);
    Debug.Log("product.IsActive: " + product.IsActive);
}

Is Active

product.IsActive - Is a very important field. 

product.IsActive = true

  • Product is recognized by the payment service.
  • This product can be purchased by a user.
  • Product info is synced with payment service remote server.

product.IsActive = true

  • Product is not recognized by the payment service.
  • You can't use this product to start a Purchase flow
  • Product info only contains information that was filled using plugin settings UI.

Get Native Product Info

But UM_iProduct only provides limited information of the product, so how can you get a platfrom spesific product model? Well, see the code smaple below:

using SA.iOS.StoreKit;
using SA.CrossPlatform.InApp;
using SA.Android.Vending.BillingClient;
...

foreach(var product in UM_InAppService.Client.Products)
{
    switch (Application.platform)
    {
        case RuntimePlatform.Android:
            AN_SkuDetails androidProduct = (AN_SkuDetails) product.NativeProductTemplate;
            break;
            
        case RuntimePlatform.IPhonePlayer:
        case RuntimePlatform.tvOS:
            ISN_SKProduct iosProduct = (ISN_SKProduct) product.NativeProductTemplate;
            break;
    }
}

If you not nessearaly have a use case for the native product model, you may still want to print it into the log or sent it's infor somwhere. For this pripsose you don't even need to cast. See the code sample below:

using SA.CrossPlatform.InApp;
...

foreach(var product in UM_InAppService.Client.Products)
{
    Debug.Log("Native Product model: " + JsonUtility.ToJson(product.NativeProductTemplate));
}

Get Native Transaction Info

You can do the same trick for transaction as well. Pretty similar to the product.  You can also convert it to JSON string with JsonUtility.ToJson

using SA.iOS.StoreKit;
using SA.CrossPlatform.InApp;
using SA.Android.Vending.BillingClient;
...

public void OnTransactionUpdated(UM_iTransaction transaction) {
	switch (transaction.State) {
	    case UM_TransactionState.Restored:
	    case UM_TransactionState.Purchased:
	        ProcessCompletedTransaction(transaction);
	        break;
	    case UM_TransactionState.Deferred:
	    case UM_TransactionState.Failed:
	    	// We may not have transaction model here. 
	    	// If you still want to try get it make sure it's not null.
	        break;
    }
}

private void ProcessCompletedTransaction(UM_iTransaction transaction)
{
    switch(Application.platform) {
        case RuntimePlatform.Android:
            var an_purchase = (AN_Purchase) transaction.NativeTemplate;
            break;
        case RuntimePlatform.IPhonePlayer:
            var iosTransaction = (ISN_iSKPaymentTransaction) transaction.NativeTemplate;
            break;
    }
}

Platform-specific features

So now we know how to get all the platfrom spesific data, but what about some functionality that is not avvaliable from the cross-platfrom API? There is also a way how you can acsses it.

Check if a product was already purchased.

There is no such feature inside the Cross-Platfrom InApps API provided by a plugin. The reason is that different platfrom has a very different way of working around that problem.

There are three main reasons why you would want that. Let's talk about each one as well as about solving it on different platforms.

#1 Once the product is purchased, you want to have a permanent effect for a user. 

Example: If the user purchased the "remove ads" product, you want to make sure there are no banners or any other ads appearing during gameplay. 

Solution: Save product state using PlayerPrefs (or anything else you like Cloud, Remote Database, etc.) when Transaction Updated with Purchased or Restored state. See the Purchase Flow guide for more details.

#2 You want purchases also to take effect on oather user deviese, and wasn't necessarily used for making a purchase. If you don't have a way to authenticate users and get saved data from your remote database, this can be a problem.

#3 The user has returned the product and got refunded. In this case, you want to cancel the effect of the refunded product.

In other words, you want to get a list of the products owns by a user. 

Android

This can be done using Billing Client API directly. See the code sample below:

using SA.CrossPlatform.InApp;
using SA.Android.Vending.BillingClient;
...

var billingClient = UM_InAppService.AndroidUtilities.ActiveBillingClient;
var purchasesResult = billingClient.QueryPurchases(AN_BillingClient.SkuType.inapp);
if (purchasesResult.IsSucceeded)
{
    foreach (var purchase in purchasesResult.Purchases)
    {
        Debug.Log("Product with id: " + purchase.Sku + " is owned by a user.");
    }
}

This is, by the way, a valid way of accessing internal billing client used by a plugin.

iOS

On iOS, there is a less elegant solution. For issue #2, you can have the "Restore Purchases" button in your store UI. And start the Restore Completed Transactions flow.

For problem #3, you can get the app store receipt:

https://unionassets.com/ios-native-pro/receipt-validation-630#validating-receipts-with-the-app-store

And then get purchases info using Apple server API (yes you would need own dedicated server fro this)

https://forums.developer.apple.com/thread/46737

Define products list via C#

Normally you need to define your products using the native plugin's editor for iOS and Android respectively. Some developers prefer to define the product manually before connection to the service. See the example below:

using SA.CrossPlatform.InApp;
...

var myProductTemplates = new List<UM_ProductTemplate>
{
    new UM_ProductTemplate {Id = "my_products1", Type = UM_ProductType.Consumable},
    new UM_ProductTemplate {Id = "my_products2", Type = UM_ProductType.NonConsumable},
    new UM_ProductTemplate {Id = "my_products3", Type = UM_ProductType.Subscription}
};

UM_InAppService.Client.Connect(myProductTemplates, connectionResult => 
{
    //Do Something
});

Conclusion

Even tho when you are using cross-platfrom API, you can find ourselves in a somewhat limited environment that may push you actually to implement billing logic using native platfrom API, especially if you have some specific needs.  With this guide, I hope you will be able to get all the platfrom particular data and use platfrom specific features you need while still enjoy simplified cross-platform API.
If you have any questions or feel like something is missing, do not hesitate to get in touch.
https://stansassets.com/