IAP OnComplete does not finish
We have discovered an issue where some of our users are pressing the Oculus home button right after they enter the pin when purchasing our subscription service. This results in the IAP.LaunchCheckoutFlow().OnComplete() never being called. Any additional attempts from the user to purchase and our app gets an immediate user_canceled error. It seems that the purchase does process on the Oculus side, but because our application does not receive the OnComplete callback, we are not made aware about this. What is the resolution to this issue?1.1KViews3likes1CommentPurchase.ReportingId field is empty after a successful payment
The "Purchase.ReportingId" field is blank after a successful payment, but for us this is a problem because we need this field to be able to attach the purchase to our own users, the question is, is this field left blank because we used a test user with an application that is not currently released? If we try this with an App Lab released app, will the contents of the "Purchase.ReportingId" field be available to us? We don't want to release the app with no guarantee that the ReportingId field will be filled, we want to avoid any potential problems340Views0likes0Commentsintegrate in app purchase in Meta SDK
I can't fetch my products in the app, it shows null, I publlished the product and it's status is "Reslease Pending" Here is the script [SerializeField] private TextMeshProUGUI m_priceText; private const string DURABLE_SKU = "box1"; private void Awake() { Oculus.Platform.Core.Initialize(); } void Start() { FetchProductPrices(); } public void FetchProductPrices() { string[] skus = { DURABLE_SKU }; Debug.Log("Products in list" + IAP.GetProductsBySKU(skus)); IAP.GetProductsBySKU(skus).OnComplete(GetProductsBySKUCallback); } void GetProductsBySKUCallback(Message<ProductList> msg) { Debug.Log("message" + msg.Data); foreach (Product p in msg.Data) { Debug.LogFormat("Product: sku:{0} name:{1} price:{2}", p.Sku, p.Name, p.FormattedPrice); if (p.Sku == DURABLE_SKU) { m_priceText.text = p.formattedPrice; } } }392Views0likes0CommentsIAP Implementation for Durable Items
Hi, I want to implement Meta Quest IAP workflow with following steps. 1.User will purchase the IAP durable item 2.Durable item is Asset bundle of a scene as in different levels 3.Once purchase is completed i want to start downloading Assets Bundles attached to that IAP as DLC . 4. While downloading i want to see the download progress 5.Once downloaded it should get stored locally 6.It should load the scene on button click. I have tried to implement this flow here is the code using UnityEngine; using Oculus.Platform; using Oculus.Platform.Models; using System.Collections.Generic; using TMPro; using System; using UnityEngine.SceneManagement; using System.Collections; using System.Linq; using System.IO; public class IAPManager : MonoBehaviour { public TextMeshPro debug, purchaseData; private void Start() { // Initialize the Oculus Platform SDK Core.Initialize(); // Fetch available IAPs FetchIAPs(); // Fetch purchased IAPs FetchPurchasedIAPs(); } private void FetchIAPs() { IAP.GetProductsBySKU(new List<string>() {"arcade_range" }.ToArray()).OnComplete(GetProductsCallback); } private void FetchPurchasedIAPs() { Entitlements.IsUserEntitledToApplication().OnComplete(r => { if (r.IsError) { Debug.Log("Error entitling user to the application: " + r.GetError().Message); } }); IAP.GetViewerPurchases().OnComplete(GetPurchasedItemsCallback); } private void GetProductsCallback(Message<ProductList> message) { if (message.IsError) { Debug.LogError("Failed to fetch IAP products: " + message.GetError().Message); // debug.text += "\n"+"Failed to fetch IAP products: " + message.GetError().Message; return; } Debug.Log("Available IAPs:"); foreach (var product in message.Data) { Debug.Log($"Product: {product.Name}, Price: {product.FormattedPrice}"); // debug.text += "\n" + $"Product: {product.Name}, Price: {product.FormattedPrice}"; } } private void GetPurchasedItemsCallback(Message<PurchaseList> message) { if (message.IsError) { Debug.LogError("Failed to fetch purchased IAPs: " + message.GetError().Message); purchaseData.text += "\n" + "Failed to fetch purchased IAPs: " + message.GetError().Message; return; } else { OnPurchasesRetrieved(); // foreach (var purchase in message.Data) //{ // Debug.Log($"Purchase: {purchase.Sku}, GrantTime: {purchase.GrantTime}"); // purchaseData.text += "\n" + $"Purchase: {purchase.Sku},Purchase: {purchase.ID}, GrantTime: {purchase.GrantTime}"; // if (String.Compare(purchase.Sku, "arcade_range") == 0) // { // purchaseData.text += "\n Downloading" + $"Purchase: {purchase.Sku}"; // AssetFile.DownloadByName(purchase.Sku).OnComplete(getDownloadedItemsCallBack); // } // } } //Debug.Log("Purchased IAPs:"); //foreach (var purchase in message.Data) //{ // Debug.Log($"Purchase: {purchase.Sku}, GrantTime: {purchase.GrantTime}"); // purchaseData.text += "\n" + $"Purchase: {purchase.Sku},Purchase: {purchase.ID}, GrantTime: {purchase.GrantTime}"; // // AssetFile.Download(ulong.Parse(purchase.ID)).OnComplete(getDownloadedItemsCallBack); //} } void OnPurchasesRetrieved() { Debug.Log("Finished checking purchased DLC. Retrieving downloadable assets"); GetOculusAssetFileList(); } public void GetOculusAssetFileList() { AssetFile.GetList().OnComplete((Message<AssetDetailsList> msg) => { if (msg.IsError) { Debug.LogError("Error retrieving DLC information: " + msg.GetError().Message); purchaseData.text += "\n" + "Error retrieving DLC information"; return; } List<ulong> assetIds = new List<ulong>(); Dictionary<string, string> newSkuToFilepathMap = new Dictionary<string, string>(); FetchPurchasedProducts((purchasedSkus) => { foreach (var assetDetail in msg.Data) { Debug.Log($"AssetID: {assetDetail.AssetId}, Filepath: {assetDetail.Filepath}"); purchaseData.text += "\n" + $"AssetID: {assetDetail.AssetId}, Filepath: {assetDetail.AssetId}"; // AssetFile.Download(assetDetail.AssetId).OnComplete(getDownloadedItemsCallBack); purchaseData.text += "\n" + Path.GetFileNameWithoutExtension(assetDetail.Filepath); StartCoroutine(LoadSceneFromBundle(assetDetail.Filepath, "ProShooterVR_ArcadeMode")); //if (!string.IsNullOrEmpty(assetDetail.Filepath)) //{ // var filepathParts = assetDetail.Filepath.Split('/'); // var filename = filepathParts.get // var sku = Path.GetFileNameWithoutExtension(filename); // if (thumbnailSKUs.Contains(sku)) // { // newSkuToFilepathMap[sku] = assetDetail.Filepath; // bool isDownloaded = File.Exists(assetDetail.Filepath); // skuDownloadStatus[sku] = isDownloaded; // assetIds.Add(assetDetail.AssetId); // skuToAssetIdMap[sku] = assetDetail.AssetId; // skuPurchaseStatus[sku] = purchasedSkus.Contains(sku); // } //} } //skuToFilepathMap = newSkuToFilepathMap; //dlcAssetIds = assetIds.ToArray(); }); }); } IEnumerator LoadSceneFromBundle(string bundlePath, string sceneName) { if (!System.IO.File.Exists(bundlePath)) { Debug.LogError("Bundle file does not exist: " + bundlePath); purchaseData.text += "\n" + "Bundle file does not exist:"; yield break; } // Load the asset bundle asynchronously AssetBundleCreateRequest bundleLoadRequest = AssetBundle.LoadFromFileAsync(bundlePath); yield return bundleLoadRequest; AssetBundle bundle = bundleLoadRequest.assetBundle; if (bundle == null) { Debug.LogError("Failed to load AssetBundle!"); purchaseData.text += "\n" + "Failed to load AssetBundle!"; yield break; } // Check if the scene is in the bundle if (!bundle.Contains(sceneName)) { Debug.LogError("Scene not found in the bundle!"); purchaseData.text += "\n" + "Scene not found in the bundle!"; yield break; } // Load the scene asynchronously string scenePath = bundle.GetAllScenePaths()[0]; // Assuming the scene is the first one listed in the asset bundle AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(scenePath); // Wait until the scene has loaded while (!asyncLoad.isDone) { yield return null; } // Optionally unload the asset bundle to free memory // bundle.Unload(false); } private void FetchPurchasedProducts(Action<List<string>> onComplete) { IAP.GetViewerPurchases().OnComplete((Message<PurchaseList> msg) => { if (msg.IsError) { Debug.LogError("Error checking purchased DLC: " + msg.GetError().Message); onComplete(new List<string>()); return; } List<string> purchasedSkus = msg.Data.Select(p => p.Sku).ToList(); onComplete(purchasedSkus); }); } /// <summary> /// ////////////////////////////////////////////////////////////// /// </summary> /// <param name="message"></param> private void getDownloadedItemsCallBack(Message<AssetFileDownloadResult> message) { if(message.IsError) { purchaseData.text += "\n" + "Failed to download purchased IAPs: " + message.GetError().Message; return; } else { purchaseData.text += "\n" + "Downloading waait"; purchaseData.text += "\n" + message.GetAppDownloadProgressResult().StatusCode.ToString(); var asset = message.GetAssetDetails(); LoadSceneFromAssetBundle(asset.Filepath); } } /// <summary> /// This Method will call the purchase flow API of Meta /// </summary> public void BuyItem() { IAP.LaunchCheckoutFlow(sku: "arcade_range").OnComplete(BuyItemCallback); } private void BuyItemCallback(Message<Purchase> message) { if (message.IsError) { return; } else { } Purchase p = message.GetPurchase(); Debug.Log("purchased " + p.ID); ulong assetFileId = ulong.Parse(p.ID); // Replace with your actual asset file ID DownloadAssetById(assetFileId); // purchaseData.text = string.Empty; // FetchPurchasedIAPs(); } private void DownloadAssetById(ulong assetFileId) { AssetFile.DownloadById(assetFileId).OnComplete(AssetDownloadComplete); } private void AssetDownloadComplete(Message<AssetFileDownloadResult> message) { if (message.IsError) { Debug.LogError("Failed to download asset file: " + message.GetError().Message); return; } else { purchaseData.text += "\n" + "Downloading waait"; var asset = message.GetAssetDetails(); LoadSceneFromAssetBundle(asset.Filepath); } } private void LoadSceneFromAssetBundle(string assetBundleUri) { StartCoroutine(LoadYourAsyncScene(assetBundleUri)); } IEnumerator LoadYourAsyncScene(string assetBundleUri) { // Load the AssetBundle file from cache if it exists with the specified version number AssetBundle myLoadedAssetBundle = AssetBundle.LoadFromFile(assetBundleUri); if (myLoadedAssetBundle == null) { Debug.Log("Failed to load AssetBundle!"); purchaseData.text += "\n" + "Failed to load AssetBundle"; yield break; } string[] scenePaths = myLoadedAssetBundle.GetAllScenePaths(); string sceneName = System.IO.Path.GetFileNameWithoutExtension(scenePaths[0]); Debug.Log("Loading scene " + sceneName); purchaseData.text += "\n" + "Loading"; // Load the scene asynchronously in the background AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName); while (!asyncLoad.isDone) { yield return null; } Debug.Log("Scene loaded"); // Optionally, unload the asset bundle to free up memory after the scene has loaded myLoadedAssetBundle.Unload(false); } } //void OnPurchasesRetrieved() //{ // Debug.Log("Finished checking purchased DLC. Retrieving downloadable assets"); // GetOculusAssetFileList(); //} //public void GetOculusAssetFileList() //{ // AssetFile.GetList().OnComplete((Message<AssetDetailsList> msg) => // { // if (msg.IsError) // { // Debug.LogError("Error retrieving DLC information: " + msg.GetError().Message); // loadStatus.text = "Error retrieving DLC information"; // return; // } // List<ulong> assetIds = new List<ulong>(); // Dictionary<string, string> newSkuToFilepathMap = new Dictionary<string, string>(); // FetchPurchasedProducts((purchasedSkus) => // { // foreach (var assetDetail in msg.Data) // { // Debug.Log($"AssetID: {assetDetail.AssetId}, Filepath: {assetDetail.Filepath}"); // if (!string.IsNullOrEmpty(assetDetail.Filepath)) // { // var filepathParts = assetDetail.Filepath.Split('/'); // var filename = filepathParts.LastOrDefault(); // var sku = Path.GetFileNameWithoutExtension(filename); // if (thumbnailSKUs.Contains(sku)) // { // newSkuToFilepathMap[sku] = assetDetail.Filepath; // bool isDownloaded = File.Exists(assetDetail.Filepath); // skuDownloadStatus[sku] = isDownloaded; // assetIds.Add(assetDetail.AssetId); // skuToAssetIdMap[sku] = assetDetail.AssetId; // skuPurchaseStatus[sku] = purchasedSkus.Contains(sku); // } // } // } // skuToFilepathMap = newSkuToFilepathMap; // dlcAssetIds = assetIds.ToArray(); // }); // }); //} //private void FetchPurchasedProducts(Action<List<string>> onComplete) //{ // IAP.GetViewerPurchases().OnComplete((Message<PurchaseList> msg) => // { // if (msg.IsError) // { // Debug.LogError("Error checking purchased DLC: " + msg.GetError().Message); // onComplete(new List<string>()); // return; // } // List<string> purchasedSkus = msg.Data.Select(p => p.Sku).ToList(); // onComplete(purchasedSkus); // }); //} Please Help Us implement this.663Views0likes0CommentsError: "Horizon isn't installed, or you're running an incompatible of home/horizon"
I've almost completed what I think is a pretty solid script to allow the user to download DLC (assetbundle) for my app using Oculus Platform. All goes well from entitlement checks, to retrieving prices, to checking already purchased items, to the purchase itself. Starting the actual download however fails and returns this error: Either the service couldn't be connected to, Horizon isn't installed, or you're running an incompatible of home/horizon What's strange is that I can't find anything about this error. Literally zero results on google. I'm testing the app and it's IAP with test users, which should be fine This error occurs in the build (I have a worldspace canvas showing debug info). Because there's such little information on this error, I think it's because the error comes from the Quest itself and not from any script of my project (and therefore hard to spot). It occurs both when I've installed the apk directly to my Quest 2 and after downloading it from App Lab GPT suggests it has likely something to do with a server or software problem on Oculus' side. However, I just don't buy it since it's a fresh new script and therefore more at risk of being buggy than Oculus. You can't just simply install Horizon as an app, so I installed Horizon Workrooms. Of course didn't solve anything.Solved1.4KViews0likes2CommentsIAP Showing up even when Status is Unavailable
Hi, I have a couple of IAPs setup in the developer dashboard, but when I set the IAP status to Unavailable it still gets fetched when i call IAP.GetProductsBySKU. AssetFile.GetList() gives me DownloadStatus: available and IapStatus gives me entitled/not entitled dependning on if it's purchased or not. But I cannot find anything that will respect the Status set in the IAP in the developer dashboard. I could of course set the metadata, but since this is this beautiful status in the develop dashboard, I'd rather use that. Anyone know anything about it?587Views0likes1CommentHow to associate SKU with assetID
Hi, I'm looking in to how to handle the association for SKU to assetId. The AssetFile.GetList() objects supports assetId, but not sku and IAP.GetProductsBySKU() supports sku, but not assetId. One way would be to only send in a single SKU as argument to IAP.GetProductsBySKU and then handle and the How can I pair them in safe way?759Views0likes1CommentOculus IAP GetProductBySKU doesn't call OnComplete
Hi, I am trying to implementent IAP in a Quest 2 game but it just won't work. All I do is calling the initialize Methode and then try to get the purchasable items that are already set up (but not in store yet). The GetProductsBySKU Method gets called but it never triggers the OnComplete Method.896Views0likes1CommentAPI to determine if user has purchased game
Is there an API method to determine if the Meta user has already purchased the game? We do have API calls to determine if the user has purchased Addons (IAP), but I have not found any method regarding the game itself. We are changing our game price to free, and want users that have previously purchased it to have special benefits ingame.2.3KViews1like3CommentsAttempting In app Purchases failing and saying "Item not available"
As shown in the title whenever I launch the checkout flow for in-app purchases it says "Item not available" even though I have been approved for IAP, and have put in the correct SKU any idea why this is happening?542Views0likes0Comments