Подавляющее большинство прибыльных приложений можно загрузить и установить бесплатно, но они предлагают ряд виртуальных продуктов для продажи. Это может варьироваться от разблокировки нового смайлика до подписки на потоковое мультимедиа и называется покупками в приложении мобильными разработчиками. Если вам интересно, как использовать их в приложении NativeScript Vue, то этот пост для вас. Я расскажу, как добавить поддержку покупки программных продуктов через Apple App Store и Google Play Store с помощью NativeScript Vue, а также как обнаружить эти покупки, чтобы разблокировать функции вашего приложения.

Для начала мы создадим новое пустое приложение Vue, используя интерфейс командной строки NativeScript:

tns create ns6inappbuy

Для поддержки In-App Purchases (IAP) мы будем использовать Плагин NativeScript Purchase (это последний бесплатный выпуск плагина, который в настоящее время работает с NativeScript 6), установленный с помощью:

tns plugin add nativescript-purchase

Прежде чем мы сможем начать использовать приложение, нам нужно настроить виртуальные продукты для продажи в App Store и Play Store. Для Android Play Store нам нужно будет создать и загрузить приложение с минимальным присутствием в магазине, прежде чем мы сможем протестировать процесс покупки. Для iOS App Store требуется только настроить новое приложение в консоли App Store Connect, не требуя двоичного файла или сведений о присутствии в магазине, поэтому мы начнем с платформы iOS, чтобы убедиться, что код покупки работает правильно.

Настройка IAP для Apple App Store

Во-первых, вам нужно будет зарегистрировать новый идентификатор приложения в Apple Developer Console, используя тот же идентификатор приложения, который указан в вашем package.json.

Это займет некоторое время, прежде чем мы сможем использовать его в App Store Connect Console. Когда он будет готов, вы сможете выбрать его в качестве идентификатора пакета для нового приложения iOS, созданного в вашей учетной записи App Store.

Теперь, когда приложение зарегистрировано, мы можем добавить продукт IAP, перейдя на вкладку «Функции» для нового приложения в консоли App Store Connect. Вы можете проигнорировать это предупреждение, так как мы будем проводить тестирование этого приложения только в песочнице, и вам не нужно будет отправлять на утверждение полное приложение, чтобы протестировать интеграцию.

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

Чтобы включить опцию автоматического продления подписки, перейдите в раздел «Соглашения, налоги и банковские услуги» в App Store Connect:

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

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

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

Для этого поста я создам два продукта для IAP. Это будет ежемесячная повторяющаяся подписка (для отображения случайного изображения) и одноразовое обновление (нерасходуемый) продукт (для отображения приветственного текста). Начнем с нерасходуемого продукта, которому мы назначим идентификатор продукта com.angelengineering.ns6inappbuy.product1. Заполните остальные данные, включая справочное имя, цену, отображаемое имя и описание, прежде чем сохранять новый продукт. Вы будете получать предупреждение «Отсутствуют метаданные», пока не добавите снимок экрана в раздел снимков экрана для продукта (сейчас вы можете добавить любой снимок экрана, сделанный из симулятора iOS, поскольку он никогда не будет отправлен на проверку).

Как только это будет принято, добавьте другой продукт в качестве автоматически возобновляемой подписки, введите имя и идентификатор продукта com.angelengineering.ns6inappbuy.product2 и продолжайте заполнять остальную информацию, включая имя для группы вариантов подписки, которые вы будете предлагать ( вместе с 1 локализацией), справочное имя, продолжительность подписки, цена подписки, локализация информации в магазине приложений и тот же снимок экрана iOS в разделе «Обзорная информация».

Когда эти два продукта готовы в App Store, давайте вернемся к приложению и начнем их использовать. Мы начнем с импорта библиотеки плагинов и добавления некоторого кода инициализации в app/app.js:

import Vue from "nativescript-vue";
import Home from "./components/Home";
import * as purchase from "nativescript-purchase";

Vue.config.silent = false;
global.purchaseInitPromise = purchase.init([
    "com.angelengineering.ns6inappbuy.product1",
    "com.angelengineering.ns6inappbuy.product2"
]);

new Vue({
    template: `
        <Frame>
            <Home />
        </Frame>`,
    components: {
        Home
    }
}).$start();

Поскольку для инициализации плагина может потребоваться больше времени, чем для инициализации приложения, мы обертываем вызов инициализации как обещание, хранящееся в глобальном объекте, чтобы мы могли получить к нему доступ на нашей главной странице приложения app/components/Home.vue. Мы также должны предоставить идентификаторы продуктов для элементов, которые должны совпадать с теми, которые вы настроили в App Store Connect. Давайте отредактируем app/components/Home.vue так:

<template>
    <Page>
        <ActionBar title="In-App Purchase" class="action-bar">
            <ActionItem ios.position="right" text="Restore" @tap="onRestoreTap" />
        </ActionBar>
        <StackLayout>
            <StackLayout v-for="item in Items" :key="item.localizedTitle">
                <GridLayout rows="auto, auto" columns="*, auto" padding="5" @tap="onProductTap(item)">
                    <Label row="0" col="0" :text="item.localizedTitle" textWrap="true" class="item-title" color="black" fontSize="18" />
                    <Label row="1" col="0" :text="item.localizedDescription" textWrap="true" color="rgb(75, 75, 75)" />
                    <Label row="0" rowSpan="2" col="1" :text="item.isPurchased?'Bought':item.priceFormatted" color="black" fontSize="24" />
                </GridLayout>
            </StackLayout>
            <Label v-show="isLoading" text="Loading..." />
            <ActivityIndicator :busy="isLoading" />
            <Label v-show="showGreeting" text="HELLO USER!!" fontSize="40" color="blue" />
            <Image v-show="showPicture" src="https://picsum.photos/300/200" />
        </StackLayout>
    </Page>
</template>

<script>
import * as purchase from "nativescript-purchase";
import * as applicationSettings from "application-settings";
import { Transaction, TransactionState } from "nativescript-purchase/transaction";
import { Product } from "nativescript-purchase/product";
import { ItemEventData } from "ui/list-view";
export default {
    data() {
        return {
            Items: [],
            isLoading: true,
            showGreeting: false,
            showPicture: false,
        }
    },
    created() {
        let that = this
        global.purchaseInitPromise.then(() => {
            purchase.getProducts().then((products) => {
                    products.forEach((product) => {
                        if (applicationSettings.getBoolean(product.productIdentifier)) {
                            console.log("product " + product.productIdentifier + " already purchased")
                            product.isPurchased = true
                            if (product.productIdentifier == 'com.angelengineering.ns6inappbuy.product1') that.showGreeting = true
                            if (product.productIdentifier == 'com.angelengineering.ns6inappbuy.product2') that.showPicture = true
                        }
                    });
                    that.Items = products
                    that.isLoading = false
                })
                .catch((e) => {
                    console.log(e)

                    that.isLoading = true
                });
        })
        purchase.on(purchase.transactionUpdatedEvent, (transaction) => {
            if (transaction.transactionState === TransactionState.Purchased) {
                alert(`Congratulations you purchased ${transaction.productIdentifier}!`);
                console.log(transaction.transactionDate);
                console.log(transaction.transactionIdentifier);
                applicationSettings.setBoolean(transaction.productIdentifier, true);
                that.Items.forEach(item => { if (item.productIdentifier == transaction.productIdentifier) item.isPurchased = true })
                if (transaction.productIdentifier == 'com.angelengineering.ns6inappbuy.product1') that.showGreeting = true
                if (transaction.productIdentifier == 'com.angelengineering.ns6inappbuy.product2') that.showPicture = true
                if (transaction.productIdentifier.indexOf(".consume") >= 0) {//Android only
                    purchase.consumePurchase(transaction.transactionReceipt)
                        .then((responseCode) => console.log(responseCode)) // If responseCode === 0 the purchase has been successfully consumed
                        .catch((e) => console.log(e));
                }
            } else if (transaction.transactionState === TransactionState.Restored) {
                console.log(`Restored purchase of ${transaction.productIdentifier}.`);
                console.log(transaction.transactionDate);
                applicationSettings.setBoolean(transaction.productIdentifier, true);
                that.Items.forEach(item => { if (item.productIdentifier == transaction.productIdentifier) item.isPurchased = true })
                if (transaction.productIdentifier == 'com.angelengineering.ns6inappbuy.product1') that.showGreeting = true
                if (transaction.productIdentifier == 'com.angelengineering.ns6inappbuy.product2') that.showPicture = true
            } else if (transaction.transactionState === TransactionState.Failed) {
                alert(`Purchase of ${transaction.productIdentifier} failed!`);
            }
        });
        //uncomment to restore automatically on app load
        // purchase.restorePurchases();
    },
    methods: {
        onProductTap(data) {
            if (data.isPurchased) {
                console.log("This has already been purchased, ignoring")
                return false
            }
            if (purchase.canMakePayments) {
                console.log("Purchase allowed, purchasing product")
                // NOTE: 'product' must be the same instance as the one returned from getProducts()
                purchase.buyProduct(data);
            } else {
                alert("Sorry, your account is not eligible to make payments!");
            }
        },
        onRestoreTap() {
            console.log("Restoring purchases")
            purchase.restorePurchases();
        },
    },
};
</script>

<style scoped lang="scss">
@import '~@nativescript/theme/scss/variables/blue';
</style>

Начиная с функции created(), мы вызываем purchase.getProducts(), чтобы получить записи о продуктах из App/Play Store и назначить их локальному массиву Items. Они отображаются в верхней части раздела XML после загрузки. Мы также проверяем, есть ли у каждого идентификатора продукта запись в ApplicationSettings, которая устанавливается, когда мы обнаруживаем покупку продукта в следующем разделе кода. В следующем разделе назначается обработчик событий, который срабатывает всякий раз, когда подключаемый модуль покупки обнаруживает транзакцию в App/Play Store. Мы проверяем возможные типы событий, и когда обнаруживается покупка, мы устанавливаем этот идентификатор в ApplicationSettings и устанавливаем соответствующий флаг для отображения функции покупки. Для Android требуется дополнительное потребление токена покупки, необходимого для правильной регистрации в Play Маркете в качестве продажи.

В случае, если приложение было установлено на новом устройстве и в нем отсутствуют эти ApplicationSettings, пользователь может вызвать функцию purchase.restorePurchases(), которая отправит событие восстановления, обрабатываемое аналогично покупке. В конце created() есть вызов для этого, который можно раскомментировать, если вы всегда хотите проверять инициализацию приложения, или использовать кнопку на панели действий в верхней части экрана.

Запустите приложение, и теперь вы должны увидеть продукты из App Store, перечисленные на экране, если все работает правильно.

Покупки на iOS

Чтобы на самом деле протестировать покупки для iOS, вы должны использовать реальное устройство и учетную запись Sandbox Tester для совершения тестовых покупок. Если вы попытаетесь запустить это на симуляторе iOS, вы получите сообщение об ошибке при попытке совершить покупку. Прежде чем мы сможем установить это приложение на реальном устройстве, нам сначала нужно настроить профиль обеспечения. Вернитесь в Консоль учетной записи разработчика Apple и нажмите на знак плюс, чтобы добавить новый профиль.

Выберите параметр «Разработка приложений для iOS», затем перейдите на следующую страницу, выберите идентификатор приложения для этого приложения и загрузите файл подготовки.

Вы можете открыть этот файл, чтобы добавить его в свою связку ключей OSX и сделать его доступным для Xcode, или загрузить файл и добавить его в Xcode позже. Теперь мы можем подготовить пакет приложения NativeScript, используя tns build ios, и открыть файл проекта /platforms/ios/ns6inappbuy.xcworkspace с помощью Xcode. Перейдите в настройки приложения и снимите флажок «Автоматически управлять подписью», затем выберите только что импортированный профиль обеспечения. Затем нажмите элемент управления «+Возможность» и добавьте в приложение возможность покупки в приложении. Теперь вы сможете собрать и развернуть приложение на своем устройстве iOS для тестирования.

Прежде чем мы сможем совершить пробную покупку, нам также потребуется создать учетную запись Sandbox Tester, связанную с вашей учетной записью разработчика App Store. Вернитесь в App Store Connect Console и перейдите в раздел Пользователи и доступ, затем выберите Тестировщики песочницы, чтобы просмотреть текущие учетные записи тестовых пользователей. Добавьте новый, нажав значок плюс, и заполните его любым именем, но убедитесь, что вы используете уникальную учетную запись электронной почты, которую вы раньше не использовали в качестве тестового пользователя (желательно в домене, которым вы управляете, чтобы вы могли его подтвердить) . Также убедитесь, что пароль состоит не менее чем из 8 символов и содержит хотя бы одну заглавную букву.

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

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

Покупки на Android

Для Android это не будет работать в эмуляторе без полностью установленных и настроенных Google Play Store и Play Services, поэтому мы разработаем и протестируем это на реальном устройстве. Кроме того, чтобы протестировать процесс покупки продуктов, вам необходимо опубликовать приложение в Play Store и установить его оттуда.

Для начала добавим тестовый аккаунт с помощью Google Play Console. Войдите в систему, затем перейдите в НастройкиУчетная запись разработчикаСведения об учетной записи и прокрутите вниз до раздела Тестирование лицензии. Добавьте адрес электронной почты в контролируемую вами учетную запись Google, которая отличается от вашей учетной записи разработчика. Любая добавленная учетная запись будет рассматриваться как тестовая песочница для этого и любого другого приложения Android, которое вы активно используете в Play Store. Вы также должны убедиться, что ваша учетная запись продавца активна, иначе покупки не будут выполнены.

Теперь мы вернемся на главную страницу приложения в консоли Play Store и создадим новое приложение с аналогичной информацией, используемой для iOS App Store. Прежде чем мы сможем добавить какие-либо продукты IAP, нам нужно загрузить двоичный файл Android. Вам также потребуется добавить достаточно снимков экрана и другие необходимые данные для отправки реального приложения, но мы выпустим его только для внутреннего тестирования, чтобы оно не отображалось в Play Store. После того, как вы загрузите подписанный двоичный файл в магазин приложений и заполните всю необходимую информацию, мы добавим наши продукты.

Перейдите в раздел «Продукты в приложении» в консоли Play Store и добавьте управляемый продукт с тем же идентификатором, названием, ценой и описанием, которые мы использовали в нашем приложении iOS для com.angelengineering.ns6inappbuy.product1.

После сохранения перейдите в раздел «Подписки» и добавьте новый продукт подписки с той же информацией, что и com.angelengineering.ns6inappbuy.product2 из версии для iOS. После этого у вас должны быть доступны оба продукта для этого приложения в Play Store.

Теперь, когда все готово, вы сможете перейти в раздел «Управление релизами» в разделе «Выпуски приложений» и начать развертывание на закрытую альфа-версию. После того, как вы начнете этот процесс отправки, вернитесь в раздел «Управление выпуском» и «Управляйте тестовым выпуском, который вы только что развернули». Нажмите на ссылку «Управление тестировщиками», создайте новый список адресов электронной почты авторизованных тестировщиков и назначьте их этому приложению.

Проверьте раздел «Панель управления», чтобы узнать, когда приложение завершило обработку и появилось в Play Store. Теперь вы должны увидеть URL-адрес подписки в разделе «Управление закрытой альфа-версией», который будет выглядеть примерно так https://play.google.com/apps/testing/com.angelengineering.ns6inappbuy. На своем реальном устройстве Android с тестовым пользователем, вошедшим в Play Store, откройте эту ссылку, чтобы зарегистрироваться в качестве тестировщика и установить приложение. Обратите внимание, что эта ссылка будет работать только через несколько часов после того, как приложение будет выпущено в альфа-версию рабочей версии, поэтому попробуйте позже, если вы получите сообщение об ошибке при переходе по ссылке.

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

Сделанный!

Это все для этого поста. Если вы хотите загрузить окончательные исходные файлы, вы можете найти их на Github.

Первоначально опубликовано на https://blog.angelengineering.com 29 января 2020 г.