Flutter + In-App Purchases

от автора

Привет, меня зовут Алексей. Сегодня я расскажу про внедрение внутренних покупок в мобильное приложение на Flutter с помощью плагина In-App Purchase.

Сначала расскажу немного о самих предметах которые мы можем продавать чтоб потом не было вопросов.
Есть 3 типа платного контента:
1. Расходуемые предметы (монеты, кристаллы, патроны и тп).
2. Нерасходуемые предметы (аватары, костюмы, и тп. т.е. все то что можно купить только один раз).
3. Подписки (ну тут все и так понятно).

Настройка iOS

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

Первым делом идем в AppstoreConnect,   там в верхнем меню выбираем agreements(Соглашения) и принимаем соглашение о «Платных приложениях и покупках в них». п.с. соглашение может принять только владелец аккаунта!

Далее мы едем на страницу приложения в сторе и в боковом меню выбираем пункт In-App Purchases или Subscriptions в зависимости от того какой продукт мы хотим продавать.
Я буду показывать на примере монет.
Выбираем пункт In-App Purchases и в открывшемся окне нажимаем + и видим такую картину.

Type — это расходуемый или нерасходуемый материал
Name — название отображаемое именно в AppStoreConnect
Id — уникальный идентификатор продукта для этого приложения

Нажимаем создать и видим окно с нашим продуктом, теперь нам надо добавить цену и названия отображаемое в самом приложении.

По цене ничего сложного просто в этом поле из дропдауна выбираем нужную и все (если надо можно добавить скидку на определенный период нажав + в этом поле)

Теперь переводы и названия нажимаем добавить локализацию

Видим такое меню

Localization — ну собственно для какого языка перевод
Display Name — это поле мы потом получим в нашем приложении как название
Description — это поле мы потом получим в нашем приложении как описание продукта

Все далее нажимаем сохранить в верхнем правом углу и готово мы создали наш продукт!
Можем повтотрить все действия и добавить нужные нам предметы!

Настройка Android

Все тоже самое что и для iOS только проще )

Заходим на https://play.google.com/console/ выбираем нужное приложение далее в правом меню выбираем продукты->продукты в приложении и нажимаем создать новый продукт.

В открывшемся окне вводим ID нового продукта, название ,описание, и устанавливаем цену.

Все! На этом со сторами мы закончили.

Настройка самого приложения

Для начала установим пакет для платежей

flutter pub add in_app_purchase

Далее создадим сервис который будет обрабатывать все наши манипуляции со стором
и добавим в него все необходимое.

class InAppPurchaseService {   final InAppPurchase _inAppPurchase = InAppPurchase.instance;   final Stream<List<PurchaseDetails>> _storeSubscription =       InAppPurchase.instance.purchaseStream;    InAppPurchase get instance => _inAppPurchase; }

Также нам нужно создать списки с айдиншиками наших объектов в сторе чтобы запрашивать их из стора.

 static const Set<String> coins = {     '70_coins',     '350_coins',     '700_coins',     '1400_coins',     '3500_coins',     '7000_coins',     '17500_coins',   };

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

Future<List<ProductDetails>> getProductsByType(StoreItemType type) async {     final Set<String> productsIds;      switch (type) {       case StoreItemType.coins:         productsIds = StoreProductsIds.coins;         break;       case StoreItemType.subscription:         productsIds = StoreProductsIds.subscriptions;         break;     }      final bool isAvailable = await _inAppPurchase.isAvailable();     if (!isAvailable) {       return [];     }      final ProductDetailsResponse productDetailResponse =         await _inAppPurchase.queryProductDetails(productsIds);      if (productDetailResponse.error != null ||         productDetailResponse.productDetails.isEmpty) {       return [];     }      return productDetailResponse.productDetails;   }

После вызова этого метода мы получим список продуктов в таком объекте.

class ProductDetails {   /// Creates a new product details object with the provided details.   ProductDetails({     required this.id,     required this.title,     required this.description,     required this.price,     required this.rawPrice,     required this.currencyCode,     this.currencySymbol = '',   });    /// The identifier of the product.   ///   /// For example, on iOS it is specified in App Store Connect; on Android, it is specified in Google Play Console.   final String id;    /// The title of the product.   ///   /// For example, on iOS it is specified in App Store Connect; on Android, it is specified in Google Play Console.   final String title;    /// The description of the product.   ///   /// For example, on iOS it is specified in App Store Connect; on Android, it is specified in Google Play Console.   final String description;    /// The price of the product, formatted with currency symbol ("$0.99").   ///   /// For example, on iOS it is specified in App Store Connect; on Android, it is specified in Google Play Console.   final String price;    /// The unformatted price of the product, specified in the App Store Connect or Sku in Google Play console based on the platform.   /// The currency unit for this value can be found in the [currencyCode] property.   /// The value always describes full units of the currency. (e.g. 2.45 in the case of $2.45)   final double rawPrice;    /// The currency code for the price of the product.   /// Based on the price specified in the App Store Connect or Sku in Google Play console based on the platform.   final String currencyCode;    /// The currency symbol for the locale, e.g. $ for US locale.   ///   /// When the currency symbol cannot be determined, the ISO 4217 currency code is returned.   final String currencySymbol; }

Так продукты получить получили, но надо теперь как-то их купить.

Тут я использую класс прослойку для хендлинга всех движений со стором. Дело вот в чем после вызова метода о покупке нам надо завершить покупку после. проверки подлинности платежа на бэке! Если мы не завершим покупку деньги вернуться покупателю через 3 дня. И транзакция будет считаться отмененной.

Для этого мы создаем класс который хэндлит все стейты платежей:

 class PurchaseDetailsStreamSubscription {   final InAppPurchaseService inAppPurchaseService = Get.find<InAppPurchaseService>();   final Function()? onPending;   final Function(PurchaseDetails purchaseDetails)? onPurchased;   final Function()? onError;   final Function()? onRestored;   final Function()? onCanceled;    StreamSubscription<List<PurchaseDetails>>? _streamSubscription;   PurchaseDetailsStreamSubscription({     this.onPending,     this.onPurchased,     this.onError,     this.onRestored,     this.onCanceled,   });    Future<void> init() async {     _streamSubscription = inAppPurchaseService.getStoreSubscription().listen(       (List<PurchaseDetails> events) {         Future.forEach(           events,           (PurchaseDetails purchaseDetails) async {             if (purchaseDetails.pendingCompletePurchase) {               await inAppPurchaseService.completePurchase(purchaseDetails);             }             switch (purchaseDetails.status) {               case PurchaseStatus.pending:                 onPending?.call();                 break;               case PurchaseStatus.purchased:                 onPurchased?.call(purchaseDetails);                 break;               case PurchaseStatus.error:                 onError?.call();                 break;               case PurchaseStatus.restored:                 onRestored?.call();                 break;               case PurchaseStatus.canceled:                 onCanceled?.call();                 break;             }           },         );       },     );   }    void close() {     _streamSubscription?.cancel();   } } 

Далее создаем еще два метода в нашем сервисе для покупок.

Future<bool> buyItemInStore(ProductDetails product) async {     final PurchaseParam purchaseParam = PurchaseParam(productDetails: product);     return InAppPurchase.instance.buyConsumable(purchaseParam: purchaseParam);   }    Future<void> completePurchase(PurchaseDetails purchaseDetails) async {     await InAppPurchase.instance.completePurchase(purchaseDetails);   }

Первый как понятно инициализирует покупку на стороне платформы, второй завершает покупку.

Далее в BloC или где вам там удобнее инициализируем наш класс прослойку и передаем в него такое

purchaseDetailsStreamSubscription = PurchaseDetailsStreamSubscription(       onCanceled: closeLoader,       onError: closeLoader,       onPurchased: (PurchaseDetails purchaseDetails) async {         closeLoader();         try {           // тут мы проверяем наш платеж на стороне сервера передав данные о платеже ,            // и если все ок завершаем  покупку           final bool res = await transactionRepository               .createTransaction(purchaseDetails.verificationData.serverVerificationData);           if (res) {             await inAppPurchaseService.completePurchase(purchaseDetails);             await updateBalance();           }         } catch (_, __) {}       },     )..init();

Все можем пробовать совершить покупку просто вызвав этот метод!

inAppPurchaseService.buyItemInStore(item);

С подписками история такая же история, а вот с не расходуемыми продуктам надо вызывать метод .

buyNonConsumable

Как-то так! Всем спасибо за внимание!

P.S. если что не так, поправьте плиз в комментах!) спасибо)


ссылка на оригинал статьи https://habr.com/ru/post/709400/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *