Привет, меня зовут Алексей. Сегодня я расскажу про внедрение внутренних покупок в мобильное приложение на 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/
Добавить комментарий