How to use Unity IAP with Pley?
A simple example on how to use a Unity IAP setup on Pley
Unity IAP has provided the ability to implement a custom store. Here is a simple implementation example of how to go about it.
Firstly we will need to create a Purchasing Module by inheriting from AbstractPurchasingModule!
using UnityEngine.Purchasing.Extension;
namespace InAppPurchases
{
public class PleyPurchasingModule : AbstractPurchasingModule
{
private static PleyPurchasingModule module;
public override void Configure() => RegisterStore("PleyStore", new PleyStore());
public static PleyPurchasingModule Instance() => module ??= new PleyPurchasingModule();
}
}
Now that we've done that, we'll need to implement the IStore interface.
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Pley;
using UnityEngine;
using UnityEngine.Purchasing;
using UnityEngine.Purchasing.Extension;
namespace InAppPurchases
{
public class PleyStore : IStore
{
private IStoreCallback callback;
private Queue<PaymentsKit.Entitlement> unconsumedEntitlements = new();
//define all the products you want available here from the Game Manager's In-game purchases section
public static readonly Dictionary<string, string> PleyProductIds = new()
{
{"ff27679c-7e4d-11ee-a322-cfcda8049fa7", "1Gold"},
{"08071b50-7e4e-11ee-a322-17e33241f0d5", "2Gold" },
{"1194a732-7e4e-11ee-8129-6f71d48378f7", "5Gold" }
};
#region Store
public void Initialize (IStoreCallback callback)
{
Debug.Log("[PleyStore] - Initializing");
if (callback != null)
this.callback = callback;
else Debug.LogError("[PleyStore] - IStoreCallback is null on Initialize");
}
public void RetrieveProducts (ReadOnlyCollection<ProductDefinition> products)
{
Debug.Log("[PleyStore] - Retrieve products!");
GetProductDescriptions(products);
}
public async void Purchase(ProductDefinition product, string developerPayload)
{
Debug.Log($"[PleyStore] - Purchase {product.storeSpecificId}");
var pleyProductId = "";
foreach (var kvp in PleyProductIds.Where(kvp => kvp.Value == product.id))
{
pleyProductId = kvp.Key;
break;
}
if (string.IsNullOrEmpty(pleyProductId))
{
Debug.LogError("[PleyStore] - Unable to find product info for "+pleyProductId);
return;
}
var (result, paymentData) = await PaymentsKit.RequestPaymentAsync(pleyProductId);
if (result.IsOk())
callback.OnPurchaseSucceeded(product.storeSpecificId, "Pley Purchase Success", paymentData.entitlementId);
else
callback.OnPurchaseFailed(
new PurchaseFailureDescription(
product.storeSpecificId,
PurchaseFailureReason.PaymentDeclined,
result.ToString()
));
}
public void FinishTransaction (ProductDefinition product, string transactionId)
{
Debug.Log($"[PleyStore] - Finished Transaction #{transactionId} : {product}");
}
#endregion
private async void GetProductDescriptions(ReadOnlyCollection<ProductDefinition> products)
{
var (result, productsData) = await PaymentsKit.GetProductsAsync(PleyProductIds.Keys.ToArray());
if (!result.IsOk())
{
Debug.LogError("[PleyStore] - Failed to get Products : " + result);
return;
}
var list = new List<ProductDescription>();
foreach (var productData in productsData)
{
if (productData.result.IsError() || !PleyProductIds.TryGetValue(productData.product.id, out var pleyProductName))
continue;
foreach (var product in products)
{
if (product.id == pleyProductName)
{
list.Add(new ProductDescription(
product.id,
new ProductMetadata(
productData.product.price.ToString(),
productData.product.name,
string.Empty,
productData.product.price.currencyIso4217,
(decimal)productData.product.price.Amount
)));
}
}
}
StartConsumeEntitlements();
callback.OnProductsRetrieved(list);
}
private async void StartConsumeEntitlements()
{
var (result, entitlements) = await PaymentsKit.GetEntitlementsAsync();
if (!result.IsOk())
{
Debug.LogError("[PleyStore] - failed to get entitlements.");
return;
}
if(entitlements.Length <= 0)
{
Debug.Log("[PleyStore] - No unconsumed entitlements pending.");
return;
}
unconsumedEntitlements = new Queue<PaymentsKit.Entitlement>(entitlements);
IAPHandler.Instance.StartCoroutine(ConsumeRoutine());
}
private IEnumerator ConsumeRoutine()
{
while(unconsumedEntitlements.TryDequeue(out var current))
{
yield return new WaitForSeconds(2f); //WaitUntil(() => IAPHandler.Instance.CanPurchase);
Debug.Log($"[PleyStore] - Consuming unconsumed entitlement: {current.productId} : entitlement: {current.entitlementId}");
callback.OnPurchaseSucceeded(current.productId, string.Empty, current.entitlementId);
}
}
}
}
Finally, we need to Initialize the store with the correct PurchasingModule for our desired platforms in your IStoreListener or IDetailedStoreListener
private async void Init()
{
await InitializeUnityServices();
InitializeUnityPurchasing();
}
private async Task InitializeUnityServices()
{
var initTask = UnityServices.InitializeAsync();
await initTask;
if (initTask.IsCompletedSuccessfully)
Debug.Log("Unity Services Initialized.");
else Debug.LogError($"Unity Services Failed to Initialize {initTask.Exception}");
}
private void InitializeUnityPurchasing()
{
Debug.Log("[IAPHandler] - Configuring UnityPurchasing. Adding Products...");
#if UNITY_EDITOR
StandardPurchasingModule.Instance().useFakeStoreAlways = true;
StandardPurchasingModule.Instance().useFakeStoreUIMode = FakeStoreUIMode.DeveloperUser;
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance(AppStore.fake));
#elif UNITY_WEBGL
var builder = ConfigurationBuilder.Instance(PleyPurchasingModule.Instance());
#else
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
#endif
//Add Products
builder
.AddProduct("1Gold", ProductType.Consumable)
.AddProduct("2Gold", ProductType.Consumable)
.AddProduct("5Gold", ProductType.Consumable);
Debug.Log("[IAPHandler] - Initializing UnityPurchasing");
//Initialize UnityPurchasing
UnityPurchasing.Initialize(this, builder);
}
NOTE : Products can be added from the IAP Catalogue as opposed to declared manually via code as in the above example.
*Be sure to Initialize Unity Services before attempting to Initialize Unity Purchasing.
Updated 9 months ago