Разработка

Интеграция дополнений

Обновлено: 1 мая 2026 г.
В этом разделе описана интеграция дополнений в ваше приложение.

Требования

Чтобы вы могли интегрировать дополнения в свое приложение, должны выполняться некоторые требования. В следующих разделах описано, как выполнить предварительные условия.
Чтобы использовать Monetization API, необходимо загрузить на Панель разработчика хотя бы одну версию APK. Помимо настройки приложения, необходимо загрузить действительный пакет APK, чтобы система могла подтвердить идентификационные данные вашего приложения и корректно связать его с Monetization API.

Настройка товаров для покупки

Прежде чем приступить к интеграции дополнений в приложение, вам необходимо определить, какие товары вы будете продавать. Инструкции по настройке дополнений см. в статье Настройка дополнений.

Поддержка движка

Unity предлагает функцию упаковки под названием AssetBundles, совместимую с общими объектами на платформе Meta Horizon. Ознакомьтесь со следующими статьями:

Указание необходимости подключения к Интернету

Для дополнений, связанных со скачиваемым контентом, укажите, что приложению требуется подключение к Интернету.
  1. Выберите свое приложение на Панели разработчика.
    Choose your app in the Developer Dashboard
  2. В области навигации слева выберите Распространение > Отправка приложений.
    Developer Dashboard menu options: Distribution and App submission
  3. На странице Отправка приложений выберите последнюю версию своего приложения.
    The app submissions page in the Developer Dashboard
  4. Перейдите на вкладку Метаданные приложения.
    The App Metadata tab in the Developer Dashboard
  5. Перейдите на вкладку Характеристики.
    The Specs tab in the Developer Dashboard
  6. Прокрутите страницу вниз и найдите раскрывающееся меню Интернет-соединение. В этом меню выберите Требуется подключение к Интернету.
    The Internet Connection dropdown menu in the Developer Dashboard

Включение покупок в приложении

Далее вы узнаете, как включить покупки в приложении. Для этого требуется доступ к ID пользователя игрока.
  1. Выберите свое приложение на Панели разработчика.
    Choose your app in the Developer Dashboard
  2. В левой области навигации выберите Требования > Проверка использования данных.
    Requirements and Data Use Checkup in the Developer Dashboard
  3. В разделе Самосертификация возрастной группы нажмите Начать.
    Age group certification window in the Developer Dashboard
  4. В открывшемся окне выберите возрастную группу, которая лучше всего соответствует вашему приложению, затем нажмите Продолжить.
    Age group certification window in the Developer Dashboard
  5. В разделе Запрос доступа к функциям платформы нажмите кнопку Добавить рядом с пунктом ID пользователя. Откроется новое окно.
    Data Use Checkup window in the Developer Dashboard
  6. В окне ID пользователя:
    1. Выберите Использование дополнений: загружаемый контент и покупки в приложении (ПВП) в раскрывающемся меню Использование.
    2. Введите подробное описание того, как вы будете использовать ID пользователя.
    3. (Необязательно) Приложите скриншоты, которые наглядно демонстрируют сценарии использования.
    4. Поставьте флажок, чтобы принять условия и правила Meta.
    5. Нажмите Добавить в запрос.
    User ID details window in the Developer Dashboard
  7. Вернитесь в раздел Запрос доступа к функциям платформы и нажмите кнопку Добавить рядом с пунктом Покупки в приложении и (или) скачиваемый контент. Откроется новое окно.
  8. В окне Покупки в приложении:
    1. Выберите Использование дополнений: загружаемый контент и покупки в приложении (ПВП) в раскрывающемся меню Использование.
    2. Введите подробное описание того, как вы будете использовать покупки в приложении.
    3. (Необязательно) Приложите скриншоты, которые наглядно демонстрируют сценарии использования.
    4. Поставьте флажок, чтобы принять условия и правила Meta, включая, помимо прочего, Политику использования данных для разработчиков.
    5. Нажмите Добавить в запрос.
    In-App Purchases details window in the Developer Dashboard
  9. В окне Проверка использования данных нажмите Отправить запросы (2).
    Submit Requests button in the Data Use Checkup window in the Developer Dashboard
  10. После отправки запросов откроется новое окно. Ответьте на все вопросы в разделе Обработка данных и нажмите Далее.
    Data handling section in the Data Use Checkup window in the Developer Dashboard
  11. На странице Проверка запросов убедитесь, что ID пользователя и Покупки в приложении и/или скачиваемый контент указаны в пункте Добавленные в разделе Ваши запросы. Нажмите Далее.
    Review requests in the Data Use Checkup window in the Developer Dashboard
  12. На странице Отправка нажмите Отправить на проверку.
    Submit button in the Data Use Checkup window in the Developer Dashboard

Интеграция с дополнениями

В этом разделе описывается, как предлагать созданные вами объекты в качестве покупок в приложении.
Из клиентского приложения можно вызывать следующие методы SDK. Подробнее обо всех функциях см. в статье Справочный контент для Platform SDK.

Retrieve a list of available items and prices by SKU

To retrieve a list of add-on items that are available to the user to purchase by SKU, use the following method. The SKUs must have a description to be retrieved by this method.
This method also returns any virtual SKUs associated with subscription periods.
Platform.IAP.GetProductsBySKU()
If your app displays a price for any add-on, you should use the localized price returned from this endpoint. Do not hard-code price amounts inside the app.

SKUs for subscriptions

If a subscription tier only has a single subscription period, you can reference that single subscription period as an add-on using the SKU of its tier.
However, to differentiate between multiple subscription periods of the same SKU, we create a virtual SKU for each period by appending the subscription period (WEEKLY, BIWEEKLY, MONTHLY, QUARTERLY, SEMIANNUAL, ANNUAL) to the SKU in this format:
<SKU>:SUBSCRIPTION__<PERIOD>
For example, consider a subscription tier with the SKU MyApp-Subscription that has both monthly and annual subscription periods. You would reference the subscription add-on items by virtual SKU as follows:
  • MyApp-Subscription:SUBSCRIPTION__MONTHLY
  • MyApp-Subscription:SUBSCRIPTION__ANNUAL
The GetProductsBySKU method also returns virtual SKUs, so to prevent errors, we recommend you call that method to obtain the full list of available SKUs instead of hardcoding virtual SKU strings into your product.
Подробнее см. в статье Подписки.

Запуск процесса оформления заказа для артикула

Чтобы начать процесс оформления заказа для приобретения пользователем определенного артикула, используйте следующий метод:
Platform.IAP.LaunchCheckoutFlow()

Получение всех купленных пользователем товаров

Чтобы получить список покупок в приложении, сделанных пользователем, используйте следующий метод. Полученный список содержит все покупки товаров длительного пользования и все покупки расходуемых элементов, которые не были израсходованы.
Platform.IAP.GetViewerPurchases()

Получение кэшированного списка товаров длительного пользования, купленных пользователем

Чтобы получить список дополнительных товаров длительного пользования, которые купил пользователь, используйте следующий метод. Полученный список содержит все покупки нерасходуемых компонентов и заполняется из кэша устройства. Всегда следует сначала использовать GetViewerPurchases, а затем этот метод, если другой вызов не срабатывает.
Platform.IAP.GetViewerPurchasesDurableCache()

Расходование купленного товара

Чтобы израсходовать купленный товар от имени пользователя, который затем в приложении отметит товар как израсходованный, воспользуйтесь следующим методом:
Platform.IAP.ConsumePurchase()

Пример реализации

В приведенном ниже примере Unity демонстрируется полный поток:
  1. Получение информации о товаре ПВП
  2. Отображение этой информации для пользователя
  3. Обработка всех незавершенных покупок
  4. Инициирование потока оформления заказа, когда пользователь указывает, что хочет совершить покупку
Этот пример взят из приложения Ultimate Glove Ball в репозитории GitHub Для большей ясности и читаемости пример был отредактирован.
Подробнее о доступных примерах кода и примерах приложений см. в разделе Примеры кода Unity.

using System;
using System.Collections.Generic;
using Meta.XR.Samples;
using Oculus.Platform;
using Oculus.Platform.Models;
using UnityEngine;

namespace UltimateGloveBall.App
{
    /// <summary>
    /// Manages in-app purchases. It's a wrapper on the Oculus.Platform.IAP functionalities.
    /// This makes it easy to fetch all products and purchases as well as make a purchase.
    /// Referenced from: https://developers.meta.com/horizon/documentation/unity/ps-iap/
    /// </summary>
    [MetaCodeSample("UltimateGloveBall")]
    public class IAPManager
    {
        #region Singleton
        private static IAPManager s_instance;

        public static IAPManager Instance
        {
            get
            {
                s_instance ??= new IAPManager();
                return s_instance;
            }
        }

        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
        public static void DestroyInstance()
        {
            s_instance = null;
        }
        #endregion // Singleton

        /// <summary>
        /// The data we get from the error message json string on purchase
        /// </summary>
        private class PurchaseErrorMessage
        {
            // Since we convert from json the naming must stay the same as the json format, which is all lowercase
            public string category;
            public int code;
            public string message;
        }

        private Dictionary<string, Product> m_products = new();
        private Dictionary<string, Purchase> m_purchases = new();

        private Dictionary<string, List<string>> m_productsByCategory = new();
        private List<string> m_availableSkus = new();
        public IList<string> AvailableSkus => m_availableSkus;

        /// <summary>
        /// Asynchronously fetch all products based on the SKU
        /// </summary>
        public void FetchProducts(string[] skus, string category = null)
        {
            _ = IAP.GetProductsBySKU(skus)?.OnComplete(message =>
            {
                GetProductsBySKUCallback(message, category);
            });
        }

        /// <summary>
        /// Asynchronously fetch all purchases that were made by the user
        /// </summary>
        public void FetchPurchases()
        {
            _ = IAP.GetViewerPurchases()?.OnComplete(GetViewerPurchasesCallback);
        }

        public List<string> GetProductSkusForCategory(string category)
        {
            return m_productsByCategory.TryGetValue(category, out var categorySkus) ? categorySkus : null;
        }

        /// <summary>
        /// Returns a product by SKU, otherwise returns null
        /// </summary>
        public Product GetProduct(string sku)
        {
            if (m_products.TryGetValue(sku, out var product))
            {
                return product;
            }

            Debug.LogError($"[IAPManager] Product {sku} doesn't exist!");
            return null;
        }
        /// <summary>
        /// Returns true if the user has purchased the SKU, false if user has not purchased the SKU or if the SKU doesn't exist
        /// </summary>
        public bool IsPurchased(string sku)
        {
            return m_purchases.TryGetValue(sku, out _);
        }

        /// <summary>
        /// Returns a purchase by SKU, otherwise returns null
        /// </summary>
        public Purchase GetPurchase(string sku)
        {
            return m_purchases.TryGetValue(sku, out var purchase) ? purchase : null;
        }

        /// <summary>
        /// Initiating the checkout flow for a SKU
        /// </summary>
        public void Purchase(string sku, Action<string, bool, string> onPurchaseFlowCompleted)
        {
#if UNITY_EDITOR
            m_purchases[sku] = null; // Keep a reference to the purchase, although purchases cannot be made in Editor
            onPurchaseFlowCompleted?.Invoke(sku, true, null);
#else
            IAP.LaunchCheckoutFlow(sku).OnComplete((Message<Purchase> msg) =>
            {
                if (msg.IsError)
                {
                    var errorMsgString = msg.GetError().Message;
                    Debug.LogError($"[IAPManager] Error while purchasing: {errorMsgString}");
                    var errorData = JsonUtility.FromJson<PurchaseErrorMessage>(errorMsgString);
                    onPurchaseFlowCompleted?.Invoke(sku, false, errorData.message);
                    return;
                }

                var p = msg.GetPurchase();
                Debug.Log("[IAPManager] Purchased " + p.Sku);
                m_purchases[sku] = p;
                onPurchaseFlowCompleted?.Invoke(sku, true, null);
            });
#endif
        }

        /// <summary>
        /// Consume a purchased item on behalf of a user
        /// </summary>
        public void ConsumePurchase(string sku, Action<string, bool> onConsumptionCompleted)
        {
#if UNITY_EDITOR
            m_purchases.Remove(sku);
            onConsumptionCompleted?.Invoke(sku, true);
#else
            _ = IAP.ConsumePurchase(sku).OnComplete(msg =>
            {
                if (msg.IsError)
                {
                    Debug.LogError($"[IAPManager] Error while consuming: {msg.GetError().Message}");
                    onConsumptionCompleted?.Invoke(sku, false);
                    return;
                }

                Debug.Log("[IAPManager] Consumed " + sku);
                m_purchases.Remove(sku);
                onConsumptionCompleted?.Invoke(sku, true);
            });
#endif
        }

        private void GetProductsBySKUCallback(Message<ProductList> msg, string category)
        {
            if (msg.IsError)
            {
                Debug.LogError($"[IAPManager] Failed to fetch products, {msg.GetError().Message}");
                return;
            }

            foreach (var p in msg.GetProductList())
            {
                Debug.LogFormat("[IAPManager] Product: sku:{0} name:{1} price:{2}", p.Sku, p.Name, p.FormattedPrice);
                m_products[p.Sku] = p;
                m_availableSkus.Add(p.Sku);
                if (!string.IsNullOrWhiteSpace(category))
                {
                    if (!m_productsByCategory.TryGetValue(category, out var categorySkus))
                    {
                        categorySkus = new List<string>();
                        m_productsByCategory[category] = categorySkus;
                    }

                    categorySkus.Add(p.Sku);
                }
            }
        }

        private void GetViewerPurchasesCallback(Message<PurchaseList> msg)
        {
            if (msg.IsError)
            {
                Debug.LogError($"[IAPManager] Failed to fetch purchased products, {msg.GetError().Message}");
                return;
            }

            foreach (var p in msg.GetPurchaseList())
            {
                Debug.Log($"[IAPManager] Purchased: sku:{p.Sku} granttime:{p.GrantTime} id:{p.ID}");
                m_purchases[p.Sku] = p;
            }
        }
    }
}

Интеграция со скачиваемым контентом

Следующие методы позволяют интегрировать скачиваемый контент в ваше приложение. Используйте их для извлечения и скачивания файлов объектов, связанных с вашим приложением, а также управления ими.

Получение списка объектов, связанных с приложением

Чтобы получить список всех связанных с приложением объектов при запуске приложения, используйте следующий метод:
Platform.AssetFile.GetList
Этот метод возвращает список со сведениями о доступных объектах. Каждый элемент в массиве имеет следующие свойства:
  • assetFileName — имя файла;
  • assetFileID — идентификатор объекта;
  • IapStatus — имеет значение free, entitled или not-entitled;
  • downloadStatus имеет одно из следующих значений:
    • installed — означает, что пользователь установил файл;
    • available — означает, что пользователь может скачать файл;
    • in-progress — означает, что файл скачивается или устанавливается для этого пользователя.

Начало скачивания

Если файл доступен для скачивания, т. е. имеет статус "свободный" или "доступный", а download_status = available, вы можете начать скачивание. Для этого вызовите следующий метод:
  • Platform.AssetFile.DownloadById
Чтобы выполнить этот вызов, передайте ID элемента, возвращенный в начальном вызове GetList.
При выполнении этого вызова вы сразу же получите ответ DownloadResult с указанием пути к объекту в качестве подтверждения того, что запрос выполнен. Вам также следует прослушивать уведомления DownloadUpdate, в которых возвращается информация о переданных байтах, и флаг завершения, который уведомляет о завершении скачивания.
Логотип навигации
Русский
© 2026 Meta