Coding Guidelines

Setup In-app products

After you have finished products setup in iTunes connect, you need to initialise those products in your application. This can be done in 2 ways.

1. Setup product with IOS Native editor Settings.

Just go to a Window -> Stan's Assets -> IOS Native -> Edit Settings. And add all your game products as showed on a screenshot bellow:

2. Set up Products with scripting 

You can simply register an id of a product with PaymentManager AddProductId or AddProduc methods. See an example bellow:

PaymentManager.Instance.AddProductId(SMALL_PACK);
PaymentManager.Instance.AddProductId(NC_PACK);

The registered products are represented as a Product model, and products info can be accessed through PaymentManager class. See the code example bellow:

using SA.IOSNative.StoreKit;
...

foreach(var product in PaymentManager.Instance.Products)  {
	Debug.Log (product.Id + " / " + product.DisplayName);
}

string myProductId = "my_product_id";
Product p = PaymentManager.Instance.GetProductById (myProductId);
Debug.Log (p.DisplayName);

Initialize StoreKit

Once you have defined app products, you can initialize / load Store Kit API using LoadStore method. OnStoreKitInitComplete will be fired with the initialisation result.

using SA.IOSNative.StoreKit;
...

PaymentManager.OnStoreKitInitComplete += OnStoreKitInitComplete;
PaymentManager.Instance.LoadStore();

void OnStoreKitInitComplete(SA.Common.Models.Result result) {
	if(result.IsSucceeded) {
		int avaliableProductsCount = 0;
		foreach(Product tpl in PaymentManager.Instance.Products) {
			if(tpl.IsAvailable) {
				avaliableProductsCount++;
			}
		}
		Debug.Log("Init Succeeded Available products count: " + avaliableProductsCount);
	} else {
		Debug.Log("Init Failed:" + result.Error.Code + " / " + result.Error.Message);
	}
}

As soon as PaymentManager is initialized successfully, we can start making purchases. You can always find out if PaymentManager has been initialized by checking:

bool IsStoreLoaded = PaymentManager.Instance.IsStoreLoaded;

Use the following flag to find out if we're still waiting for the initialization response:

bool IsWaitingLoadResult = PaymentManager.Instance.IsWaitingLoadResult;

Or if a current device is eligible for purchases at all:

bool IsInAppPurchasesEnabled = PaymentManager.Instance.IsInAppPurchasesEnabled;

Note: Do not use any of IOSInAppPurchaseManager class API before initialization is completed (i. e. OnStoreKitInitComplete  action fired)

Getting In-app products info

After initialization is completed, you can get information about your products. All product info is stored within the PaymentManager.instance.Products array and represented as Product object:

So, for example, here is how you can get localised price of a specific product:

using SA.IOSNative.StoreKit;

string localizedPrice = PaymentManager.Instance.GetProductById("YOUT_PRODUCT_ID").localizedPrice;

Or printing data for all products:

using SA.IOSNative.StoreKit;
...

foreach(Product tpl in PaymentManager.Instance.Products) {
	Debug.Log("id" + tpl.Id);
	Debug.Log("title" + tpl.Title);
	Debug.Log("description" + tpl.Description);
	Debug.Log("price" + tpl.Price);
	Debug.Log("localizedPrice" + tpl.LocalizedPrice);
	Debug.Log("currencySymbol" + tpl.CurrencySymbol);
	Debug.Log("currencyCode" + tpl.CurrencyCode);
	Debug.Log("-------------");
}

Note: Even if In-App purchasing is locked on the device, initialization can be successful and you will receive product data. However, purchasing will fail with a corresponding error.

In-App purchasing can be disabled under the device Settings: General -> Restrictions

 

If you want to check In-App device Settings State, you can do this as shown on the code snippet bellow.

using SA.IOSNative.StoreKit;
...

bool IsInAppPurchasesEnabled =  PaymentManager.Instance.IsInAppPurchasesEnabled;

Purchasing

Now we're ready to make the purchase. All you need to do is to call BuyProduct function with the product id you want to buy and make sure you are subscribed to a Transaction Complete event. See the code snippet bellow:

using SA.IOSNative.StoreKit;
...

PaymentManager.OnTransactionComplete += OnTransactionComplete;
PaymentManager.Instance.BuyProduct("your.product.id1.here");

void OnTransactionComplete (PurchaseResult result) {

        PaymentManager.OnTransactionComplete -= OnTransactionComplete;

		ISN_Logger.Log("OnTransactionComplete: " + result.ProductIdentifier);
		ISN_Logger.Log("OnTransactionComplete: state: " + result.State);

        switch(result.State) {
        case PurchaseState.Purchased:
        case PurchaseState.Restored:
            //Our product been successfully purchased or restored
            //So we need to provide content to our user 
            //depends on productIdentifier
            UnlockProducts(result.ProductIdentifier);
            break;
        case PurchaseState.Deferred:
            //iOS 8 introduces Ask to Buy, which lets 
            //parents approve any purchases initiated by children
            //You should update your UI to reflect this 
            //deferred state, and expect another Transaction 
            //Complete  to be called again with a new transaction state 
            //reflecting the parent's decision or after the 
            //transaction times out. Avoid blocking your UI 
            //or gameplay while waiting for the transaction to be updated.
            break;
        case PurchaseState.Failed:
            //Our purchase flow is failed.
            //We can unlock interface and report user that the purchase is failed. 
			ISN_Logger.Log("Transaction failed with error, code: " + result.Error.Code);
			ISN_Logger.Log("Transaction failed with error, description: " + result.Error.Message);
            break;
        }

    if(result.State == PurchaseState.Failed) {
    	IOSNativePopUpManager.showMessage("Transaction Failed", "Error code: " + result.Error.Code 
    + "\n" + "Error description:" + result.Error.Message);
    } else {
    	IOSNativePopUpManager.showMessage("Store Kit Response", "product " + 
    result.ProductIdentifier + " state: " + result.State.ToString());
    }
}

The transaction response is represented as a PurchaseResult object:

If the Transaction is PurchaseState.Failed , we can use the error property to find out the exact reason it failed:

ISN_Logger.Log("Transaction failed with error, code: " + result.Error.Code);
ISN_Logger.Log("Transaction failed with error, description: " + result.Error.Message);

Possible error codes are listed in the TransactionErrorCode enum.

Note: So far I haven't found any specific recommendations from Apple what to do when you receive a Deferred purchase state. Here's Apple's recommendation:

You should update your UI to reflect this deferred state, and expect another Transaction Complete  to be called again with a new transaction state reflecting the parent's decision or after the transaction times out. Avoid blocking your UI or gameplay while waiting for the transaction to be updated.

In other words:

  • Keep listening for the transaction complete event, you will get fail/success depending on a parent's decision or request timeout.
  • Make sure you are not blocking UI or gameplay while awaiting the parents response.
  • Update your UI to inform the user that a purchase is waiting for an approval.

Manual Transactions Handling

By default, plugin will automatically finish the transaction after firing event with the transaction state.  But if you work on a big project, you would probably want to finish transaction manually after making sure that a user has got what he has paid for. In this case you need to switch Transaction Handling Mode  from Auto to Manual using plugin editor setting under the billing tab.

Now transaction will not be marked as finished until you call FinishTransaction method:

PaymentManager.Instance.FinishTransaction (productIdentifier);

So, the common usage scenario with the manual transaction confirmation is:

  1. Player has bought an item.
  2. Transaction completed action is received (with state Purchased) - please, note that your player has already seen StoreKit default purchase confirmation popup, and he has been already charged.
  3. Connect to your server to validate the receipt and give players their items.
  4. Manually finish the transaction on the device.

Best option is to lock user interface before you are working on steps 3 and 4 - for example, you can show a simple preloader.

You should also know:

1. If your app crashed while you was working on items 3 and 4. With the next session you will get Purchase Success right after you've initialised the Store Kit API. That's why it's important to subscribe for purchase event before you do the initialisation.

2. User will not be able to buy this product for the second time (even if that is a consumable product) before you finish a previous transaction associated with that product. If the user tries to make the second purchase, he will get a similar notification:

He will not be charged for a second purchase effort, but you will get the very same Purchase Successful event.

3. If you have Non-Consumable products in your game, you also need to implement Purchase Restoring.

A complete example of using can be found in a PaymentManagerExample.cs script.