Обзор StoreKit и получение сведений о продукте в Xamarin.iOS

Пользовательский интерфейс для покупки в приложении показан на снимках экрана ниже. Перед выполнением любой транзакции приложение должно получить цену и описание продукта для отображения. Затем, когда пользователь нажимает кнопку "Купить", приложение отправляет запрос в StoreKit, который управляет диалоговым окном подтверждения и именем входа Apple ID. Если транзакция завершается успешно, StoreKit уведомляет код приложения, который должен хранить результат транзакции и предоставить пользователю доступ к своей покупке.

StoreKit уведомляет код приложения, который должен хранить результат транзакции и предоставить пользователю доступ к своей покупке

Классы

Для реализации покупок в приложении требуются следующие классы из платформы StoreKit:

SKProductsRequest — запрос на StoreKit для утвержденных продуктов для продажи (App Store). Можно настроить с помощью нескольких идентификаторов продукта.

  • SKProductsRequestDelegate — объявляет методы для обработки запросов и ответов на продукты.
  • SKProductsResponse — отправлено делегату из StoreKit (App Store). Содержит skProducts, соответствующие идентификаторам продукта, отправленным с запросом.
  • SKProduct — продукт, полученный из StoreKit (настроенный в iTunes Подключение). Содержит сведения о продукте, например идентификатор продукта, название, описание и цена.
  • SKPayment — создан с идентификатором продукта и добавлен в очередь оплаты для выполнения покупки.
  • SKPaymentQueue — запросы на оплату в очереди, отправляемые в Apple. Уведомления активируются в результате обработки каждого платежа.
  • SKPaymentTransaction — представляет завершенную транзакцию (запрос на покупку, обработанный App Store и отправленный обратно в приложение через StoreKit). Транзакция может быть приобретена, восстановлена или не выполнена.
  • SKPaymentTransactionObserver — настраиваемый подкласс, который отвечает на события, созданные очередью оплаты StoreKit.
  • Операции StoreKit являются асинхронными — после запуска SKProductRequest или skPayment добавляется в очередь, элемент управления возвращается в код. StoreKit будет вызывать методы на подклассе SKProductsRequestDelegate или SKPaymentTransactionObserver при получении данных с серверов Apple.

На следующей схеме показаны связи между различными классами StoreKit (абстрактные классы должны быть реализованы в приложении):

Связи между различными классами StoreKit абстрактные классы должны быть реализованы в приложении

Эти классы подробно описаны далее в этом документе.

Тестирование

Большинство операций StoreKit требуют реального устройства для тестирования. Получение сведений о продукте (т. е. цена и описание) будет работать в симуляторе, но операции покупки и восстановления возвращают ошибку (например, FailedTransaction Code=5002 Произошла неизвестная ошибка).

Примечание. StoreKit не работает в симуляторе iOS. При запуске приложения в симуляторе iOS StoreKit записывает предупреждение, если приложение пытается получить очередь оплаты. Тестирование хранилища должно выполняться на фактических устройствах.

Важно. Не войдите с помощью тестовой учетной записи в приложении Параметры. Вы можете использовать приложение Параметры для выхода из любой существующей учетной записи Apple ID, а затем необходимо дождаться запроса в последовательности покупки в приложении для входа с помощью тестового идентификатора Apple ID.

Если вы пытаетесь войти в реальное хранилище с помощью тестовой учетной записи, она будет автоматически преобразована в реальный идентификатор Apple ID. Эта учетная запись больше не будет использоваться для тестирования.

Чтобы проверить код StoreKit, необходимо выйти из обычной тестовой учетной записи iTunes и войти с помощью специальной тестовой учетной записи (созданной в iTunes Подключение), связанной с тест-хранилищем. Чтобы выйти из текущей учетной записи, посетите Параметры > iTunes и App Store, как показано ниже.

Чтобы выйти из текущей учетной записи, посетите Параметры iTunes и App Store

затем выполните вход с помощью тестовой учетной записи при запросе StoreKit в приложении:

Чтобы создать тестовых пользователей в iTunes Подключение щелкните "Пользователи и роли" на главной странице.

Чтобы создать тестовых пользователей в iTunes Подключение щелкните

Выбор тестировщиков песочницы

Выбор тестировщиков песочницы

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

Отображается список существующих пользователей

Новые тестовые пользователи имеют похожие атрибуты на реальный идентификатор Apple ID (например, имя, пароль, секретный вопрос и ответ). Сохраните запись всех сведений, введенных здесь. Поле Select iTunes Store определяет, какая валюта и язык покупки в приложении будут использоваться при входе в систему в качестве этого пользователя.

Поле Select iTunes Store определяет валюту и язык пользователя для покупок в приложении

Получение сведений о продукте

Первым шагом в продаже продукта покупки в приложении является его отображение: получение текущей цены и описания из App Store для отображения.

Независимо от типа продуктов, которые продает приложение (потребляемые, неиспотребляемые или тип подписки), процесс получения сведений о продукте для отображения совпадает. Код InAppPurchaseSample, сопровождающий эту статью, содержит проект с именем Consumables , демонстрирующий получение рабочей информации для отображения. В примере показано:

  • Создайте реализацию SKProductsRequestDelegate и реализуйте абстрактный ReceivedResponse метод. Пример кода вызывает этот InAppPurchaseManager класс.
  • Проверьте, разрешены ли платежи (с помощью SKPaymentQueue.CanMakePayments ).
  • Создайте SKProductsRequest экземпляр с идентификаторами продукта, определенными в iTunes Подключение. Это делается в методе примера InAppPurchaseManager.RequestProductData .
  • Вызов метода Start в объекте SKProductsRequest . Это активирует асинхронный вызов серверов App Store. Делегат ( InAppPurchaseManager ) будет вызываться с результатами.
  • Метод делегата InAppPurchaseManagerReceivedResponse обновляет пользовательский интерфейс с данными, возвращаемыми из App Store (цены и описания продуктов, или сообщения о недопустимых продуктах).

Общее взаимодействие выглядит следующим образом ( StoreKit встроенный в iOS, и App Store представляет серверы Apple):

Получение графа сведений о продукте

Пример отображения сведений о продукте

Пример кода для использования показывает, как можно получить сведения о продукте. На главном экране примера отображаются сведения о двух продуктах, полученных из App Store:

На главном экране отображаются информационные продукты, полученные из App Store

Пример кода для получения и отображения сведений о продукте подробно описан ниже.

Методы ViewController

Класс ConsumableViewController будет управлять отображением цен для двух продуктов, идентификаторы продуктов которых жестко закодированы в классе.

public static string Buy5ProductId = "com.xamarin.storekit.testing.consume5credits",
   Buy10ProductId = "com.xamarin.storekit.testing.consume10credits";
List<string> products;
InAppPurchaseManager iap;
public ConsumableViewController () : base()
{
   // two products for sale on this page
   products = new List<string>() {Buy5ProductId, Buy10ProductId};
   iap = new InAppPurchaseManager();
}

На уровне класса также должен быть объявлен NSObject, который будет использоваться для настройки NSNotificationCenter наблюдателя:

NSObject priceObserver;

В методе ViewWillAppear наблюдатель создается и назначается с помощью центра уведомлений по умолчанию:

priceObserver = NSNotificationCenter.DefaultCenter.AddObserver (
  InAppPurchaseManager.InAppPurchaseManagerProductsFetchedNotification,
(notification) => {
   // display code goes here, to handle the response from the App Store
}

В конце ViewWillAppear метода вызовите RequestProductData метод для запуска запроса StoreKit. После выполнения этого запроса StoreKit асинхронно обращается к серверам Apple, чтобы получить информацию и отправить ее обратно в приложение. Это достигается подклассом SKProductsRequestDelegate ( InAppPurchaseManager), описанным в следующем разделе.

iap.RequestProductData(products);

Код для отображения цены и описания просто извлекает сведения из SKProduct и назначает его элементам управления UIKit (обратите внимание, что мы отображаем LocalizedTitle и LocalizedDescription — StoreKit автоматически разрешает правильный текст и цены на основе параметров учетной записи пользователя). Следующий код относится к созданному выше уведомлению:

priceObserver = NSNotificationCenter.DefaultCenter.AddObserver (
  InAppPurchaseManager.InAppPurchaseManagerProductsFetchedNotification,
(notification) => {
   // display code goes here, to handle the response from the App Store
   var info = notification.UserInfo;
   if (info.ContainsKey(NSBuy5ProductId)) {
       var product = (SKProduct) info.ObjectForKey(NSBuy5ProductId);
       buy5Button.Enabled = true;
       buy5Title.Text = product.LocalizedTitle;
       buy5Description.Text = product.LocalizedDescription;
       buy5Button.SetTitle("Buy " + product.Price, UIControlState.Normal); // price display should be localized
   }
}

Наконец, метод должен убедиться, что ViewWillDisappear наблюдатель удален:

NSNotificationCenter.DefaultCenter.RemoveObserver (priceObserver);

Методы SKProductRequestDelegate (InAppPurchaseManager)

Метод RequestProductData вызывается, когда приложение хочет получить цены на продукты и другие сведения. Он анализирует коллекцию идентификаторов продукта в правильный тип данных, а затем создает SKProductsRequest с этой информацией. Вызов метода Start приводит к тому, что сетевой запрос будет выполнен на серверах Apple. Запрос будет выполняться асинхронно и вызывать ReceivedResponse метод делегата после успешного завершения.

public void RequestProductData (List<string> productIds)
{
   var array = new NSString[productIds.Count];
   for (var i = 0; i < productIds.Count; i++) {
       array[i] = new NSString(productIds[i]);
   }
   NSSet productIdentifiers = NSSet.MakeNSObjectSet<NSString>(array);​​​
   productsRequest = new SKProductsRequest(productIdentifiers);
   productsRequest.Delegate = this; // for SKProductsRequestDelegate.ReceivedResponse
   productsRequest.Start();
}

iOS автоматически перенаправит запрос на версию песочницы или рабочей версии App Store в зависимости от того, с каким профилем подготовки приложение работает, поэтому при разработке или тестировании приложения запрос будет иметь доступ ко всем продуктам, настроенным в iTunes Подключение (даже тех, которые еще не были отправлены или утверждены Apple). Когда приложение находится в рабочей среде, запросы StoreKit возвращают только сведения для утвержденных продуктов.

Переопределенный ReceivedResponse метод вызывается после того, как серверы Apple ответили данными. Так как это вызывается в фоновом режиме, код должен проанализировать допустимые данные и использовать уведомление для отправки сведений о продукте в любые представленияController, которые прослушиваются для этого уведомления. Код для сбора допустимых сведений о продукте и отправки уведомления показан ниже:

public override void ReceivedResponse (SKProductsRequest request, SKProductsResponse response)
{
   SKProduct[] products = response.Products;
   NSDictionary userInfo = null;
   if (products.Length > 0) {
       NSObject[] productIdsArray = new NSObject[response.Products.Length];
       NSObject[] productsArray = new NSObject[response.Products.Length];
       for (int i = 0; i < response.Products.Length; i++) {
           productIdsArray[i] = new NSString(response.Products[i].ProductIdentifier);
           productsArray[i] = response.Products[i];
       }
       userInfo = NSDictionary.FromObjectsAndKeys (productsArray, productIdsArray);
   }
   NSNotificationCenter.DefaultCenter.PostNotificationName (InAppPurchaseManagerProductsFetchedNotification, this, userInfo);
}

Хотя на RequestFailed схеме не отображается, метод также следует переопределить, чтобы предоставить пользователю некоторые отзывы, если серверы App Store недоступны (или другая ошибка). Пример кода просто записывается в консоль, но реальное приложение может выбрать запрос к error.Code свойству и реализовать пользовательское поведение (например, оповещение пользователю).

public override void RequestFailed (SKRequest request, NSError error)
{
   Console.WriteLine (" ** InAppPurchaseManager RequestFailed() " + error.LocalizedDescription);
}

На этом снимке экрана показан пример приложения сразу после загрузки (если сведения о продукте недоступны):

Пример приложения сразу после загрузки, если сведения о продукте недоступны

Недопустимые продукты

Также SKProductsRequest может возвращать список недопустимых идентификаторов продуктов. Недопустимые продукты обычно возвращаются из-за одной из следующих:

Идентификатор продукта неправильно указан. Принимаются только допустимые идентификаторы продукта.

Продукт не был утвержден . Во время тестирования все продукты, которые очищаются для продажи, должны быть возвращены SKProductsRequest; однако в рабочей среде возвращаются только утвержденные продукты.

Идентификатор приложения не является явным — идентификаторы приложений карта (с звездочкой) не допускают покупку в приложении.

Неправильный профиль подготовки. Если вы вносите изменения в конфигурацию приложения на портале подготовки (например, включение покупок в приложении), не забудьте повторно создать и использовать правильный профиль подготовки при создании приложения.

Контракт с платными приложениями iOS не существует . Функции StoreKit не будут работать вообще, если нет действительного контракта для учетной записи разработчика Apple.

Двоичный файл находится в состоянии "Отклонено" — если в состоянии "Отклонено " (либо командой App Store, либо разработчиком), функции StoreKit не будут работать.

Метод ReceivedResponse в примере кода выводит недопустимые продукты в консоль:

public override void ReceivedResponse (SKProductsRequest request, SKProductsResponse response)
{
   // code removed for clarity
   foreach (string invalidProductId in response.InvalidProducts) {
       Console.WriteLine("Invalid product id: " + invalidProductId );
   }
}

Отображение локализованных цен

Ценовые категории указывают определенную цену для каждого продукта во всех международных магазинах приложений. Чтобы убедиться, что цены отображаются правильно для каждой валюты, используйте следующий метод расширения (определенный в SKProductExtension.cs) вместо свойства Price каждого SKProduct:

public static class SKProductExtension {
   public static string LocalizedPrice (this SKProduct product)
   {
       var formatter = new NSNumberFormatter ();
       formatter.FormatterBehavior = NSNumberFormatterBehavior.Version_10_4;  
       formatter.NumberStyle = NSNumberFormatterStyle.Currency;
       formatter.Locale = product.PriceLocale;
       var formattedString = formatter.StringFromNumber(product.Price);
       return formattedString;
   }
}

Код, задающий заголовок кнопки, использует метод расширения следующим образом:

string Buy = "Buy {0}"; // or a localizable string
buy5Button.SetTitle(String.Format(Buy, product.LocalizedPrice()), UIControlState.Normal);

Использование двух разных тестовых учетных записей iTunes (один для американского магазина и одного для японского магазина) приводит к следующим снимкам экрана:

Две разные тестовые учетные записи iTunes с определенными результатами языка

Обратите внимание, что магазин влияет на язык, используемый для сведений о продукте и цене валюты, а язык устройства влияет на метки и другое локализованное содержимое.

Помните, что для использования другой тестовой учетной записи магазина необходимо выйти из Параметры > iTunes и App Store и повторно запустить приложение для входа с другой учетной записью. Чтобы изменить язык устройства, перейдите на Параметры общий > международный > язык>.