2024-08-18 16:52:03 +00:00
|
|
|
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;
|
2024-08-24 21:09:58 +00:00
|
|
|
case MnemoCardsProductType.discount:
|
|
|
|
|
return;
|
2024-08-18 16:52:03 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|