WT1.1.5 In-app-purchase

This commit is contained in:
bossanyit
2021-01-24 14:34:13 +01:00
parent d44fefe9af
commit 71efd3f349
52 changed files with 847 additions and 367 deletions
+10
View File
@@ -8,6 +8,7 @@ import 'package:badges/badges.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:sentry/sentry.dart';
class DateRate {
static String daily = "daily";
@@ -130,4 +131,13 @@ mixin Common {
),
);
}
static Future<void> sendMessage(String message) async {
// Sends a full Sentry event payload to show the different parts of the UI.
await Sentry.captureMessage(
message,
level: SentryLevel.info,
template: 'Message: %s, customerId: ' + Cache().userLoggedIn.customerId.toString(),
);
}
}
+167 -98
View File
@@ -1,28 +1,29 @@
import 'dart:async';
import 'dart:io';
/*
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/product.dart';
import 'package:aitrainer_app/service/logging.dart';
import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
class PlatformPurchaseApi with Logging {
static final PlatformPurchaseApi _singleton = PlatformPurchaseApi._internal();
StreamSubscription _purchaseUpdatedSubscription;
StreamSubscription _purchaseErrorSubscription;
StreamSubscription _conectionSubscription;
String _platformVersion = 'Unknown';
List<IAPItem> _items = [];
List getIAPItems() => _items;
List<PurchasedItem> _purchases = [];
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;
}
@@ -30,55 +31,152 @@ class PlatformPurchaseApi with Logging {
PlatformPurchaseApi._internal();
Future<void> close() async {
if (_conectionSubscription != null) {
_conectionSubscription.cancel();
_conectionSubscription = null;
_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;
}
await FlutterInappPurchase.instance.endConnection;
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> initPurchasePlatformState() async {
Future<void> initPurchasePlatform() async {
if (_productList.length > 0) {
return;
}
log(' --- InappPurchase connection: $_connection');
log(" --- Init PurchasePlatform");
String platformVersion;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
platformVersion = await FlutterInappPurchase.instance.platformVersion;
} on Exception {
platformVersion = 'Failed to get platform version for InAppPurchase.';
log("PlatformVersion" + platformVersion);
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;
}
}
// prepare
var result = await FlutterInappPurchase.instance.initConnection;
log(' FlutterInappPurchase init result: $result');
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);
}
_platformVersion = platformVersion;
void _handleInvalidPurchase(PurchaseDetails purchaseDetails) {
// handle invalid purchase here if _verifyPurchase` failed.
log("Invalid Purchase" + purchaseDetails.toString());
}
// refresh items for android
try {
String msg = await FlutterInappPurchase.instance.consumeAllItems;
log('consumeAllItems: $msg');
} catch (err) {
log('consumeAllItems error: $err');
}
_conectionSubscription = FlutterInappPurchase.connectionUpdated.listen((connected) {
log('connected: $connected');
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);
}
}
});
_purchaseUpdatedSubscription = FlutterInappPurchase.purchaseUpdated.listen((productItem) {
log('purchase-updated: $productItem');
});
_purchaseErrorSubscription = FlutterInappPurchase.purchaseError.listen((purchaseError) {
log('purchase-error: $purchaseError');
});
getSubscriptions();
}
void getSubscriptions() {
@@ -98,7 +196,6 @@ class PlatformPurchaseApi with Logging {
_productList.forEach((element) {
print(element);
});
_getSubscription();
}
void purchase(Product product) {
@@ -106,57 +203,29 @@ class PlatformPurchaseApi with Logging {
throw Exception("No product to purchase");
}
String productId = Platform.isAndroid ? product.productIdAndroid : product.productIdIos;
IAPItem selected;
for (var item in _items) {
if (item.productId == productId) {
selected = item;
log("Item to purchase: " + item.toString());
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);
}
}
});
if (selected != null) {
requestPurchase(selected);
} else {
throw Exception("product " + productId + " not defined in the PlatformStore");
}
}
void requestPurchase(IAPItem item) {
//FlutterInappPurchase.instance.requestPurchase(item.productId);
FlutterInappPurchase.instance.requestPurchase(item.productId);
}
Future _getSubscription() async {
List<IAPItem> items = await FlutterInappPurchase.instance.getSubscriptions(_productList);
for (var item in items) {
log('${item.toString()}');
this._items.add(item);
}
this._items = items;
this._purchases = [];
}
Future _getPurchases() async {
List<PurchasedItem> items = await FlutterInappPurchase.instance.getAvailablePurchases();
for (var item in items) {
log('${item.toString()}');
this._purchases.add(item);
}
this._items = [];
this._purchases = items;
}
Future _getPurchaseHistory() async {
List<PurchasedItem> items = await FlutterInappPurchase.instance.getPurchaseHistory();
for (var item in items) {
log('${item.toString()}');
this._purchases.add(item);
}
this._items = [];
this._purchases = items;
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`.
}
}
*/
+120
View File
@@ -0,0 +1,120 @@
import 'dart:io';
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/service/logging.dart';
import 'package:aitrainer_app/util/common.dart';
import 'package:flutter/services.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
import 'package:aitrainer_app/model/product.dart' as wtproduct;
class RevenueCatPurchases with Logging {
static final RevenueCatPurchases _singleton = RevenueCatPurchases._internal();
Offering _offering;
String appUserId;
factory RevenueCatPurchases() {
return _singleton;
}
RevenueCatPurchases._internal();
Offering get offering => _offering;
Future<void> initPlatform() async {
if (Cache().userLoggedIn != null) {
await Purchases.setDebugLogsEnabled(true);
await Purchases.setup("yLxGFWaeRtThLImqznvBnUEKjgArSsZE", appUserId: Cache().userLoggedIn.customerId.toString());
appUserId = await Purchases.appUserID;
log("AppUserId: " + appUserId);
await this.restore();
}
}
Future<void> restore() async {
if (appUserId != null) {
try {
PurchaserInfo purchaserInfo = await Purchases.restoreTransactions();
if (purchaserInfo != null &&
purchaserInfo.entitlements.all["wt_subscription"] != null &&
purchaserInfo.entitlements.all["wt_subscription"].isActive) {
Cache().hasPurchased = true;
log(" ******************************************** ");
log(" Purchase active ! ");
} else {
log(" ** No subscription active");
}
} on PlatformException catch (e) {
log("Purchaserinfo not reachable " + e.toString());
}
}
}
Future<Offering> getOfferings() async {
if (appUserId == null) {
await initPlatform();
}
log(" .. acessing offerings...");
try {
Offerings offerings = await Purchases.getOfferings();
if (offerings.current != null && offerings.current.availablePackages.isNotEmpty) {
// Display packages for sale
_offering = offerings.current;
} else {
log("No current offerings");
Common.sendMessage("No Current offerings");
}
} on PlatformException catch (e) {
// optional error handling
log("!!!! Getting offerings error " + e.toString());
}
return _offering;
}
Future<void> makePurchase(wtproduct.Product product) async {
try {
if (_offering == null) {
_offering = await getOfferings();
}
if (_offering != null) {
String productId = Platform.isAndroid ? product.productIdAndroid : product.productIdIos;
Package selectedPackage;
log("Nr of packages: " + _offering.availablePackages.length.toString() + " ProductId: " + productId);
for (var package in _offering.availablePackages) {
log("package to check " + package.product.identifier.toString());
if (package.product.identifier == productId) {
selectedPackage = package;
log("**** Selected package to purchase" + package.product.identifier);
break;
}
}
if (selectedPackage != null) {
PurchaserInfo purchaserInfo = await Purchases.purchasePackage(selectedPackage);
if (purchaserInfo.entitlements.all["wt_subscription"].isActive) {
Cache().hasPurchased = true;
}
} else {
log("!!!! No Selected package to purchase");
Common.sendMessage("No Selected package to purchase");
throw Exception("Purchase was not successful");
}
} else {
log("!!!! No active offering");
}
} on PlatformException catch (e) {
var errorCode = PurchasesErrorHelper.getErrorCode(e);
if (errorCode == PurchasesErrorCode.invalidReceiptError) {
log("iOS Sandbox invalid receipt");
Cache().hasPurchased = true;
return;
}
log(e.toString());
if (errorCode == PurchasesErrorCode.purchaseCancelledError) {
Common.sendMessage("Purchase was cancelled");
throw Exception("Purchase was cancelled");
} else {
Common.sendMessage("Purchase was not successful");
throw Exception("Purchase was not successful");
}
}
}
}
+2
View File
@@ -6,6 +6,7 @@ import 'package:aitrainer_app/service/api.dart';
import 'package:aitrainer_app/service/logging.dart';
import 'package:aitrainer_app/service/product_service.dart';
import 'package:aitrainer_app/service/property_service.dart';
import 'package:aitrainer_app/util/purchases.dart';
import 'package:devicelocale/devicelocale.dart';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -28,6 +29,7 @@ class Session with Logging {
Cache().setServerAddress(_sharedPreferences);
Cache().getHardware(_sharedPreferences);
await _fetchToken(_sharedPreferences);
await RevenueCatPurchases().initPlatform();
//initDeviceLocale();
// Create the initialization Future outside of `build`: