Приложения для всех: готовность к мировому рынку и Магазин Windows
Пробные версии и покупка приложения
Реализация пробной версии приложения, использование которой, можно надеяться, ведет к покупке полной версии приложения, показана в Сценарии 1 примера "Пробное приложение и покупки в приложении" (http://code.msdn.microsoft.com/windowsapps/Licensing-API-Sample-19712f1a). Когда вы запускаете этот пример и выбираете Сценарий 1, как показано на рис. 15.4., объект имитатора инициализируется с использованием данных из data/trial-mode.xml, где элементы IsActive и IsTrial установлены в true, что означает, что у нас есть действительная пробная лицензия. Параметр ExpirationDate для этой лицензии установлен в значение 1 Января 2014 года, но мы скоро с ним поэкспериментируем.
Кнопка Trial Period (Пробный период) в данном сценарии просто вычисляет количество дней, оставшихся до окончания пробного периода, используя обычные арифметические действия и сведения из свойства licenseInformation.expirationDate. Опять же, позвольте мне отметить, что подходящий способ выполнения этого заключается в использовании класса Windows.Globalization.Calendar (http://msdn.microsoft.com/library/windows/apps/windows.globalization.calendar.aspx), который мы рассмотрим позже, в разделе "Готовность к мировому рынку и локализация", так же, это показано в примере "Календарные сведения и вычисления" (http://code.msdn.microsoft.com/windowsapps/Calendar-details-and-math-b1683bb7). Использование API, разработанных для этой цели изолирует ваше приложение от региональных отличий.
Кнопки Trial Mode (Пробный режим) и Purchased (Куплено) просто выводят различные сообщения, основываясь на состоянии свойств isActive и isTrial. Оба обработчика нажатия на эти кнопки начинаются следующим образом:
var licenseInformation = currentApp.licenseInformation; if (licenseInformation.isActive) { if (licenseInformation.isTrial) {
Гораздо интереснее может сделать данные, выводимые благодаря этим кнопкам, модификация файла data/trial-mode.xml путём задания различных начальных значений для IsActive и IsTrial. Кроме того, попробуйте установить ExpirationDate на дату и время, которые уже прошли (помните, что это UTC-время, а не локальное время), и вы увидите, как IsActive автоматически примет значение false. Тае же вы можете попытаться установить ExpirationDate примерно на минуту больше текущего времени, установить точку останова в функции trailModeRefreshScenario в js/trial-mode.js, а затем запустить пример снова.
Однако, точка останова не сработает немедленно после того, как настанет время, указанное в ExpirationDate. Из соображений повышения производительности событие licensechanged не вызывается немедленно—в системе могут быть сотни дат истечения срока лицензий, которые нужно отслеживать. Событие, вместо этого, выполняется через некоторое время, приблизительно в пределах 20 минут, пэтого вы можете начать подобное тестирование после того, как сходите перекусить.
Этот пример, конечно, всего лишь меняет выводимые сообщения в соответствии с действительностью лицензии. В реальном приложении вы либо заблокируете некоторые возможности приложения для пробной лицензии, или не позволите пользователю сделать ничего, кроме покупки полной версии, когда пробный период истечет. Подобные проверки обычно производят в обработчиках событий запуска и возобновления (resuming) работы приложения.
Последний вариант в этом сценарии поддерживается с помощью кнопки Buy App (Купить приложение), возможность, которую вы практически всегда будете предоставлять пользователям пробной версии в соответствующее время, независимо от срока действия лицензии. Эта кнопка вызывает функцию, которая называется doTrialConversion и использует метод CurrentApp.requestAppPurchaseAsync (данные, выводимые примером, здесь заменены, соответствующими комментариями):
var licenseInformation = currentApp.licenseInformation; if (!licenseInformation.isActive || licenseInformation.isTrial) { currentApp.requestAppPurchaseAsync(false).done( function () { if (licenseInformation.isActive && !licenseInformation.isTrial) { // Покупка выполнена } else { // Пользовательский интерфейс покупки показан, но пользователь отменил операцию. } }, function () { // Произошла ошибка в транзакции; покупка не состоялась });
Единственный аргумент requestAppPurchaseAsync показывает, была ли отправлена строка с чеком в обработчик завершения; смотрите раздел "Чеки" ниже. В любом случае, если пользователь совершает покупку, вызывается событие licensechanged , как это происходит при истечении срока действия лицензии, в итоге вы можете собрать здесь код для обработки данных о лицензии.
Если вы вызываете requestAppPurchaseAsync при исполнении примера с использованием имитатора, вы не увидите настоящего пользовательского интерфейса Магазина Windows. Вместо этого вам будет показано предельно простое диалоговое окно из объекта имитатор, в котором вы можете задать конкретное возвращаемое значение (HRESULT):
Отправка значения S_OK указывает на то, что покупка была совершена. Флаг isTrial меняется на false, isActive устанавливается в true. Возврат других результатов, сообщающих об ошибке, активирует обработчик ошибки для requestAppPurchaseAsync. Нажатие на кнпоку Cancel (Отмена), с другой стороны, вызывает обработчик завершения, но значения isTrial и isActive остаются неизменными.
В реальных условиях, конечно, покупатели не работают с имитированными условиями Магазина Windows. Вместо этого, если ваше приложение имеет статус пробной версии (то, что вы задали при отправке приложения в Магазин Windows), они увидят кнопку Попробовать (Try) в окне описания приложения, как здесь:
Нажатие на кнопку Попробовать (Try) приведет к установке значений isActive и isTrial в true. В момент, когда приложение вызывает requestAppPurchaseAsync, Windows открывает Магазин Windows и показывает пользователю страницу сведений о приложении, где он может нажать на кнопку Купить (Buy), если захочет.
Совет. Когда я писал этот курс, я видел множество приложений, доступных в Магазине Windows и обнаружил, что, в то время как многие предлагают пробную версию, некоторые из них не сообщали о том, как купить полную версию. Я видел подобную опцию лишь тогда, когда (неизвестный) пробный период заканчивался. Если вы хотите превратить пробную лицензию в платную, лучше, как показано даже в примере, сообщить пользователю о том, что он работает в пробной версии и напомнить о возможности покупки полной версии приложения!
Врезка: приложения с истекающим сроком действия лицензии?
Хотя срок действия лицензии приложения часто используется вместе с пробной лицензией, на то, как это должно использоваться, нет ограничений: бесплатные или платные приложения так же могут иметь срок действия лицензии. Если вы не указываете на то, что приложение является пробной версией при отправке его в Магазин Windows, но задаете срок действия лицензии, флаг isActive будет установлен в false в заданную дату и время. Так же будет вызвано событие licensechanged, что позволит исполняющемуся приложению предпринять необходимые действия. Кроме того, приложение может проверить статус активности или дату истечения срока лицензии при запуске и в событии resuming и отобразить соответствующее сообщение. Подобная функция, возможна, полезна, для приложений, которые действительно имеют ограниченный срок действия, например, приложения, которые предоставляют информацию о политической кампании какого-нибудь кандидата. Конечно, срок их действия может не заканчиваться точно в день выборов – возможно, приложению есть смысл поработать еще несколько месяцев, после чего его можно полностью убрать из Магазина Windows. Всё же, как упомянуто выше, если пользователь потратил силы и время на получение приложения, почему бы не сделать его полезным даже после того, как оно выполнило свою основную задачу? Обновления приложения, кроме того, позже могут добавить к нему свежее содержимое и возможности.
Просмотр и покупка продуктов из приложения
Есть два аспекта в работе с покупками из приложения, или с продуктами (products), как они упоминаются в API. Во-первых, нужно соответствующим образом дать пользователю знать о том, что данные продукты доступны в вашем приложении. Во-вторых, позволить совершить покупку и активировть лицензию продукта.
Если вы используете пользовательскую коммерческую подсистему, приложение использует собственные службы для получения необходимой информации о продукте и поддерживает весь пользовательский интерфейс и процесс управления лицензиями для совершения операции – API Магазина Windows не играет здесь никакой роли. Если же управление продуктами осуществляется с помощью Магазина Windows, с другой стороны, коллекция ListingInformation.productListings (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.store.listinginformation.productlistings.aspx) поддерживает локализованные сведения о продукте, а метод CurrentApp.requestProductPurchaseAsync (http://msdn.microsoft.com/library/windows/apps/hh967814.aspx) обрабатывает транзакцию, в том числе, предоставляет пользовательский интерфейс. Данный раздел посвящен этим API.
Как упомянуто ранее, покупки из приложения могут иметь срок действия лицензии, после чего пользователю нужно повторно купить продукт для того, чтобы продолжить им пользоваться. Это хорошо подходит для подписок или аренды, когда пользователю сообщается во время покупки, что срок действия продукта ограничен. В общем, вам всегда следует четко сообщать о том, что лицензия на продукт имеет ограниченный срок действия. Не удивляйте пользователя, или пользователь удивит вас нелестными отзывами в Магазине Windows.
Покупки из приложения показаны в Сценарии 2 примера, который мы рассматриваем. В данном случае объект CurrentAppSimulator инициализирован данными из data/in-app-purchase.xml, которые определяют два продукта (прозаично названных Product 1и Product 2), первый из них имеет активную лицензию, второй – неактивную Когда вы переключаетесь на этот сценарий (js/in-app-purchase.js), пример загружает объект приложения ListingInformation, получает сведения о продукте из коллекции productListings и затем отображает эти сведения (как показано на рис. 15.5).
currentApp.loadListingInformationAsync().done( function (listing) { var product1 = listing.productListings.lookup("product1"); var product2 = listing.productListings.lookup("product2"); document.getElementById("product1SellMessage").innerText = "You can buy " + product1.name + " for: " + product1.formattedPrice + "."; document.getElementById("product2SellMessage").innerText = "You can buy " + product2.name + " for: " + product2.formattedPrice + "."; });
Рис. 15.5. Сценарий 2 примера "Пробное приложение и покупки в приложении" (окно обрезано). Информация о продукте, полученная из Магазина Windows, отображена синим текстом (обведена здесь красными кружками)
Обратите внимание на то, что коллекция productListings является объектом IMapView (http://msdn.microsoft.com/library/windows/apps/br226037.aspx), а не массивом. Вы можете получать конкретные элементы коллекции с использованием её метода lookup, как в вышеприведенном коде, или с использованием синтаксиса обращения к элементу по имени:
var product1 = listing.productListings["product1"];
Обход объекта IMapView требует немного больше усилий. Он не поддерживает обращение по индексу и метод foreach method. Вместо этого применяют объект IIterator (http://msdn.microsoft.com/library/windows/apps/br226026.aspx), полученный с помощью метода first, как показано здесь:
var iterator = listing.productListings.first() var product; while (iterator.hasCurrent) { product = iterator.current.value; document.getElementById(product.productId + "SellMessage").innerText = "You can buy " + product.name + " for: " + product.formattedPrice + "."; iterator.moveNext(); };
Этот код полностью эквивалентен вышеприведенному фрагменту и использует тот факт, что ID продукта в данном случае совпадает с первой частью соответствующего ID элемента в HTML. В любом случае, это код, которые можно использовать для представления переменного списка опций пользователю.
Вам, вероятно, понадобится отфильтровать в подобном списке продукты, которые уже куплены. В подобном случае выполните поиск элементов в коллекции CurrentApp.licenseInformation.productLicenses, которая является еще одним представлением IMapView объектов ProductLicense, испльзуя ID продукта в качестве ключа. Вот как выглядит код, модифицированный с учетом подобной фильтрации:
var iterator = listing.productListings.first() var licenses = currentApp.licenseInformation.productLicenses; var product, message; while (iterator.hasCurrent) { product = iterator.current.value; if (licenses[product.productId].isActive) { message = "You own " + product.name + "."; } else { message = "You can buy " + product.name + " for: " + product.formattedPrice + "."; } document.getElementById(product.productId + "SellMessage").innerText = message; iterator.moveNext(); };
Если вы используете этот код в Сценарии 2 примера (который есть в модифицированном примере в дополнительных материалах к лекции), вы увидите сообщение: "You own Product 1" (У вас есть Product 1", когда вы переключитесь на этот сценарий. Так же вы можете добавить дальнейшие улучшения для проверки свойства expirationDate лицензии на каждый из продуктов и для отображения оставшегося времени её действия.
Кроме того, вы можете заметить, что ProductListing (из коллекции приложения productListings) содержит лишь значения name, а не описания. Это означает, что имя – это и есть описание и вам следует использовать его таким образом, вместо того, чтобы использовать другой тип идентификатора, для которого у вас уже есть productId. Другими словами, продукт, который предоставляет 20 дополнительных уровней к игре, нужно назвать как-то наподобие "Двадцать дополнительных уровней к игре", а не "extra_Levels", так как данное имя будет отображаться в пользовательском интерфейсе.
Когда вы выставляете перед пользователем ваши продукты, пользователь решит (я здесь очень позитивно настроен) в один прекрасный момент купить один из них или несколько. Когда пользователь нажимает на соответствующую кнопку, наподбие кнопок Buy Product (Купить продукт) в примере (посмотрите снова на Рис. 6.5.), приложению лишь нужно вызвать requestProductPurchaseAsync с ID продукта и логическим параметров, указывающим на то, нужно ли методу предоставлять чек:
currentApp.requestProductPurchaseAsync("product1", false).done( function () { if (licenseInformation.productLicenses.lookup("product1").isActive) { // Покупка была выполнена; пользовательский интерфейс не отображается, //если пользователь уже владеет данным продуктом. } else { // Пользовательский интерфейс для покупки был показан, но пользователь отменил операцию. } }, function () { // Произошла ошибка в транзакции; покупка не состоялась });
Если продукт уже имеет активную лицензию, requestProductPurchaseAsync просто вызовет обработчик завершения без показа пользовательского интерфейса, так как в этом нет необходимости. В противном случае пользователь увидит серию вопрос о подтверждении покупки, включая поля для ввода учетных данных. Для того, чтобы увидеть весь этот процесс, посмотрите материал "Покупка из приложения с точки зрения пользователя" (http://msdn.microsoft.com/library/windows/apps/Hh924350.aspx). Обычное сообщение о подтверждении покупки показано ниже:
Обратите внимание на предупреждение, отображенное здесь, которое существует в соответствии с законодательством некоторых стран. Оно гласит, что пользователь не сможет отменить операцию, нажав на кнопку Buy (Купить). Это не вполне верно: на следующем шаге, при вводе учетных данных, от покупки можно отказаться, отменив их ввод.
При использовании CurrentAppSimulator, конечно, вы не увидите диалоговых окон Магазина Windows, вместо них отобразится лишь простое диалоговое окно для управления возвращаемым результатом:
Как и в случае с покупкой приложения, любые изменения в статусе лицензии продукта приводят к исполнению события licensechanged; ваш обработчик для этого события – это хорошее место для обновления статуса приложения и запуска загрузки данных, связанных с покупкой. То же самое событие вызывается, если лицензия на продукт, имеющая срок действия, истекает, что сигнализирует вам о том, что можно снова сделать доступной покупку продукта. Вероятно, вам так же понадобится сообщить пользователю о данном состоянии, может быть, с помощью всплывающего уведомления или встроенного сообщения, когда пользователь попытается получить доступ к данной возможности приложения.
Чеки
И метод requestAppPurchaseAsync , и метод requestProductPurchaseAsync объекта CurrentApp имеют опцию, как мы видели ранее, для предоставления строки чека (receipt) в обработчик завершения асинхронной операции. Их метод getAppReceiptAsync (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.store.currentapp.getappreceiptasync.aspx) так же предоставляет все чеки для приложения и любых покупок из приложения в любое время. Вообще говоря, чеки особенно полезны тогда, когда сервису нужно проверить, что у приложения есть право использовать некоторую функциональность. Приложение получает чек и отправляет его на сервис, который может выполнить любую необходимую проверку.
Во всех случаях, чеки – это XML-строки, содержащие такую информацию, как идентификатор приложения или продукта, даты выполнения покупок и выпуска чека и цифровую подпись. Подробности о применяемой схеме XML можно найти по ссылке, приведенной выше.
В качестве примера, вот строка чека, полученная из requestProductPurchaseAsync при покупке продуктаProduct 2 в Сценарии 2 примера:
<?xml version=\"1.0\" encoding=\"utf-8\"?><Receipt Version=\"1.0\" ReceiptDate=\"2012-09-11T17:35:55Z\" CertificateId=\"\" ReceiptDeviceId=\"7a61447d-c8f4-457a-8310-363cbdffd21c\"> <ProductReceipt Id=\"e729be49-8299-4122-b6fb-a95bcfac6a7c\" AppId=\"Microsoft.SDKSamples.Store.JS_8wekyb3d8bbwe\" ProductId=\"product2\" PurchaseDate=\"2012-09-11T17:35:55Z\" ProductType=\"Durable\" /> </Receipt> Вот что в Сценарии 5 того же примера мы получаем с помощью вызова getAppReceiptAsync: <?xml version=\"1.0\" encoding=\"utf-8\"?><Receipt Version=\"1.0\" ReceiptDate=\"2012-09-11T17:39:39Z\" CertificateId=\"\" ReceiptDeviceId=\"50b4267d-437d-429e-a4b8-88da96da9e52\"> <AppReceipt Id=\"1550eb89-31ae-4559-b516-267afe47ae19\" AppId=\"Microsoft.SDKSamples.Store.JS_8wekyb3d8bbwe\" PurchaseDate=\"2012-09-11T17:39:12Z\" LicenseType=\"Full\" /> <ProductReceipt Id=\"e2a62d42-dbca-43d2-b779-66eb916d9df4\" AppId=\"Microsoft.SDKSamples.Store.JS_8wekyb3d8bbwe\" ProductId=\"product2\" PurchaseDate=\"2012-09-11T17:39:12Z\" ProductType=\"Durable\" ExpirationDate=\"2014-01-01T00:00:00Z\" /> <ProductReceipt Id=\"21967f50-ac55-4b41-acd9-f1e86ad6c7b9\" AppId=\"Microsoft.SDKSamples.Store.JS_8wekyb3d8bbwe\" ProductId=\"product1\" PurchaseDate=\"2012-09-11T17:39:12Z\" ProductType=\"Durable\" /> </Receipt>
Если вы хотите получить чек в виде XML-документа вместо строки (для отображения или печати), подобный объект создать очень просто, как, например, делалось в Главе 2 для XML плиток и уведомлений:
var receiptDOM = new Windows.Data.Xml.Dom.XmlDocument(); receiptDOM.loadXml(receipt);