Андрей Смирнов
Время чтения: ~13 мин.
Просмотров: 0

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

Немного о покупках внутри приложений

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

Проблема в том, что родители могут и не знать о наличии таких in-app purchase в очередной игре, которую дают на растерзание своему чаду. Поверьте, встроенные покупки поджидают пользователя именно в критичные моменты, когда для победы не хватает совсем чуть-чуть, и вот он — подарок от разработчика, с ним вы точно пройдёте уровень, над которым так долго пыхтели, и всего за $4.99. Взрослый человек сможет сдержаться, а вот ребёнок нажмёт даже не задумываясь.

iOS и Android используют стандартный способ ограничения доступа к покупкам внутри приложений. В качестве защиты служит PIN-код, который необходимо ввести дял подтверждения покупки. Это удобно и быстро.

iOS

Для того, чтобы включить PIN-код в iOS, необходимо зайти в «Настройки» системы. Здесь переходим в пункт «Основные».

IMG_1181-e1365500729988.png

Нам нужен раздел «Ограничения».

IMG_1182-e1365500756645.png

Система попросит создать пароль для ограничений, после чего необходимо найти в списке пункт «Встроенные покупки» и при необходимости запретить их.

IMG_1183-e1365500801323.png

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

IMG_1185-e1365500831339.png

Android

Для того, чтобы включить PIN-код в Android, необходимо зайти в настройки Google Play.

Screenshot_2013-04-09-13-27-31-e1365500859460.png

Находим опцию создания пароля для совершения покупок, после чего создаём сам пароль.

Screenshot_2013-04-09-12-02-11-e1365500923620.png

Остаётся включить соответствующую опцию «Требовать PIN для покупок».

Screenshot_2013-04-09-12-02-18-e1365500943190.png

Вот и всё.

Рано или поздно наступает момент, когда разработчику нужно задуматься о том, как монетизировать своё приложение, чтобы оно приносило доход. Есть различные бизнес-модели, с помощью которых можно этого достичь, однако наиболее популярной является использование рекламы в приложении. Одним из плюсов использования рекламы является то, что она хорошо сочетается с другой бизнес-моделью — покупками внутри приложения. Например, пользователь может заплатить некоторую сумму денег для того, чтобы отключить показ рекламы в приложении.

В этой статье мы рассмотрим, как можно реализовать встроенные покупки на примере своего приложения Менеджер паролей от Wi-Fi сетей.

Android_in-app_billing_purchase.png

Возможность покупок в приложениях реализована благодаря 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: можно это делать как вручную, так и используя сторонние библиотеки. Одной из таких библиотек является 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. Вы также можете добавить его вручную, добавив в файл манифеста следующую строчку перед элементом :

Создадим экземпляр класса Billing в Application, откуда затем будем брать его при необходимости. Если у вас нет класса Application в проекте, вы можете легко создать его. Для этого нужно добавить в файле AndroidManifest.xml в элемент атрибут android:name=».Имя класса», например:

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> { de 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();      }    }  }</purchase>

Класс PurchaseListener наследует от , который имеет методы 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> lict.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();        }      }    }  }</purchase>

Метод onLoaded() вызывается, когда все данные загружены. В нём проверяются различные данные о продуктах. Например, можно проверить с помощью поля supported можно проверить, поддерживается ли продукт, а метод getSku() возвращает идентификатор продукта. Если нужно узнать стоимость продукта на основе локали устройства, то следует вызывать getSku(TYPE).price.

Метод isPurchased() проверяет, был ли продукт куплен пользователем. В случае с рекламой это будет означать, что её следует отключать.

Теперь нужно отправлять платёжные запросы сервису. Для этого в настройках приложения есть две кнопки «Удалить рекламу» и «Поддержать проект материально».

device-2017-12-27-185110.png

Обработка кнопки отключения рекламы выглядит следующим образом:

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");    }  });

Таким образом, с помощью библиотеки мы реализовали встроенные покупки в приложении без использования шаблонного кода.

1*MEl6386c9aV-g7EG3-HrKw.png И как это до сих пор на Хабре нет статьи об этом? Не дело, надо исправлять. Есть 2 способа добавить In-App покупки в Android-приложение — старый и новый. До 2017 года все пользовались библиотекой от anjlab, но с июня 2017 года ситуация изменилась, Google выпустила собственную библиотеку для внутренних покупок и подписок — Play Billing Library. Сейчас последний считается стандартом. Play Billing Library это очень просто. Подключите зависимость.

implementation 'com.android.billingclient:billing:1.2'

Добавьте разрешение в манифесте.

Создайте инстанс BillingClient и начните соединение.

 private BillingClient mBillingClient; ... mBillingClient = BillingClient.newBuilder(this).setListener(new PurchasesUpdatedListener() {     @Override     public void onPurchasesUpdated(int responseCode, @Nullable List purchases) {         if (responseCode == BillingClient.BillingResponse.OK && purchases != null) {             //сюда мы попадем когда будет осуществлена покупка          }     } }).build(); mBillingClient.startConnection(new BillingClientStateListener() {     @Override     public void onBillingSetupFinished(@BillingClient.BillingResponse int billingResponseCode) {         if (billingResponseCode == BillingClient.BillingResponse.OK) {             //здесь мы можем запросить информацию о товарах и покупках          }     }      @Override     public void onBillingServiceDisconnected() {         //сюда мы попадем если что-то пойдет не так     } });

В метод onPurchasesUpdated() мы попадаем когда покупка осуществлена, в методе onBillingSetupFinished() можно запросить информацию о товарах и покупках. Запросить информацию о товарах. Поместите querySkuDetails() в onBillingSetupFinished().

 private Map<string> mlsMap = new HashMap<>(); private String mSkuId = "sku_id_1"; ... @Override public void onBillingSetupFinished(@BillingClient.BillingResponse int billingResponseCode) {     if (billingResponseCode == BillingClient.BillingResponse.OK) {         //здесь мы можем запросить информацию о товарах и покупках         querySkuDetails(); //запрос о товарах      } } ... private void querySkuDetails() {     SkuDetailsParams.Builder skuDetailsParamsBuilder = SkuDetailsParams.newBuilder();     List<string> s new ArrayList<>();     skuList.add(mSkuId);     skuDetailsParamsBuilder.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);     mBillingClient.querySkuDetailsAsync(skuDetailsParamsBuilder.build(), new SkuDetailsResponseListener() {         @Override         public void onSkuDetailsResponse(int responseCode, List<skudetails> skuDetailsLis     if (responseCode == 0) {                 for (SkuDetails skuDetails : skuDetailsList) {                     mSkuDetailsMap.put(skuDetails.getSku(), skuDetails);                 }             }         }     }); }</skudetails></string></string>

В коде вы могли заметить понятие SKU, что это? SKU — от английского Stock Keeping Unit (идентификатор товарной позиции). Теперь в mSkuDetailsMap у нас лежит вся информация о товарах (имя, описание, цена), зарегистрированных в Play Console данного приложения (об этом позже). Обратите внимание на эту строку skuList.add(mSkuId);, здесь мы добавили id товара из Play Console, перечислите здесь все товары, с которыми вы хотите взаимодействовать. У нас товар один —sku_id_1. Все готово к тому, чтобы выполнить запрос на покупку. Передаем id товара. Запустите этот метод, например, по клику на кнопку.

public void launchBilling(String skuId) {     BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()             .setSkuDetails(mSkuDetailsMap.get(skuId))             .build();     mBillingClient.launchBillingFlow(this, billingFlowParams); }

Теперь, запустив этот метод, вы увидите вот такое диалоговое окно (прим. картинки из Интернета).1*GMy6xh4083vU7c6SHng57g.png Теперь если пользователь купит товар — его ему надо предоставить. Добавьте метод payComplete() и осуществите в нем действия, предоставляющие доступ к купленному товару. Например, если пользователь покупал отключение рекламы, сделайте в этом методе так, чтобы реклама больше не показывалась.

... @Override public void onPurchasesUpdated(int responseCode, @Nullable List purchases) {     if (responseCode == BillingClient.BillingResponse.OK && purchases != null) {         //сюда мы попадем когда будет осуществлена покупка         payComplete();     } } ...

Все хорошо, но если пользователь перезапустит приложение, наша программа ничего не знает о покупках. Надо запросить информацию о них. Сделайте это в onBillingSetupFinished().

 @Override public void onBillingSetupFinished(@BillingClient.BillingResponse int billingResponseCode) {     if (billingResponseCode == BillingClient.BillingResponse.OK) {         //здесь мы можем запросить информацию о товарах и покупках         querySkuDetails(); //запрос о товарах         List purchasesList = queryPurchases(); //запрос о покупках          //если товар уже куплен, предоставить его пользователю         for (int i = 0; i < purchasesList.size(); i++) {             String purchaseId = purchasesList.get(i).getSku();             if(TextUtils.equals(mSkuId, purchaseId)) {                 payComplete();             }         }     } } ... private List queryPurchases() {     Purchase.PurchasesResult purchasesResult = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);     return purchasesResult.getPurchasesList(); } 

В purchasesList попадает список всех покупок, сделанных пользователем. Делаем проверку: если товар куплен — выполнить payComplete(). Готово. Осталось это приложение опубликовать в Play Console и добавить товары. Как добавить товар: Описание страницы приложения > Контент для продажи > Создать ограниченный контент.Примечание 1: Вы не сможете добавить товар пока не загрузите билд приложения в Play Console.Примечание 2: Чтобы увидеть диалоговое окно о покупке, вам надо загрузить билд в Play Console, добавить товар и подождать какое-то время (~30 минут — 1 час — 3 часа), пока товар обновится, только после этого появится диалоговое окно и можно будет осуществить покупку.Примечание 3: Ошибка Please fix the input params. SKU can’t be null — товар в Play Console еще не успел обновиться, подождите.Примечание 4: Вы можете столкнуться с ошибкой Error «Your transaction cannot be completed», в логах как response code 6 пока будете тестировать. По каким причинам это происходит мне точно неизвестно, но по моим наблюдениям это происходит после частых манипуляций с покупкой и возвратом товара. Чтобы это починить перейдите в меню банковских карт и передобавьте вашу карту. Как этого избежать? Добавьте ваш аккаунт в Play Console в качестве тестировщика и покупайте только с тестовой карточки.Демо на GitHubКупите мне кофе(Кстати, на Хабре работает система донейтов по кнопке под статьёй — прим. модератора).Используемые источники:

  • https://lifehacker.ru/kak-zashhitit-sebya-ot-vnezapnyx-trat-na-pokupki-vnutri-prilozhenij/
  • https://android-tools.ru/coding/vstraivanie-pokupok-v-prilozheniya/
  • https://habr.com/post/444072/

Рейтинг автора
5
Подборку подготовил
Андрей Ульянов
Наш эксперт
Написано статей
168
Ссылка на основную публикацию
Похожие публикации