Coding Guidelines

Setup In-app products

After you done with 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 product's id's with PaymentManager AddProductId or AddProduc methods. See the 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 thru PaymentManager clas. See 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 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 was 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 complete (i. e. OnStoreKitInitComplete  action fired)

Getting In-app products info

After initialization is complete, 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 recommendation 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 purchase is waiting for 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 probably would want to finish transaction manually after making sure that user 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 complete action received (with state Purchased) - please note that your player already saw StoreKit default purchase confirmation popup, and he was 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 working on steps 3 and 4 - for example you can show simple preloader.

You should also know:

1. If you're 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 able to buy this product second time (event if that is a consumable product) before you will finish a previous transaction associated with that product. If the user will try to make a 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 use example can be found in a PaymentManagerExample.cs script.