Рано или поздно наступает момент, когда разработчику нужно задуматься о том, как монетизировать своё приложение, чтобы оно приносило доход. Есть различные бизнес-модели, с помощью которых можно этого достичь, однако наиболее популярной является использование рекламы в приложении. Одним из плюсов использования рекламы является то, что она хорошо сочетается с другой бизнес-моделью — покупками внутри приложения. Например, пользователь может заплатить некоторую сумму денег для того, чтобы отключить показ рекламы в приложении.
В этой статье мы рассмотрим, как можно реализовать встроенные покупки на примере своего приложения Менеджер паролей от Wi-Fi сетей.
Возможность покупок в приложениях реализована благодаря In-app Billing. In-app Billing — это сервис Google Play, который позволяет продавать цифровой контент внутри приложений. Этот сервис можно использовать для продажи широкого спектра контента, включая загружаемый контент, такой как мультимедийные файлы и фотографии, виртуальный контент, такой как уровни игры или различные вспомогательные предметы, премиальные услуги и многое другое.
Встроенные покупки можно подключить для любого приложения, опубликованного в Google Play. Ничего особенного для этого не требуется, только аккаунт разработчика Google Play Console и аккаунт продавца Google Wallet. Android SDK также содержит пример приложения с реализованными встроенными покупками.
Как работают встроенные покупки?
Ваше приложение обращается к сервису In-app Billing с помощью API, который предоставляется приложением Google Play, установленным на устройстве. Затем Google Play передает платежные запросы и ответы на запросы между вашим приложением и сервером Google Play. Таким образом, ваше приложение никогда напрямую не связывается с сервером Google Play. Вместо этого ваше приложение отправляет запросы в приложение Google Play через межпроцессную связь (IPC) и получает от него ответы, нет необходимости поддерживать какие-либо соединения между вашим приложением и сервером Google Play.
In-app Billing поддерживает широкую совместимость, он работает на устройствах под управлением Android 2.2 (API 8) или выше, на которых установлена последняя версия приложения Google Play.
API In-app Billing предоставляет следующие возможности:
- Ваше приложение отправляет запросы с помощью модернизированного API, который позволяет пользователям легко запрашивать информацию о продукте из Google Play и заказывать продукты в приложении. API быстро восстанавливает продукты на основе прав пользователя.
- API синхронно передает информацию о заказе на устройство при завершении покупки.
- Все покупки регулируемы, т.е. Google Play отслеживает права пользователя на продукты. Пользователь не может владеть несколькими экземплярами одного продукта в приложении; только один экземпляр может принадлежать пользователю в любой момент времени.
- Приобретённые продукты могут быть использованы. В таком случае они возвращаются в бесхозное состояние и могут быть куплены снова.
- API обеспечивает поддержку подписки.
Интеграция In-app Billing в приложение
Есть разные способы, как встроить в своё приложение In-app Billing: можно это делать как вручную, так и используя сторонние библиотеки. Одной из таких библиотек является Checkout, которая уже содержит в себе готовую к применению реализацию сервиса. Ею и воспользуемся.
Checkout — это реализация In-app Billing API. Большим плюсом здесь является, что с помощью этой библиотеки можно сделать интеграцию встроенных покупок в приложение намного проще, чем если бы это делалось вручную с нуля.
Checkout решает общие проблемы, с которыми могут столкнуться разработчики при работе с покупками, например:
- Как отменить все запросы, когда активность уничтожена?
- Как запросить информацию о покупках в фоновом потоке?
- Как проверить покупку?
- Как загрузить все покупки с использованием данных continuationToken или SKU (уникальный идентификатор продукта)?
- Как добавить покупки с минимумом шаблонного кода?
Checkout может быть использован с любым фреймворком или без него. Он имеет четкое разграничение функциональности, доступной в разных контекстах: покупки могут быть сделаны только в активности, тогда как SKU может быть загружен в сервис или класс Application.
Перед началом работы библиотеку нужно добавить в проект. Для этого в файле build.gradle модуля приложения добавить зависимость в блок dependencies.
dependencies { ... compile 'org.solovyev.android:checkout:1.2.1' }
Для работы с покупками требуется специальное разрешение com.android.vending.BILLING, которое будет добавлено в AndroidManifest.xml автоматически с помощью Gradle. Вы также можете добавить его вручную, добавив в файл манифеста следующую строчку перед элементом <application>:
<uses-permission android:name="com.android.vending.BILLING"/>
Создадим экземпляр класса Billing в Application, откуда затем будем брать его при необходимости. Если у вас нет класса Application в проекте, вы можете легко создать его. Для этого нужно добавить в файле AndroidManifest.xml в элемент <application> атрибут android:name=».Имя класса», например:
<application android:name=".App" android:allowBackup="true" android:fullBackupContent="@xml/mybackupscheme" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:resizeableActivity="true" android:roundIcon="@mipmap/ic_launcher_round" android:theme="@style/Theme.DesignDemo">
После этого нужно поставить курсор на имя класса, нажать Alt + Enter и выбрать опцию «Create class», после чего Android Studio создаст его.
В этом классе нам нужно добавить следующий код:
public class App extends Application { public void onCreate() { super.onCreate(); } private final Billing mBilling = new Billing(this, new Billing.DefaultConfiguration() { @Override public String getPublicKey() { return BASE64_PUBLIC_KEY; } }); public Billing getBilling() { return mBilling; } }
BASE64_PUBLIC_KEY это ключ, который используется для установления безопасного подключения между вашим приложением и сервером Google Play. Получить этот ключ вы можете в Google Play Console, перейдя в раздел «Инструменты разработки» — «Службы и API». Там в «Лицензирование и продажа контента» вы увидите сгенерированный для вашего приложения ключ, который нужно будет добавить в приложение, например, объявить как строковую константу в классе Application.
Класс Billing это основной класс для работы с библиотекой, он отвечает за:
- подключение и отключение услуг биллинга;
- выполнение платежных запросов;
- кеширование результатов запросов;
- создание объектов Checkout;
- логирование;
Для того, чтобы избежать множественных подключений к службе In-app Billing, следует использовать только один экземпляр класса Billing, именно по этой причине мы и создаём его в классе Application.
Теперь в классе активности при её создании инициализируем экземпляр класса ActivityCheckout, который наследует от базового класса Checkout.
private ActivityCheckout mCheckout; ... mCheckout = Checkout.forActivity(mainView.getActivity(), App.get().getBilling()); mCheckout.start(); mCheckout.createPurchaseFlow(new PurchaseListener());
Класс Checkout это средний уровень библиотеки, он использует класс Billing в определённом контексте (в Application, активности или сервисе), проверяет, поддерживаются ли покупки на устройстве и выполняет запросы. ActivityCheckout это подкласс, который способен покупать различные предметы, для создания его экземпляра нужно вызвать метод Checkout.forActivity() и передать в параметры активность и экземпляр Billing.
Метод start() запускает созданный экземпляр и отправляет запрос, который проверяет, поддерживается ли биллинг на этом устройстве.
Метод createPurchaseFlow() создаёт постоянный поток для покупок со слушателем, который будет получать обновления данных о покупках. Код слушателя выглядит следующим образом:
private class PurchaseListener extends EmptyRequestListener<Purchase> { @Override public void onSuccess(@Nonnull Purchase purchase) { if (purchase.sku.equals(AD_FREE)) { SP.setBoolean(mainView.getContext(), AD_FREE, true); } if (purchase.sku.equals(DONATE)) { Toast.makeText(mainView.getContext(), R.string.message_donate_tnx, Toast.LENGTH_LONG) .show(); } } }
Класс PurchaseListener наследует от EmptyRequestLisneter<Purchase>, который имеет методы onSuccess() и onError(). В данном случае, если пользователь купит отключение рекламы или сделает пожертвование, то слушатель получит данные о покупке и выполнит нужные операции.
Теперь нужно создать экземпляр класса Invertory.
mCheckout = Checkout.forActivity(mainView.getActivity(), App.get().getBilling()); mCheckout.start(); mCheckout.createPurchaseFlow(new PurchaseListener()); Inventory mInventory = mCheckout.makeInventory(); mInventory.load( Inventory.Request.create().loadAllPurchases().loadSkus(ProductTypes.IN_APP, AD_FREE), new InventoryCallback());
Класс Invertory загружает данные о продуктах, SKU и покупках. Его жизненный цикл связан с жизненным циклом Checkout, в котором он был создан.
Метод makeInvertory() создаёт экземпляр Invertory и привязывает его к нужному объекту Checkout.
Метод load() отправляет запрос на получение данных о продуктах и асинхронно загружает результат в callback. В параметрах формируется запрос, какие именно продукты нужно получить (в данном случае, все имеющиеся, а именно донаты и отключение рекламы). Код коллбека, который принимает результат запроса, представлен ниже:
private class InventoryCallback implements Inventory.Callback { @Override public void onLoaded(@Nonnull Inventory.Products products) { final Inventory.Product product = products.get(ProductTypes.IN_APP); if (!product.supported) { Crashlytics.log(Log.ERROR, "MainPresenterImpl.InventoryCallback", "Billing is not supported, user can't purchase anything"); isBillingSupported = false; return; } List<Purchase> list = product.getPurchases(); if (mainView != null) { if (list.size() == 0) SP.setBoolean(mainView.getContext(), AD_FREE, false); if (product.getSku(AD_FREE) != null) { adFreePrice = product.getSku(AD_FREE).price; } if (product.isPurchased(AD_FREE)) { SP.setBoolean(mainView.getContext(), AD_FREE, true); adRemoved = true; Ads.getInstance().hideBanner(); } } } }
Метод onLoaded() вызывается, когда все данные загружены. В нём проверяются различные данные о продуктах. Например, можно проверить с помощью поля supported можно проверить, поддерживается ли продукт, а метод getSku() возвращает идентификатор продукта. Если нужно узнать стоимость продукта на основе локали устройства, то следует вызывать getSku(TYPE).price.
Метод isPurchased() проверяет, был ли продукт куплен пользователем. В случае с рекламой это будет означать, что её следует отключать.
Теперь нужно отправлять платёжные запросы сервису. Для этого в настройках приложения есть две кнопки «Удалить рекламу» и «Поддержать проект материально».
Обработка кнопки отключения рекламы выглядит следующим образом:
mCheckout.whenReady(new Checkout.EmptyListener() { @Override public void onReady(@NonNull BillingRequests requests) { requests.purchase(ProductTypes.IN_APP, AD_FREE, null, mCheckout.getPurchaseFlow()); Crashlytics.log(Log.INFO, "MainPresenterImpl.removeAds", "Ads was removed"); } });
С помощью данного метода формируется запрос на покупку продукта, результат которого будет получен коллбеком.
Аналогичным образом формируется запрос на донат.
mCheckout.whenReady(new Checkout.EmptyListener() { @Override public void onReady(@NonNull BillingRequests requests) { requests.purchase(ProductTypes.IN_APP, DONATE, null, mCheckout.getPurchaseFlow()); Crashlytics.log(Log.INFO, "MainPresenterImpl.buyDonate", "Got donate"); } });
Таким образом, с помощью библиотеки мы реализовали встроенные покупки в приложении без использования шаблонного кода.
А физ. лицо может зарегистрировать Google Wallet? Или нужно ИП открывать?
Может, в этом плене Google очень демократичная компания. Выплаты начнутся по достижении порога в 100$
А как тестировать покупки? Я прописал в play console тестового пользователя и через него покупаю например рекламу. Деньги не списываются но покупка числиться у него в google play.
Но в классе InventoryCallback mPurchases = 0.
Как правильно тестировать?
Здравствуйте! К сожалению, насчёт тестирования не получится что-либо подсказать, попробуйте написать автору библиотеки на гитхабе.
Про тестирование написано тут в начале статьи https://habrahabr.ru/post/313416/
Я не понимаю зачем писать инструкцию, в которой сначала написано как создать класс в андроид студии, а потом писать код, который непонятно куда вставлять. Если это инструкция для новичков, которые не умеют создавать классы, то почему не написано куда вставлять этот код »
mCheckout = Checkout.forActivity(mainView.getActivity(), App.get().getBilling());
mCheckout.start();
mCheckout.createPurchaseFlow(new PurchaseListener());
Inventory mInventory = mCheckout.makeInventory();
mInventory.load(
Inventory.Request.create().loadAllPurchases().loadSkus(ProductTypes.IN_APP, AD_FREE),
new InventoryCallback());, почему не написано что после его вставки студия будет ругаться cannot resolve symbol mainView и даже can not resolve metod get() и даже еще вот так createPurchaseFlow in UICheckout cannot be apply to PurchaseListener. Остальные классы так же все подчеркиваются красным.
Если же эта инструкция для проффесиионалов, которые сразу понимают как исправить ваш код, и что куда вставлять, то зачем в статье писать как создать класс а андроид студии?