mnemo_cards/lib/features/purchase/google_purchase_handler.dart
2024-08-25 00:09:58 +03:00

206 lines
5.9 KiB
Dart

import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:in_app_purchase_android/billing_client_wrappers.dart';
import 'package:in_app_purchase_android/in_app_purchase_android.dart';
import 'package:mnemo_cards/features/analytics/analytics.dart';
import 'package:mnemo_cards/managers/repository/repository.dart';
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
import 'purchase_handler.dart';
class GooglePurchaseHandler implements PurchaseHandler {
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
final Repository _repository;
StreamSubscription? _subscription;
GooglePurchaseHandler(this._repository);
Future<void> initPurchase({
required String id,
required MnemoCardsProductType productType,
required String paymentId,
String? source,
}) async {
if (productType == MnemoCardsProductType.unknown) {
log('unknown product type');
return;
}
if (!await isAvailable) {
log('Google play payment not available');
return;
}
final productId = paymentId.split('/').first;
final subscriptionId = paymentId.split('/').last;
final products = await _getStoreProduct(productId);
if (products.isEmpty) {
Analytics.buyError(
null,
paymentSystem,
paymentId: paymentId,
id: id,
message: 'No products',
);
return;
}
switch (productType) {
case MnemoCardsProductType.pack:
final product = products.first;
await _buyInStore(product);
return;
case MnemoCardsProductType.subscription:
// choosing the right subscription plan
final subs = products.first.productDetails.subscriptionOfferDetails;
final index = subs?.indexWhere((s) => s.basePlanId == subscriptionId);
if (index == null) {
Analytics.buyError(
null,
paymentSystem,
paymentId: paymentId,
id: id,
message: 'Unknown subscription id',
);
return;
}
await _buyInStore(products[index]);
return;
case MnemoCardsProductType.discount:
return;
case MnemoCardsProductType.unknown:
Analytics.buyError(
null,
paymentSystem,
paymentId: paymentId,
id: id,
message: 'Unknown product type',
);
return;
}
}
Future<List<GooglePlayProductDetails>> _getStoreProduct(
String googlePlayId) async {
final productDetailResponse = await _inAppPurchase.queryProductDetails({
googlePlayId,
});
if (productDetailResponse.error != null ||
productDetailResponse.productDetails.isEmpty) {
Analytics.buyError(
null,
paymentSystem,
paymentId: googlePlayId,
data: {
'msg': 'cant get product details ${productDetailResponse.error}',
'not_found': productDetailResponse.notFoundIDs.join(','),
},
);
return [];
}
return productDetailResponse.productDetails.cast();
}
Future<bool> _buyInStore(ProductDetails product) async {
final PurchaseParam purchaseParam = PurchaseParam(productDetails: product);
product as GooglePlayProductDetails;
if (product.productDetails.productType == ProductType.inapp) {
final result = await InAppPurchase.instance.buyConsumable(
purchaseParam: purchaseParam,
);
return result;
} else {
final result = InAppPurchase.instance.buyNonConsumable(
purchaseParam: purchaseParam,
);
return result;
}
}
Future<void> completePurchase(PurchaseDetails purchaseDetails) async {
await InAppPurchase.instance.completePurchase(purchaseDetails);
}
Future<void> _onPurchased(PurchaseDetails purchaseDetails) async {
try {
final result = await checkPurchase(
product: MnemoCardsProduct(
id: purchaseDetails.productID,
type: MnemoCardsProductType.unknown,
),
paymentToken: purchaseDetails.verificationData.serverVerificationData,
);
if (result) {
Analytics.buyComplete(
null,
paymentSystem,
message: purchaseDetails.productID,
);
log('Purchased');
} else {
Analytics.buyError(
null,
PaymentSystem.yookassa,
message: 'payment not verified ${purchaseDetails.productID}',
);
}
} catch (e, s) {
log(e.toString(), stackTrace: s);
}
}
Future<bool> get isAvailable => _inAppPurchase.isAvailable();
@override
PaymentSystem get paymentSystem => PaymentSystem.google;
@override
Future<bool> checkPurchase({
required MnemoCardsProduct product,
required String paymentToken,
}) async {
final result = await _repository.checkPayment(
product: product,
key: paymentToken,
paymentSystem: paymentSystem,
);
return result;
}
Future<void> init() async {
if (Platform.isAndroid) {
_subscription = _inAppPurchase.purchaseStream.listen(
(List<PurchaseDetails> events) {
Future.forEach(
events,
(PurchaseDetails purchaseDetails) async {
if (purchaseDetails.pendingCompletePurchase ||
purchaseDetails.status == PurchaseStatus.purchased) {
Analytics.buyEvent(
null,
paymentSystem,
message: 'onPurchased',
data: {
'status': purchaseDetails.status.name,
'product': purchaseDetails.productID,
'date': purchaseDetails.transactionDate,
},
);
_onPurchased(purchaseDetails);
}
},
);
},
);
}
}
Future<void> dispose() async {
await _subscription?.cancel();
_subscription = null;
}
}