The creator-centric platform for building live games in Unity

Easily add social, commerce, and content management features to your live game with low-code, drag-and-drop prefabs fully integrated with your Unity workflow.

Adding Custom Purchaser

Overview

Here is everything needed to use the Custom PurchaserPurchaser - The mechanism which connects the Beamable StoreFlow feature prefab to the Beamable PayService for in-app purchasing in the "Beamable SDK for Unity". Or watch this video.

šŸ“˜

Related Guides

A common use case for the feature is covered in the guides. Adding custom PurchaserPurchaser - The mechanism which connects the Beamable StoreFlow feature prefab to the Beamable PayService for in-app purchasing is part of a larger process.

User Experience

When setup properly, the user experience in the game project will be as follows.

Steps

Follow these steps to get started.

This step assumes no dependency on Unity's UnityIAP solution. There is significant custom code required for this solution.

A benefit here is flexible development.

StepsDetail
1. Create Purchaser C# Classā€¢ Ex. CustomPurchaser.cs
2. Create Purchaser Resolver C# Classā€¢ Ex. CustomPurchaserResolver.cs
3. Register the purchaser with BeamableProvide
ServiceManager.Provide(new CustomPurchaserResolver());

Complete
var customPurchaser = ServiceManager.Resolve<IBeamablePurchaser>();
await customPurchaser.Initialize();
beamableAPI.BeamableIAP.CompleteSuccess(customPurchaser);
4. Verify Successā€¢ Setup a store. See Using Store Flow for more info
ā€¢ Create a scene with the StoreFlow feature prefab
ā€¢ Play the Scene
ā€¢ "Buy" the listing through the StoreFlow

Note: The verification process depends on the specifics of the game makers custom purchaser solution

API

Here are API highlights for IBeamablePurchaser.

The custom purchaser class must implement this interface.

Method NameDetail
Initializeā€¢ Setup the chosen IAP implementation
GetLocalizedPriceā€¢ Fetches the localized string from the provider
StartPurchaseā€¢ Start a purchase through the chosen IAP implementation.

Here are API highlights for beamableAPI.PaymentService.

The custom purchaser class must reference these methods and call each if/when appropriate.

Method NameDetail
GetSKUsā€¢ Get commerce SKUs from platform
BeginPurchaseā€¢ Begin purchase request
CompletePurchaseā€¢ Call after the provider has charged the player. Used to verify the legitimacy of the purchase receipt and complete the item fulfillment process
CancelPurchaseā€¢ Cancel purchase request
FailPurchaseā€¢ Fail purchase request

Inspiration

The UnityBeamablePurchaser represents a fully-functional Beamable solution for connecting with Unity's native IAP system. Checkout the source-code. Its included in the "Beamable SDK for Unity".

The custom purchaser class must not reference this class. It's only for reference.

Example

Here are examples which cover common programming needs.

šŸ“˜

Beamable SDK Examples

ā€¢ This and all examples are available for download at GitHub.com/Beamable_SDK_Examples

The CustomPurchaserExample showcases how to use the CustomPurchaser.

using Beamable.Api.Payments;
using Beamable.Service;
using UnityEngine;

namespace Beamable.Examples.Prefabs.StoreFlow.MyCustomPurchaser
{
    /// <summary>
    /// Implementation of custom Beamable purchasing.
    ///
    /// 1. See the partially-functional <see cref="CustomPurchaser"/> as a template.
    ///   Copy it and complete your custom implementation.
    ///
    /// 2. See the fully-functional <see cref="UnityBeamablePurchaser"/> (Search
    ///   in Beamable SDK) for inspiration.
    /// 
    /// </summary>
    public class CustomPurchasingSystemExample : MonoBehaviour
    {
        //  Unity Methods  --------------------------------
        protected void Start()
        {
            Debug.Log("Start()");
            SetupBeamable();
        }
        
        //  Methods  --------------------------------------
        private async void SetupBeamable()
        {
            // Order is important here...
            
            // Order #1 - Call for Instance
            var beamableAPI = await Beamable.API.Instance;
            Debug.Log($"beamableAPI.User.id = {beamableAPI.User.id}");
            
            // Order #2 - Register the purchaser
            ServiceManager.Provide(new CustomPurchaserResolver());
            var customPurchaser = ServiceManager.Resolve<IBeamablePurchaser>();
            await customPurchaser.Initialize();
            beamableAPI.BeamableIAP.CompleteSuccess(customPurchaser);
                
            // Order #3 - Now use the StoreFlow feature prefab at runtime
            // and 'Buy' an item. Watch the Unity Console Window for logging
            // to verify success
        }
    }
}

The CustomPurchaser is provided as inspiration to game makers. It is not to be used directly. It is partially-functional template to be copied and completed.

using System;
using System.Collections.Generic;
using System.Linq;
using Beamable.Api;
using Beamable.Api.Payments;
using Beamable.Common;
using Beamable.Coroutines;
using Beamable.Service;
using UnityEngine;

namespace Beamable.Examples.Prefabs.StoreFlow.MyCustomPurchaser
{
   /// <summary>
   /// Implementation of custom Beamable purchasing.
   ///
   /// 1. See this partially-functional <see cref="CustomPurchaser"/> as a template.
   ///   Copy it and complete your custom implementation.
   ///
   /// 2. See the fully-functional <see cref="UnityBeamablePurchaser"/> (Search
   ///   in Beamable SDK) for inspiration.
   /// 
   /// </summary>
   public class CustomPurchaser : BasePurchaser, IBeamablePurchaser
   {
      //  Fields  ---------------------------------------
      private List<CustomStoreProduct> _customStoreProducts = new List<CustomStoreProduct>();

      //  Methods  --------------------------------------
      #region "IBeamablePurchaser"
      
      /// <summary>
      /// Begin initialization of Beamable purchasing.
      /// </summary>
      public override Promise<Unit> Initialize()
      {
         base.Initialize();
         
         Debug.Log($"CustomPurchaser.Initialize()");
            
         _paymentService.GetSKUs().Then(rsp =>
         {
            _customStoreProducts.Clear();
            foreach (SKU sku in rsp.skus.definitions)
            {
               Dictionary<string, string> idDictionary = new Dictionary<string, string>
               {
                  {sku.productIds.itunes, AppleAppStore},
                  {sku.productIds.googleplay, GooglePlay}
               };
              
               _customStoreProducts.Add(
                     new CustomStoreProduct(sku, ProductType.Consumable, idDictionary));
            }

            // Todo Your Implementation: Determine initialization Success/Failure
            // Success
            OnInitialized();
            // or Failure
            // OnInitializeFailed("tbd error");
            
         });

         return _initializePromise;
      }
      
      
      /// <summary>
      /// Get the localized price string for a given SKU.
      /// </summary>
      public string GetLocalizedPrice(string skuSymbol)
      {
         Debug.Log($"CustomPurchaser.GetLocalizedPrice() skuSymbol = {skuSymbol}");
         
         var product = _customStoreProducts.FirstOrDefault(
               p => p.SKU.name == skuSymbol);
         
         // Todo Your Implementation: Get the localized price. Not this.
         return product?.SKU.realPrice.ToString() ?? "???";
      }

      
      /// <summary>
      /// Start a purchase for the given listing using the given SKU.
      /// </summary>
      /// <param name="listingSymbol">Store listing that should be purchased.</param>
      /// <param name="skuSymbol">Platform specific SKU of the item being purchased.</param>
      /// <returns>Promise containing completed transaction.</returns>
      public Promise<CompletedTransaction> StartPurchase(string listingSymbol, string skuSymbol)
      {
         Debug.Log($"CustomPurchaser.StartPurchase() skuSymbol = {skuSymbol}");
         
         _transactionId = 0;
         var completedTransactionPromise = new Promise<CompletedTransaction>();
         
         _onBeginPurchaseSuccess = completedTransactionPromise.CompleteSuccess;
         _onBeginPurchaseError = completedTransactionPromise.CompleteError;

         if (_onBeginPurchaseCancelled == null)
         {
            _onBeginPurchaseCancelled = () =>
            { 
               completedTransactionPromise.CompleteError( 
                  new ErrorCode(400, GameSystem.GAME_CLIENT, "Purchase Cancelled"));
            };
         }

         _paymentService.BeginPurchase(listingSymbol).Then(rsp =>
         {
            _transactionId = rsp.txid;
            PaymentService_OnBeginPurchase(skuSymbol, _transactionId);
            
         }).Error(err =>
         {
            Debug.LogError($"CustomPurchaser.OnBeginPurchaseError() error = {err}");
            _onBeginPurchaseError?.Invoke(err as ErrorCode);
         });

         return completedTransactionPromise;
      }
      #endregion

      
      /// <summary>
      /// Process a completed purchase by fulfilling it.
      /// </summary>
      /// <param name="product"></param>
      /// <returns></returns>
      public ProcessingResult ProcessPurchase(CustomStoreProduct product)
      {
         Debug.Log($"CustomPurchaser.ProcessPurchase() product = {product}");
         
         string rawReceipt;
         if (product.HasReceipt)
         {
            var receipt = JsonUtility.FromJson<CustomPurchaseReceipt>(product.Receipt);
            rawReceipt = receipt.Payload;
            
            Debug.Log($"receipt = {receipt}");
         }
         else
         {
            rawReceipt = product.Receipt;
         }

         var transaction = new CompletedTransaction(
            _transactionId,
            rawReceipt,
            GetLocalizedPrice(product.SKU.name),
            "tbdIsoCurrencyCode"
         );
         
         FulfillTransaction(transaction, product);

         return ProcessingResult.Pending;
      }

      
      /// <summary>
      /// Handle a purchase failure event from IAP.
      /// </summary>
      /// <param name="product">The product whose purchase was attempted</param>
      /// <param name="failureReason">Information about why the purchase failed</param>
      public void OnPurchaseFailed(CustomStoreProduct product, string failureReason)
      {
         Debug.LogError($"CustomPurchaser.OnPurchaseFailed() product = {product.SKU.name}");
         
         // Todo Your Implementation: Setup custom reasons...
         if (failureReason == "tbdSomeReason")
         {
            // Maybe cancel...
            _paymentService.CancelPurchase(_transactionId);
            _onBeginPurchaseCancelled?.Invoke();
         }
         else
         {
            // Todo Your Implementation: Setup custom reasons...
            int reasonInt = 1;
            string reason = product.SKU.name+ ":" + failureReason;
            
            // Maybe fail...
            _paymentService.FailPurchase(_transactionId, reason);

            var errorCode = new ErrorCode(reasonInt, GameSystem.GAME_CLIENT, reason);
            _onBeginPurchaseError?.Invoke(errorCode);
         }

         ClearCallbacks();
      }

      
      /// <summary>
      /// Initiate transaction restoration if needed.
      /// </summary>
      public void RestorePurchases()
      {
         Debug.Log($"CustomPurchaser.RestorePurchases() platform = {Application.platform}");
         
         if (Application.platform == RuntimePlatform.IPhonePlayer ||
             Application.platform == RuntimePlatform.OSXPlayer)
         {
            // Todo Your Implementation: For iOS...
         }
         else
         {
            // Todo Your Implementation: For other platform(s)...
         }
      }
      
      
      /// <summary>
      /// Fulfill a completed transaction by completing the purchase in the
      /// payments service and informing IAP completion.
      /// </summary>
      /// <param name="transaction"></param>
      /// <param name="product"></param>
      protected override void FulfillTransaction(CompletedTransaction transaction, 
         CustomStoreProduct product)
      {
         base.FulfillTransaction(transaction, product);
         
         Debug.Log($"CustomPurchaser.FulfillTransaction() SKUSymbol = {transaction.SKUSymbol}");
         
         _paymentService.CompletePurchase(transaction).Then(_ =>
         {
            PaymentService_OnCompletePurchase(product);
            _onBeginPurchaseSuccess?.Invoke(transaction);
            ClearCallbacks();
         }).Error(ex =>
         {
            Debug.LogError($"error = {ex.Message}");
            var errorCode = ex as ErrorCode;

            if (errorCode == null)
            {
               return;
            }

            var isRetryable = IsRetryable(errorCode);
            if (isRetryable)
            {
               ServiceManager.Resolve<CoroutineService>().StartCoroutine(
                  RetryTransaction(transaction, product));
            }
            else
            {
               PaymentService_OnCompletePurchase(product);
               _onBeginPurchaseError?.Invoke(errorCode);
               ClearCallbacks();
            }
         });
      }
      
      
      //  Event Handlers  -------------------------------
      private void OnInitialized()
      {
         Debug.Log($"CustomPurchaser.OnInitialized() count = {_customStoreProducts.Count}");
         RestorePurchases();
         _initializePromise.CompleteSuccess(PromiseBase.Unit);
         
         // Todo Your Implementation: Handle success
      }

      
      private void OnInitializeFailed(string error)
      {
         Debug.LogError($"CustomPurchaser.OnInitializeFailed() error = {error}");
         _initializePromise.CompleteError(new Exception(error));
         
         // Todo Your Implementation: Handle failure
      }
      
      
      private void PaymentService_OnBeginPurchase(string skuSymbol, long transactionId)
      {
         Debug.Log($"OnBeginPurchase() skuSymbol = {skuSymbol}, " +
                   $"transactionId = {transactionId}");
         
         // Todo Your Implementation: Implement purchase
      }
      
      
      private void PaymentService_OnCompletePurchase(CustomStoreProduct product)
      {
         Debug.Log($"OnCompletePurchase() product = {product.SKU.name}"); 
         
         // Todo Your Implementation: Complete the purchase
      }
   }
}

Updated 3 days ago


Adding Custom Purchaser


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.