the in_app_purchase is unavaliable when running into inital logic in flutter - flutter

Now I am using in_app_purchase to integrete with my app. This is my dependencies using:
in_app_purchase: 1.0.0
I follow the docs of example. When running into page and initial the store info I facing a problem. This is my full code I am using now:
import 'dart:async';
import 'package:cruise/src/models/pay/pay_model.dart';
import 'package:fish_redux/fish_redux.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'action.dart';
import 'state.dart';
Effect<PayState> buildEffect() {
return combineEffects(<Object, Effect<PayState>>{
Lifecycle.initState: _onInit,
});
}
const bool _kAutoConsume = true;
const String _kConsumableId = 'consumable';
const String _kUpgradeId = 'upgrade';
const String _kSilverSubscriptionId = 'subscription_silver';
const String _kGoldSubscriptionId = 'subscription_gold';
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
late StreamSubscription<List<PurchaseDetails>> _subscription;
String? _queryProductError;
const List<String> _kProductIds = <String>[
_kConsumableId,
_kUpgradeId,
_kSilverSubscriptionId,
_kGoldSubscriptionId,
];
Future _onInit(Action action, Context<PayState> ctx) async {
// https://pub.dev/packages/in_app_purchase
// https://joebirch.co/flutter/adding-in-app-purchases-to-flutter-apps/
final Stream<List<PurchaseDetails>> purchaseUpdated =
_inAppPurchase.purchaseStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
initStoreInfo(ctx);
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
//_showPendingUI();
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
//_handleError(purchaseDetails.error!);
} else if (purchaseDetails.status == PurchaseStatus.purchased ||
purchaseDetails.status == PurchaseStatus.restored) {}
if (purchaseDetails.pendingCompletePurchase) {
await InAppPurchase.instance.completePurchase(purchaseDetails);
}
}
});
}
Future<void> initStoreInfo(Context<PayState> ctx) async {
final bool isAvailable = await _inAppPurchase.isAvailable();
if (!isAvailable) {
PayModel payModel = PayModel(
isAvailable: isAvailable,
products: [],
purchases: [],
notFoundIds: [],
purchasePending: false,
loading: false);
ctx.dispatch(PayActionCreator.onUpdate(payModel));
return;
}
ProductDetailsResponse productDetailResponse = await _inAppPurchase.queryProductDetails(_kProductIds.toSet());
if (productDetailResponse.error != null) {
/*setState(() {
_queryProductError = productDetailResponse.error!.message;
_isAvailable = isAvailable;
_products = productDetailResponse.productDetails;
_purchases = [];
_notFoundIds = productDetailResponse.notFoundIDs;
_consumables = [];
_purchasePending = false;
_loading = false;
});*/
return;
}
if (productDetailResponse.productDetails.isEmpty) {
/*setState(() {
_queryProductError = null;
_isAvailable = isAvailable;
_products = productDetailResponse.productDetails;
_purchases = [];
_notFoundIds = productDetailResponse.notFoundIDs;
_consumables = [];
_purchasePending = false;
_loading = false;
});*/
return;
}
await _inAppPurchase.restorePurchases();
/*List<String> consumables = await ConsumableStore.load();
setState(() {
_isAvailable = isAvailable;
_products = productDetailResponse.productDetails;
_notFoundIds = productDetailResponse.notFoundIDs;
_consumables = consumables;
_purchasePending = false;
_loading = false;
});*/
}
what makes me confusing is that when running into this code _inAppPurchase.isAvailable(), shows the _inAppPurchase is unavaliable(return false). why the _inAppPurchase unavaliable? what situation may cause this problem? what should I do to make it avaliable? this is the full code with project context maybe help to understand what I am facing.
I already do:
add subscribe product in apple connect
add a sandbox user
add in app purchase capablilities in XCode
anywhere could see the log why the inAppPurchase is unavaliable? This is my in purchase config:

Related

Flutter QR-code scanner rapidly scan issue, how to control the second scan?

Does anyone know how to adjust the QR code scanner speed? since the second scan is so quick,
source example is down below, you can download it add and run it in your Flutter project to test!
What are the differenceis are in the controller.scannedDataStream.listen() function. I posted down below:
Barcode? result;
var qrText = "";
bool isValid = false;
bool isExpired = false;
QRViewController? controller;
controller.scannedDataStream.listen((scanData) async {
log(scanData.code);
if (!scanData.code.contains('?key=')) {
setState(() {
qrText = "This is not a correct code";
isValid = false;
});
} else {
var key_hash = scanData.code.split('?key=');
log('hash: ' + key_hash[1]);
await decrypt(key_hash[1]).then((String result) async {
if (result == "") {
log('result: ' + result);
await controller.stopCamera();
setState(() {
qrText = result;
isValid = true;
isExpired = true;
});
}
else {
log('result: ' + result);
await controller.pauseCamera();
await controller.stopCamera();
setState(() {
qrText = result;
isValid = true;
isExpired = false;
});
}
});
}
});
https://pub.dev/packages/qr_code_scanner/example

Flutter can't restore purchases

This was working already but I updated package to latest version:
https://pub.dev/packages/in_app_purchase
I can make the payment and check what I bought but after refresh it doesn't restore this. If I try to buy the same version again it says that I already own it:
Future<void> initStore() async {
final bool isAvailableTemp = await inAppPurchase.isAvailable();
if (!isAvailable) {
isAvailable = isAvailableTemp;
products = [];
purchases = [];
notFoundIds = [];
purchasePending = false;
loading = false;
return;
}
if (Platform.isIOS) {
final InAppPurchaseStoreKitPlatformAddition iosPlatformAddition =
inAppPurchase
.getPlatformAddition<InAppPurchaseStoreKitPlatformAddition>();
await iosPlatformAddition.setDelegate(PaymentQueueDelegate());
}
final ProductDetailsResponse productDetailResponse =
await inAppPurchase.queryProductDetails(_androidProductIds.toSet());
if (productDetailResponse.error != null) {
queryProductError = productDetailResponse.error!.message;
isAvailable = isAvailable;
products = productDetailResponse.productDetails;
purchases = <PurchaseDetails>[];
notFoundIds = productDetailResponse.notFoundIDs;
purchasePending = false;
loading = false;
return;
}
}
Future<List<ProductDetails>> getProducts() async {
if (products.isEmpty) {
final ProductDetailsResponse products =
await inAppPurchase.queryProductDetails(
Platform.isAndroid
? _androidProductIds.toSet()
: _iOSProductIds.toSet(),
);
if (products.productDetails.isNotEmpty) {
// double check the order of the products
if (Helpers.containsInStringIgnoreCase(
products.productDetails[0].title, "plus")) {
this.products.addAll(products.productDetails);
} else {
// pro comes first
this.products.add(products.productDetails[1]);
this.products.add(products.productDetails[0]);
}
}
}
return products;
}
Future<List<PurchaseDetails>> getPastPurchases(BuildContext context) async {
if (purchases.isEmpty) {
final Stream<List<PurchaseDetails>> purchaseUpdated =
inAppPurchase.purchaseStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
if (purchaseDetailsList.isEmpty) {
detectProVideo(context);
} else {
// this.purchases.addAll(purchaseDetailsList);
listenToPurchaseUpdated(context, purchaseDetailsList);
}
_subscription.cancel();
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
_subscription.cancel();
});
await inAppPurchase.restorePurchases();
if (Platform.isIOS) {
Map<String, PurchaseDetails> purchases =
Map.fromEntries(this.purchases.map((PurchaseDetails purchase) {
if (purchase.pendingCompletePurchase) {
inAppPurchase.completePurchase(purchase);
}
return MapEntry<String, PurchaseDetails>(
purchase.productID, purchase);
}));
if (purchases.isEmpty) {
Provider.of<AdState>(context, listen: false).toggleAds(context, true);
} else {
Provider.of<AdState>(context, listen: false)
.toggleAds(context, false);
}
}
}
return purchases;
}
So this is always true:
if (purchaseDetailsList.isEmpty) {
Testing locally on an Android emulator

Flutter in_app_purchase, show content after purchase

I take code from offical in_app_purchases documentation, purchases work correctly.
I have function that show paid content, and i need to run it after purchase done correctly, but i don't know where i need to put it, because i steel don't inderstand purchases 100% correctly.
this is my code
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
final String _productID = '1d7ea644f690ffa';
bool _available = true;
List<ProductDetails> _products = [];
List<PurchaseDetails> _purchases = [];
StreamSubscription<List<PurchaseDetails>>? _subscription;
#override
void initState() {
final Stream<List<PurchaseDetails>> purchaseUpdated = _inAppPurchase.purchaseStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
setState(() {
_purchases.addAll(purchaseDetailsList);
_listenToPurchaseUpdated(purchaseDetailsList);
});
}, onDone: () {
_subscription!.cancel();
}, onError: (error) {
_subscription!.cancel();
});
_initialize();
super.initState();
}
#override
void dispose() {
_subscription!.cancel();
super.dispose();
}
void _initialize() async {
_available = await _inAppPurchase.isAvailable();
List<ProductDetails> products = await _getProducts(
productIds: Set<String>.from(
[_productID],
),
);
setState(() {
_products = products;
});
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
switch (purchaseDetails.status) {
case PurchaseStatus.pending:
// _showPendingUI();
break;
case PurchaseStatus.purchased:
break;
case PurchaseStatus.restored:
// bool valid = await _verifyPurchase(purchaseDetails);
// if (!valid) {
// _handleInvalidPurchase(purchaseDetails);
// }
break;
case PurchaseStatus.error:
print(purchaseDetails.error!);
// _handleError(purchaseDetails.error!);
break;
default:
break;
}
if (purchaseDetails.pendingCompletePurchase) {
await _inAppPurchase.completePurchase(purchaseDetails);
CheckListModel.addPaidData(purchaseDetails.productID);
}
});
}
Future<List<ProductDetails>> _getProducts({required Set<String> productIds}) async {
ProductDetailsResponse response = await _inAppPurchase.queryProductDetails(productIds);
return response.productDetails;
}
void _subscribe({required ProductDetails product}) {
final PurchaseParam purchaseParam = PurchaseParam(productDetails: product);
_inAppPurchase.buyNonConsumable(
purchaseParam: purchaseParam,
);
}
_subscribe function start when user click on special button in ui
function that show paid contenct name is
CheckListModel.addPaidData(purchaseDetails.productID);
when function start it create file paid.paid in getApplicationDocumentsDirectory, if it doesn't exist, and add in it productID . That what must happen
Where i need to place this function?

How to use in_app_purchase using BLoC?

I'm trying to refactor my code a little bit, and I want to refactor payment logic using BLoC.
The problem is that I'm still getting an error that says:
emit was called after an event handler completed normally.
I only need information from state if the transaction is Pending, Complete or there is an error, so there is only true or false to show the CircularIndicator.
There is my try to implement Payment using BLoC.
_onStarted - starts subscription and is called once on initState
_buyProduct - is called when user hits the button
class PaymentBloc extends Bloc<PaymentEvent, PaymentState> {
static const bool _started = false;
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
Set<String> _kProductIds = {"mini_burger_4_99"};
List<ProductDetails> _products = [];
PaymentBloc() : super(PaymentInitial(_started)) {
on<PaymentStarted>(_onStarted);
on<PaymentCompletion>(_buyProduct);
}
StreamSubscription<List<PurchaseDetails>>? _subscription;
void _onStarted(PaymentStarted event, Emitter<PaymentState> emit) {
final Stream<List<PurchaseDetails>> purchaseUpdated =
_inAppPurchase.purchaseStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
emit(PaymentPending());
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
emit(PaymentError());
} else if (purchaseDetails.pendingCompletePurchase) {
await _inAppPurchase.completePurchase(purchaseDetails);
emit(PaymentComplete());
}
}
});
}, onDone: () {
_subscription?.cancel();
}, onError: (error) {
// handle error.
});
initStoreInfo();
}
Future<void> _buyProduct(
PaymentCompletion event, Emitter<PaymentState> emit) async {
var paymentWrapper = SKPaymentQueueWrapper();
var transactions = await paymentWrapper.transactions();
transactions.forEach((transaction) async {
await paymentWrapper.finishTransaction(transaction);
});
final PurchaseParam purchaseParam = PurchaseParam(
productDetails:
_products.firstWhere((product) => product.id == event.id));
await _inAppPurchase.buyConsumable(purchaseParam: purchaseParam);
}
#override
Future<void> close() {
_subscription?.cancel();
return super.close();
}
Future<void> initStoreInfo() async {
final bool isAvailable = await _inAppPurchase.isAvailable();
if (!isAvailable) {
_products = [];
return;
}
if (Platform.isIOS) {
var iosPlatformAddition = _inAppPurchase
.getPlatformAddition<InAppPurchaseIosPlatformAddition>();
await iosPlatformAddition.setDelegate(ExamplePaymentQueueDelegate());
}
ProductDetailsResponse productDetailResponse =
await _inAppPurchase.queryProductDetails(_kProductIds);
if (productDetailResponse.error != null) {
_products = productDetailResponse.productDetails;
return;
}
if (productDetailResponse.productDetails.isEmpty) {
_products = productDetailResponse.productDetails;
return;
}
_products = productDetailResponse.productDetails;
}
}
Thanks for any kind of help.
Matt

Flutter: How to set up Non-Consumable purchase?

I want to set up Non-Consumable purchase in my app. I found a lot of tutorials about Consumable purchases, but unfortunately I haven't found any articles about Non-Consumable.
I tried using in_app_purchase package, but it didn't work.
final String testIdAdvanced = 'advanced_training';
InAppPurchaseConnection _iap = InAppPurchaseConnection.instance;
bool _available = true;
List<ProductDetails> advancedProducts = [];
List<ProductDetails> advancedPurchases = [];
StreamSubscription _subscription;
void _initialize() async {
_available = await _iap.isAvailable();
if (_available) {
await _getProducts();
await _getPastPurchases();
}
}
Future<void> _getProducts() async {
Set<String> ids = Set.from([testIdAdvanced, 'test_a']);
ProductDetailsResponse response = await _iap.queryProductDetails(ids);
setState(() {
advancedProducts = response.productDetails;
});
}
// Gets previous purchases
Future<void> _getPastPurchases() async {
QueryPurchaseDetailsResponse response =
await _iap.queryPastPurchases();
for (PurchaseDetails purchase in response.pastPurchases) {
if (Platform.isIOS) {
InAppPurchaseConnection.instance.completePurchase(purchase);
}
}
setState(() {
advancedPurchases = response.pastPurchases.cast<ProductDetails>();
});
}
void _buyProduct(ProductDetails prod) {
final PurchaseParam purchaseParam = PurchaseParam(productDetails: prod);
_iap.buyNonConsumable(purchaseParam: purchaseParam);
}