mnemo_cards/lib/features/purchase/in_app_purchase.dart

258 lines
7.7 KiB
Dart
Raw Normal View History

2024-05-22 20:48:44 +00:00
import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
2024-08-06 21:33:11 +00:00
import 'package:in_app_purchase_android/in_app_purchase_android.dart';
2024-05-22 20:48:44 +00:00
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
2024-07-25 08:10:15 +00:00
import 'package:url_launcher/url_launcher_string.dart';
2024-05-22 20:48:44 +00:00
import 'package:webview_flutter/webview_flutter.dart';
import '../../di/injector.dart';
import '../../di/locator.dart';
2024-07-18 21:45:04 +00:00
import '../../flags.dart';
2024-05-22 20:48:44 +00:00
import '../../main.dart';
2024-05-28 21:25:51 +00:00
import '../analytics/analytics.dart';
2024-07-18 21:45:04 +00:00
import 'rustore/mnemo_rustore.dart';
2024-05-22 20:48:44 +00:00
enum StoreItemType {
ads,
subscription,
pack,
}
class InAppPurchaseService {
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
final Stream<List<PurchaseDetails>> storeSubscription =
InAppPurchase.instance.purchaseStream;
InAppPurchase get instance => _inAppPurchase;
2024-08-18 16:52:03 +00:00
// Future<bool> buySubscription(SubscriptionPlanDto dto) async {
// if (dto.paymentSystem == PaymentSystem.yookassa) {
// return false;
// }
//
// final subscriptionProducts = await _getSubscriptionStoreProduct(dto);
// ProductDetails? product;
// if (Platform.isAndroid && !RUSTORE_BUILD) {
// // final googlePlayProducts = subscriptionProducts.whereType<GooglePlayProductDetails>();
// product = subscriptionProducts.firstWhereOrNull(
// (p) => p.rawPrice == double.parse(dto.price),
// );
// } else {
// product = subscriptionProducts.first;
// }
// if (product == null) {
// Analytics.buySubscriptionError(
// dto,
// androidPaymentSystem,
// message: 'cant find plan with price ${dto.price}',
// data: {
// 'plans': subscriptionProducts.join(';'),
// },
// );
// return false;
// }
// final result = await _buySubscriptionInStore(product);
// return result;
// }
2024-08-06 21:33:11 +00:00
2024-05-22 20:48:44 +00:00
Future<bool> buyPack(CardPackBuyDto dto) async {
2024-05-28 21:25:51 +00:00
try {
2024-07-18 21:45:04 +00:00
if (RUSTORE_BUILD) {
2024-08-05 00:12:20 +00:00
// return RustorePurchaseService().buyPack(dto);
return false;
2024-07-18 21:45:04 +00:00
} else {
final pack = await _getPackStoreProduct(dto);
final result = await _buyItemInStore(pack.first);
return result;
}
2024-05-28 21:25:51 +00:00
} catch (e, s) {
log('err', error: e, stackTrace: s);
Analytics.buyError(
dto,
2024-07-18 21:45:04 +00:00
androidPaymentSystem,
2024-05-28 21:25:51 +00:00
data: {
'e': e.toString(),
's': s.toString(),
},
);
return false;
}
2024-05-22 20:48:44 +00:00
}
2024-07-18 21:45:04 +00:00
Future<List<ProductDetails>> _getPackStoreProduct(CardPackBuyDto dto) async {
2024-05-22 20:48:44 +00:00
final bool isAvailable = await _inAppPurchase.isAvailable();
if (!isAvailable) {
2024-05-28 21:25:51 +00:00
Analytics.buyError(
dto,
2024-07-18 21:45:04 +00:00
androidPaymentSystem,
2024-05-28 21:25:51 +00:00
message: 'in app purchase not available',
);
2024-05-22 20:48:44 +00:00
return [];
}
final ids = {
2024-08-06 21:33:11 +00:00
if (Platform.isAndroid) RUSTORE_BUILD ? dto.rustoreId : dto.googlePlayId,
2024-05-22 20:48:44 +00:00
if (Platform.isIOS) dto.appStoreId,
}.whereNotNull().toSet();
if (ids.isEmpty) {
2024-05-28 21:25:51 +00:00
Analytics.buyError(
dto,
2024-07-18 21:45:04 +00:00
androidPaymentSystem,
2024-05-28 21:25:51 +00:00
message: 'product id not set',
);
2024-05-22 20:48:44 +00:00
return [];
}
final ProductDetailsResponse productDetailResponse =
await _inAppPurchase.queryProductDetails(ids);
if (productDetailResponse.error != null ||
productDetailResponse.productDetails.isEmpty) {
2024-05-28 21:25:51 +00:00
Analytics.buyError(
dto,
2024-07-18 21:45:04 +00:00
androidPaymentSystem,
2024-05-28 21:25:51 +00:00
data: {
'msg': 'cant get product details ${productDetailResponse.error}',
'not_found': productDetailResponse.notFoundIDs.join(','),
},
);
2024-05-22 20:48:44 +00:00
return [];
}
return productDetailResponse.productDetails;
}
2024-08-18 16:52:03 +00:00
// Future<List<ProductDetails>> _getSubscriptionStoreProduct(
// SubscriptionPlanDto dto,
// ) async {
// final bool isAvailable = await _inAppPurchase.isAvailable();
// if (!isAvailable) {
// Analytics.buySubscriptionError(
// dto,
// androidPaymentSystem,
// message: 'in app purchase not available',
// );
// return [];
// }
// final ids = {
// if (Platform.isAndroid)
// if (RUSTORE_BUILD && dto.paymentSystem == PaymentSystem.rustore)
// dto.paymentId,
// if (!RUSTORE_BUILD && dto.paymentSystem == PaymentSystem.google)
// dto.googlePlaySubscriptionId,
// if (dto.paymentSystem == PaymentSystem.yookassa) dto.paymentId,
// if (Platform.isIOS && dto.paymentSystem == PaymentSystem.apple)
// dto.paymentId,
// }.whereNotNull().toSet();
// if (ids.isEmpty) {
// Analytics.buySubscriptionError(
// dto,
// androidPaymentSystem,
// message: 'product id not set',
// );
// return [];
// }
//
// final productDetailResponse = await _inAppPurchase.queryProductDetails(ids);
// if (productDetailResponse.error != null ||
// productDetailResponse.productDetails.isEmpty) {
// Analytics.buySubscriptionError(
// dto,
// androidPaymentSystem,
// data: {
// 'msg': 'cant get product details ${productDetailResponse.error}',
// 'not_found': productDetailResponse.notFoundIDs.join(','),
// },
// );
// return [];
// }
//
// return productDetailResponse.productDetails;
// }
2024-08-06 21:33:11 +00:00
2024-07-18 21:45:04 +00:00
Future<bool> _buyItemInStore(ProductDetails product) async {
2024-08-06 21:33:11 +00:00
final PurchaseParam purchaseParam = PurchaseParam(productDetails: product);
return InAppPurchase.instance.buyConsumable(purchaseParam: purchaseParam);
}
Future<bool> _buySubscriptionInStore(ProductDetails product) async {
2024-05-22 20:48:44 +00:00
final PurchaseParam purchaseParam = PurchaseParam(productDetails: product);
return InAppPurchase.instance
.buyNonConsumable(purchaseParam: purchaseParam);
}
Future<void> completePurchase(PurchaseDetails purchaseDetails) async {
await InAppPurchase.instance.completePurchase(purchaseDetails);
}
}
class PurchaseDetailsStreamSubscription {
final InAppPurchaseService inAppPurchaseService = getIt.get();
Future<void> onPurchased(PurchaseDetails purchaseDetails) async {
try {
2024-05-28 21:25:51 +00:00
Analytics.buyEvent(
null,
2024-07-18 21:45:04 +00:00
androidPaymentSystem,
2024-05-28 21:25:51 +00:00
message: 'onPurchased',
data: {
'status': purchaseDetails.status.name,
'product': purchaseDetails.productID,
'date': purchaseDetails.transactionDate,
},
);
2024-05-22 20:48:44 +00:00
final result = await locator.repository.checkPayment(
2024-08-18 16:52:03 +00:00
product: MnemoCardsProduct(
id: purchaseDetails.productID,
type: MnemoCardsProductType.unknown,
),
2024-08-03 22:37:10 +00:00
key: purchaseDetails.verificationData.serverVerificationData,
paymentSystem: androidPaymentSystem,
2024-05-22 20:48:44 +00:00
);
if (result) {
2024-05-28 21:25:51 +00:00
Analytics.buyComplete(
null,
2024-07-18 21:45:04 +00:00
androidPaymentSystem,
2024-05-28 21:25:51 +00:00
message: purchaseDetails.productID,
);
2024-05-22 20:48:44 +00:00
log('Purchased');
} else {
2024-05-28 21:25:51 +00:00
Analytics.buyError(
null,
PaymentSystem.yookassa,
message: 'payment not verified ${purchaseDetails.productID}',
);
2024-05-22 20:48:44 +00:00
throw Exception('Cant verify purchase');
}
} catch (e, s) {
log(e.toString(), stackTrace: s);
}
}
StreamSubscription<List<PurchaseDetails>>? _streamSubscription;
PurchaseDetailsStreamSubscription();
Future<void> init() async {
_streamSubscription = inAppPurchaseService.storeSubscription.listen(
(List<PurchaseDetails> events) {
Future.forEach(
events,
(PurchaseDetails purchaseDetails) async {
if (purchaseDetails.pendingCompletePurchase ||
purchaseDetails.status == PurchaseStatus.purchased) {
onPurchased(purchaseDetails);
}
},
);
},
);
}
void close() {
_streamSubscription?.cancel();
}
}