mnemo_cards/lib/features/purchase/in_app_purchase.dart
2024-08-18 20:02:36 +03:00

257 lines
7.7 KiB
Dart

import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:in_app_purchase_android/in_app_purchase_android.dart';
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:webview_flutter/webview_flutter.dart';
import '../../di/injector.dart';
import '../../di/locator.dart';
import '../../flags.dart';
import '../../main.dart';
import '../analytics/analytics.dart';
import 'rustore/mnemo_rustore.dart';
enum StoreItemType {
ads,
subscription,
pack,
}
class InAppPurchaseService {
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
final Stream<List<PurchaseDetails>> storeSubscription =
InAppPurchase.instance.purchaseStream;
InAppPurchase get instance => _inAppPurchase;
// 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;
// }
Future<bool> buyPack(CardPackBuyDto dto) async {
try {
if (RUSTORE_BUILD) {
// return RustorePurchaseService().buyPack(dto);
return false;
} else {
final pack = await _getPackStoreProduct(dto);
final result = await _buyItemInStore(pack.first);
return result;
}
} catch (e, s) {
log('err', error: e, stackTrace: s);
Analytics.buyError(
dto,
androidPaymentSystem,
data: {
'e': e.toString(),
's': s.toString(),
},
);
return false;
}
}
Future<List<ProductDetails>> _getPackStoreProduct(CardPackBuyDto dto) async {
final bool isAvailable = await _inAppPurchase.isAvailable();
if (!isAvailable) {
Analytics.buyError(
dto,
androidPaymentSystem,
message: 'in app purchase not available',
);
return [];
}
final ids = {
if (Platform.isAndroid) RUSTORE_BUILD ? dto.rustoreId : dto.googlePlayId,
if (Platform.isIOS) dto.appStoreId,
}.whereNotNull().toSet();
if (ids.isEmpty) {
Analytics.buyError(
dto,
androidPaymentSystem,
message: 'product id not set',
);
return [];
}
final ProductDetailsResponse productDetailResponse =
await _inAppPurchase.queryProductDetails(ids);
if (productDetailResponse.error != null ||
productDetailResponse.productDetails.isEmpty) {
Analytics.buyError(
dto,
androidPaymentSystem,
data: {
'msg': 'cant get product details ${productDetailResponse.error}',
'not_found': productDetailResponse.notFoundIDs.join(','),
},
);
return [];
}
return productDetailResponse.productDetails;
}
// 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;
// }
Future<bool> _buyItemInStore(ProductDetails product) async {
final PurchaseParam purchaseParam = PurchaseParam(productDetails: product);
return InAppPurchase.instance.buyConsumable(purchaseParam: purchaseParam);
}
Future<bool> _buySubscriptionInStore(ProductDetails product) async {
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 {
Analytics.buyEvent(
null,
androidPaymentSystem,
message: 'onPurchased',
data: {
'status': purchaseDetails.status.name,
'product': purchaseDetails.productID,
'date': purchaseDetails.transactionDate,
},
);
final result = await locator.repository.checkPayment(
product: MnemoCardsProduct(
id: purchaseDetails.productID,
type: MnemoCardsProductType.unknown,
),
key: purchaseDetails.verificationData.serverVerificationData,
paymentSystem: androidPaymentSystem,
);
if (result) {
Analytics.buyComplete(
null,
androidPaymentSystem,
message: purchaseDetails.productID,
);
log('Purchased');
} else {
Analytics.buyError(
null,
PaymentSystem.yookassa,
message: 'payment not verified ${purchaseDetails.productID}',
);
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();
}
}