232 lines
7.7 KiB
Dart
232 lines
7.7 KiB
Dart
/*
|
|
import 'package:aitrainer_app/model/cache.dart';
|
|
import 'package:aitrainer_app/model/product.dart';
|
|
import 'package:aitrainer_app/service/logging.dart';
|
|
import 'package:in_app_purchase/in_app_purchase.dart';
|
|
|
|
class PlatformPurchaseApi with Logging {
|
|
static final PlatformPurchaseApi _singleton = PlatformPurchaseApi._internal();
|
|
|
|
bool _kAutoConsume = true;
|
|
final String _kConsumableId = 'consumable';
|
|
final InAppPurchaseConnection _connection = InAppPurchaseConnection.instance;
|
|
bool _isAvailable = false;
|
|
List<ProductDetails> _products = [];
|
|
List<PurchaseDetails> _purchases = [];
|
|
List<String> _notFoundIds = [];
|
|
List<String> _consumables = [];
|
|
bool _purchasePending = false;
|
|
String _queryProductError;
|
|
|
|
StreamSubscription<List<PurchaseDetails>> _subscription;
|
|
|
|
final List<String> _productList = List();
|
|
|
|
List getProductList() => _productList;
|
|
|
|
factory PlatformPurchaseApi() {
|
|
return _singleton;
|
|
}
|
|
|
|
PlatformPurchaseApi._internal();
|
|
|
|
Future<void> close() async {
|
|
_subscription.cancel();
|
|
}
|
|
|
|
Future<void> initStoreInfo() async {
|
|
final bool isAvailable = await _connection.isAvailable();
|
|
if (!isAvailable) {
|
|
_isAvailable = isAvailable;
|
|
_products = [];
|
|
_purchases = [];
|
|
_notFoundIds = [];
|
|
_consumables = [];
|
|
_purchasePending = false;
|
|
log("Payment processor not available");
|
|
return;
|
|
}
|
|
|
|
getSubscriptions();
|
|
ProductDetailsResponse productDetailResponse = await _connection.queryProductDetails(_productList.toSet());
|
|
log("ProductDetailsResponse " + productDetailResponse.toString());
|
|
if (productDetailResponse.error != null) {
|
|
_queryProductError = productDetailResponse.error.message;
|
|
_isAvailable = isAvailable;
|
|
_products = productDetailResponse.productDetails;
|
|
_purchases = [];
|
|
_notFoundIds = productDetailResponse.notFoundIDs;
|
|
_consumables = [];
|
|
_purchasePending = false;
|
|
log("Payment processor not available");
|
|
return;
|
|
}
|
|
|
|
if (productDetailResponse.productDetails.isEmpty) {
|
|
_queryProductError = null;
|
|
_isAvailable = isAvailable;
|
|
_products = productDetailResponse.productDetails;
|
|
_purchases = [];
|
|
_notFoundIds = productDetailResponse.notFoundIDs;
|
|
_consumables = [];
|
|
_purchasePending = false;
|
|
|
|
return;
|
|
}
|
|
|
|
_products = productDetailResponse.productDetails;
|
|
_products.forEach((element) {
|
|
ProductDetails product = element as ProductDetails;
|
|
log("Product " + product.id + " " + product.price + " " + product.skuDetail.toString());
|
|
});
|
|
|
|
final QueryPurchaseDetailsResponse purchaseResponse = await _connection.queryPastPurchases();
|
|
if (purchaseResponse.error != null) {
|
|
// handle query past purchase error..
|
|
}
|
|
|
|
final List<PurchaseDetails> verifiedPurchases = [];
|
|
for (PurchaseDetails purchase in purchaseResponse.pastPurchases) {
|
|
if (await _verifyPurchase(purchase)) {
|
|
verifiedPurchases.add(purchase);
|
|
}
|
|
}
|
|
|
|
//TODO List<String> consumables = await ConsumableStore.load();
|
|
_isAvailable = isAvailable;
|
|
_products = productDetailResponse.productDetails;
|
|
_purchases = verifiedPurchases;
|
|
_notFoundIds = productDetailResponse.notFoundIDs;
|
|
//_consumables = consumables;
|
|
_purchasePending = false;
|
|
}
|
|
|
|
// Platform messages are asynchronous, so we initialize in an async method.
|
|
Future<void> initPurchasePlatform() async {
|
|
if (_productList.length > 0) {
|
|
return;
|
|
}
|
|
log(' --- InappPurchase connection: $_connection');
|
|
log(" --- Init PurchasePlatform");
|
|
await this.initStoreInfo();
|
|
|
|
Stream purchaseUpdated = InAppPurchaseConnection.instance.purchaseUpdatedStream;
|
|
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
|
|
_listenToPurchaseUpdated(purchaseDetailsList);
|
|
}, onDone: () {
|
|
_subscription.cancel();
|
|
}, onError: (error) {
|
|
// handle error here.
|
|
log("Error listen purchase updated " + error.toString());
|
|
});
|
|
}
|
|
|
|
void deliverProduct(PurchaseDetails purchaseDetails) async {
|
|
log("DeliverProduct");
|
|
// IMPORTANT!! Always verify a purchase purchase details before delivering the product.
|
|
if (purchaseDetails.productID == _kConsumableId) {
|
|
//TODO
|
|
//await ConsumableStore.save(purchaseDetails.purchaseID);
|
|
//List<String> consumables = await ConsumableStore.load();
|
|
|
|
_purchasePending = false;
|
|
//_consumables = consumables;
|
|
} else {
|
|
_purchases.add(purchaseDetails);
|
|
_purchasePending = false;
|
|
}
|
|
}
|
|
|
|
Future<bool> _verifyPurchase(PurchaseDetails purchaseDetails) {
|
|
log("verifyPurchase");
|
|
// IMPORTANT!! Always verify a purchase before delivering the product.
|
|
// For the purpose of an example, we directly return true.
|
|
log("Verifying Purchase" + purchaseDetails.toString());
|
|
return Future<bool>.value(true);
|
|
}
|
|
|
|
void _handleInvalidPurchase(PurchaseDetails purchaseDetails) {
|
|
// handle invalid purchase here if _verifyPurchase` failed.
|
|
log("Invalid Purchase" + purchaseDetails.toString());
|
|
}
|
|
|
|
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
|
|
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
|
|
if (purchaseDetails.status == PurchaseStatus.pending) {
|
|
//showPendingUI();
|
|
log("Purchase pending");
|
|
} else {
|
|
if (purchaseDetails.status == PurchaseStatus.error) {
|
|
//handleError(purchaseDetails.error);
|
|
} else if (purchaseDetails.status == PurchaseStatus.purchased) {
|
|
bool valid = await _verifyPurchase(purchaseDetails);
|
|
if (valid) {
|
|
deliverProduct(purchaseDetails);
|
|
} else {
|
|
_handleInvalidPurchase(purchaseDetails);
|
|
return;
|
|
}
|
|
}
|
|
if (Platform.isAndroid) {
|
|
if (!_kAutoConsume && purchaseDetails.productID == _kConsumableId) {
|
|
await InAppPurchaseConnection.instance.consumePurchase(purchaseDetails);
|
|
}
|
|
}
|
|
if (purchaseDetails.pendingCompletePurchase) {
|
|
await InAppPurchaseConnection.instance.completePurchase(purchaseDetails);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void getSubscriptions() {
|
|
Cache().products.forEach((element) {
|
|
Product product = element as Product;
|
|
if (Platform.isAndroid) {
|
|
if (product.productIdAndroid != null && product.productIdAndroid.isNotEmpty) {
|
|
_productList.add(product.productIdAndroid);
|
|
}
|
|
} else {
|
|
if (product.productIdIos != null && product.productIdIos.isNotEmpty) {
|
|
_productList.add(product.productIdIos);
|
|
}
|
|
}
|
|
});
|
|
|
|
_productList.forEach((element) {
|
|
print(element);
|
|
});
|
|
}
|
|
|
|
void purchase(Product product) {
|
|
if (product == null) {
|
|
throw Exception("No product to purchase");
|
|
}
|
|
String productId = Platform.isAndroid ? product.productIdAndroid : product.productIdIos;
|
|
ProductDetails productDetails;
|
|
_products.forEach((element) {
|
|
if (element.id == productId) {
|
|
productDetails = element;
|
|
log("product to purchase: " +
|
|
productDetails.id +
|
|
" title " +
|
|
productDetails.title +
|
|
" price " +
|
|
productDetails.price +
|
|
" period " +
|
|
productDetails.skuDetail.subscriptionPeriod);
|
|
}
|
|
});
|
|
|
|
final PurchaseParam purchaseParam = PurchaseParam(productDetails: productDetails);
|
|
/* if (_isConsumable(productDetails)) {
|
|
InAppPurchaseConnection.instance.buyConsumable(purchaseParam: purchaseParam);
|
|
} else { */
|
|
InAppPurchaseConnection.instance.buyNonConsumable(purchaseParam: purchaseParam);
|
|
//}
|
|
// From here the purchase flow will be handled by the underlying storefront.
|
|
// Updates will be delivered to the `InAppPurchaseConnection.instance.purchaseUpdatedStream`.
|
|
}
|
|
}
|
|
*/
|