I’m trying to set up a subscription system in my flutter application but I have a recurring error that I can’t fix.
My subscription window is well sheet with the different plans and when I click to buy the sandbox opens but when paying I always have the following error :
<code>***Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot finish a purchasing transaction' ***First throw call stack: (0x19af2269c 0x1981e32e4 0x19b01d408 0x1c84f7e48 0x10776fb24 0x10776d968 0x10777ee18 0x10777f100 0x107779440 0x10cf8c4c8 0x10c9f9794 0x109ce8a30 0x109cea71c 0x109cfade8 0x109cfa9a4 0x19aef60d4 0x19aef32f8 0x19aef2728 0x1e79971c4 0x19da541fc 0x19db02984 0x104c25174 0x1c158aec8) libc++abi: terminating due to uncaught exception of type NSException
</code>
<code>***Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot finish a purchasing transaction' ***First throw call stack: (0x19af2269c 0x1981e32e4 0x19b01d408 0x1c84f7e48 0x10776fb24 0x10776d968 0x10777ee18 0x10777f100 0x107779440 0x10cf8c4c8 0x10c9f9794 0x109ce8a30 0x109cea71c 0x109cfade8 0x109cfa9a4 0x19aef60d4 0x19aef32f8 0x19aef2728 0x1e79971c4 0x19da541fc 0x19db02984 0x104c25174 0x1c158aec8) libc++abi: terminating due to uncaught exception of type NSException
</code>
***Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot finish a purchasing transaction' ***First throw call stack: (0x19af2269c 0x1981e32e4 0x19b01d408 0x1c84f7e48 0x10776fb24 0x10776d968 0x10777ee18 0x10777f100 0x107779440 0x10cf8c4c8 0x10c9f9794 0x109ce8a30 0x109cea71c 0x109cfade8 0x109cfa9a4 0x19aef60d4 0x19aef32f8 0x19aef2728 0x1e79971c4 0x19da541fc 0x19db02984 0x104c25174 0x1c158aec8) libc++abi: terminating due to uncaught exception of type NSException
Here is my purchasing system
<code>class BillingService extends ChangeNotifier {
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
List<ProductDetails> _products = [];
final SupabaseClient _supabase = Supabase.instance.client;
StreamSubscription<List<PurchaseDetails>>? _subscription;
bool _isAvailable = false;
final Map<String, bool> _savingSubscriptions = {};
Set<String> _completedTransactions = {};
List<ProductDetails> get products => _products;
bool get isAvailable => _isAvailable;
final StreamController<PurchaseStatus> _purchaseStatusController = StreamController<PurchaseStatus>.broadcast();
Stream<PurchaseStatus> get purchaseStatusStream => _purchaseStatusController.stream;
BillingService() {
_initialize();
}
Future<void> _initialize() async {
_isAvailable = await _inAppPurchase.isAvailable();
if (_isAvailable) {
await _getProducts();
await clearTransactions();
_subscription = _inAppPurchase.purchaseStream.listen(_onPurchaseUpdate, onDone: _updateStreamOnDone, onError: _updateStreamOnError);
}
notifyListeners();
}
Future<void> clearTransactions() async {
final transactions = await SKPaymentQueueWrapper().transactions();
for (var transaction in transactions) {
if (transaction.transactionState == SKPaymentTransactionStateWrapper.purchased ||
transaction.transactionState == SKPaymentTransactionStateWrapper.failed ||
transaction.transactionState == SKPaymentTransactionStateWrapper.restored) {
await SKPaymentQueueWrapper().finishTransaction(transaction);
} else {
print("Transaction ${transaction.transactionIdentifier} dans l'état ${transaction.transactionState} - ne peut pas être terminée");
}
}
}
Future<void> _handlePendingPurchases(PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.purchased ||
purchaseDetails.status == PurchaseStatus.restored) {
if (purchaseDetails.pendingCompletePurchase &&
!_completedTransactions.contains(purchaseDetails.purchaseID)) {
try {
await Future.delayed(const Duration(seconds: 1)); // Ajout d'un court délai
await _saveSubscriptionToSupabase(purchaseDetails);
if (purchaseDetails.pendingCompletePurchase) {
await InAppPurchase.instance.completePurchase(purchaseDetails);
_completedTransactions.add(purchaseDetails.purchaseID!);
}
} catch (e) {
print("Erreur lors de la finalisation de la transaction : $e");
}
}
}
}
Future<void> _getProducts() async {
Set<String> ids = {GeneralVariables.storeKeyyYearly, GeneralVariables.storeKeyMonthly};
ProductDetailsResponse response = await _inAppPurchase.queryProductDetails(ids);
_products = response.productDetails;
notifyListeners();
}
void _onPurchaseUpdate(List<PurchaseDetails> purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
print("Mise à jour de l'achat - Status: ${purchaseDetails.status}");
print("Mise à jour de l'achat - ProductID: ${purchaseDetails.productID}");
print("Mise à jour de l'achat - PurchaseID: ${purchaseDetails.purchaseID}");
print("Mise à jour de l'achat - TransactionDate: ${purchaseDetails.transactionDate}");
print("Mise à jour de l'achat - PendingCompletePurchase: ${purchaseDetails.pendingCompletePurchase}");
switch (purchaseDetails.status) {
case PurchaseStatus.pending:
print("Achat en attente");
break;
case PurchaseStatus.error:
print("Erreur d'achat: ${purchaseDetails.error}");
await _handlePendingPurchases(purchaseDetails);
break;
case PurchaseStatus.purchased:
case PurchaseStatus.restored:
print("Achat réussi ou restauré");
await _handlePendingPurchases(purchaseDetails);
break;
case PurchaseStatus.canceled:
print("Achat annulé");
break;
}
});
}
Future<void> _saveSubscriptionToSupabase(PurchaseDetails purchaseDetails) async {
print("Début de _saveSubscriptionToSupabase pour ${purchaseDetails.purchaseID}");
final userId = _supabase.auth.currentUser?.id;
if (userId == null) {
print("User is not authenticated");
return;
}
// Vérifier si on est déjà en train de sauvegarder cet abonnement
if (_savingSubscriptions[purchaseDetails.purchaseID] == true) {
print("Sauvegarde déjà en cours pour cet abonnement");
return;
}
_savingSubscriptions[purchaseDetails.purchaseID!] = true;
try {
// Vérifier si un abonnement existe déjà
final existingSubscriptions = await _supabase
.from('subscriptions')
.select()
.eq('user_id', userId)
.eq('status', 'active');
if (existingSubscriptions.isNotEmpty) {
// Mettre à jour l'abonnement existant
await _supabase
.from('subscriptions')
.update({
'subscription_id': purchaseDetails.purchaseID,
'platform': defaultTargetPlatform == TargetPlatform.iOS ? 'apple' : 'google',
'start_date': DateTime.now().toIso8601String(),
'expiry_date': _calculateExpiryDate(purchaseDetails.productID).toIso8601String(),
})
.eq('user_id', userId)
.eq('status', 'active');
print("Abonnement existant mis à jour dans Supabase");
} else {
// Insérer un nouvel abonnement
final subscriptionData = {
'user_id': userId,
'subscription_id': purchaseDetails.purchaseID,
'platform': defaultTargetPlatform == TargetPlatform.iOS ? 'apple' : 'google',
'status': 'active',
'start_date': DateTime.now().toIso8601String(),
'expiry_date': _calculateExpiryDate(purchaseDetails.productID).toIso8601String(),
};
final response = await _supabase
.from('subscriptions')
.insert(subscriptionData);
if (response.error != null) {
print("Failed to save subscription to Supabase. Error: ${response.error!.message}");
} else {
print("Nouvel abonnement sauvegardé dans Supabase avec succès");
}
}
} catch (e) {
print("Exception while saving subscription to Supabase: $e");
} finally {
_savingSubscriptions[purchaseDetails.purchaseID!] = false;
}
print("Fin de _saveSubscriptionToSupabase pour ${purchaseDetails.purchaseID}");
}
DateTime _calculateExpiryDate(String productId) {
// Implement your logic to calculate expiry date based on the product ID
// For example:
if (productId == GeneralVariables.storeKeyyYearly) {
return DateTime.now().add(const Duration(days: 365));
} else if (productId == GeneralVariables.storeKeyMonthly) {
return DateTime.now().add(const Duration(days: 30));
}
// Default to 1 month if unknown
return DateTime.now().add(const Duration(days: 30));
}
void _updateStreamOnDone() {
_subscription?.cancel();
}
void _updateStreamOnError(dynamic error) {
// Handle error here
}
Future<void> buy(ProductDetails product) async {
try {
print("Préparation de l'achat");
await clearTransactions(); // Nettoyer les transactions en attente avant un nouvel achat
final purchaseParam = PurchaseParam(productDetails: product);
print("PurchaseParam créé : ${purchaseParam.productDetails.id}");
print("Tentative d'achat pour ${product.id}");
var transactions = await SKPaymentQueueWrapper().transactions();
transactions.forEach((skPaymentTransactionWrapper) {
SKPaymentQueueWrapper().finishTransaction(skPaymentTransactionWrapper);
});
final bool success = await _inAppPurchase.buyNonConsumable(purchaseParam: purchaseParam);
if (success) {
print("Achat initié avec succès pour ${product.id}");
} else {
print("L'achat n'a pas été initié pour ${product.id}");
throw Exception("L'achat n'a pas pu être initié");
}
} catch (e) {
print('Exception dans buy : $e');
if (e is SKError) {
print('SKError code: ${e.code}, message: ${e.domain}');
}
rethrow;
}
}
Future<void> restorePurchases() async {
await _inAppPurchase.restorePurchases();
}
@override
void dispose() {
_subscription?.cancel();
_purchaseStatusController.close();
super.dispose();
}
}
class ExamplePaymentQueueDelegate implements SKPaymentQueueDelegateWrapper {
@override
bool shouldContinueTransaction(SKPaymentTransactionWrapper transaction, SKStorefrontWrapper storefront) {
return true;
}
@override
bool shouldShowPriceConsent() {
return false;
}
}
</code>
<code>class BillingService extends ChangeNotifier {
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
List<ProductDetails> _products = [];
final SupabaseClient _supabase = Supabase.instance.client;
StreamSubscription<List<PurchaseDetails>>? _subscription;
bool _isAvailable = false;
final Map<String, bool> _savingSubscriptions = {};
Set<String> _completedTransactions = {};
List<ProductDetails> get products => _products;
bool get isAvailable => _isAvailable;
final StreamController<PurchaseStatus> _purchaseStatusController = StreamController<PurchaseStatus>.broadcast();
Stream<PurchaseStatus> get purchaseStatusStream => _purchaseStatusController.stream;
BillingService() {
_initialize();
}
Future<void> _initialize() async {
_isAvailable = await _inAppPurchase.isAvailable();
if (_isAvailable) {
await _getProducts();
await clearTransactions();
_subscription = _inAppPurchase.purchaseStream.listen(_onPurchaseUpdate, onDone: _updateStreamOnDone, onError: _updateStreamOnError);
}
notifyListeners();
}
Future<void> clearTransactions() async {
final transactions = await SKPaymentQueueWrapper().transactions();
for (var transaction in transactions) {
if (transaction.transactionState == SKPaymentTransactionStateWrapper.purchased ||
transaction.transactionState == SKPaymentTransactionStateWrapper.failed ||
transaction.transactionState == SKPaymentTransactionStateWrapper.restored) {
await SKPaymentQueueWrapper().finishTransaction(transaction);
} else {
print("Transaction ${transaction.transactionIdentifier} dans l'état ${transaction.transactionState} - ne peut pas être terminée");
}
}
}
Future<void> _handlePendingPurchases(PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.purchased ||
purchaseDetails.status == PurchaseStatus.restored) {
if (purchaseDetails.pendingCompletePurchase &&
!_completedTransactions.contains(purchaseDetails.purchaseID)) {
try {
await Future.delayed(const Duration(seconds: 1)); // Ajout d'un court délai
await _saveSubscriptionToSupabase(purchaseDetails);
if (purchaseDetails.pendingCompletePurchase) {
await InAppPurchase.instance.completePurchase(purchaseDetails);
_completedTransactions.add(purchaseDetails.purchaseID!);
}
} catch (e) {
print("Erreur lors de la finalisation de la transaction : $e");
}
}
}
}
Future<void> _getProducts() async {
Set<String> ids = {GeneralVariables.storeKeyyYearly, GeneralVariables.storeKeyMonthly};
ProductDetailsResponse response = await _inAppPurchase.queryProductDetails(ids);
_products = response.productDetails;
notifyListeners();
}
void _onPurchaseUpdate(List<PurchaseDetails> purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
print("Mise à jour de l'achat - Status: ${purchaseDetails.status}");
print("Mise à jour de l'achat - ProductID: ${purchaseDetails.productID}");
print("Mise à jour de l'achat - PurchaseID: ${purchaseDetails.purchaseID}");
print("Mise à jour de l'achat - TransactionDate: ${purchaseDetails.transactionDate}");
print("Mise à jour de l'achat - PendingCompletePurchase: ${purchaseDetails.pendingCompletePurchase}");
switch (purchaseDetails.status) {
case PurchaseStatus.pending:
print("Achat en attente");
break;
case PurchaseStatus.error:
print("Erreur d'achat: ${purchaseDetails.error}");
await _handlePendingPurchases(purchaseDetails);
break;
case PurchaseStatus.purchased:
case PurchaseStatus.restored:
print("Achat réussi ou restauré");
await _handlePendingPurchases(purchaseDetails);
break;
case PurchaseStatus.canceled:
print("Achat annulé");
break;
}
});
}
Future<void> _saveSubscriptionToSupabase(PurchaseDetails purchaseDetails) async {
print("Début de _saveSubscriptionToSupabase pour ${purchaseDetails.purchaseID}");
final userId = _supabase.auth.currentUser?.id;
if (userId == null) {
print("User is not authenticated");
return;
}
// Vérifier si on est déjà en train de sauvegarder cet abonnement
if (_savingSubscriptions[purchaseDetails.purchaseID] == true) {
print("Sauvegarde déjà en cours pour cet abonnement");
return;
}
_savingSubscriptions[purchaseDetails.purchaseID!] = true;
try {
// Vérifier si un abonnement existe déjà
final existingSubscriptions = await _supabase
.from('subscriptions')
.select()
.eq('user_id', userId)
.eq('status', 'active');
if (existingSubscriptions.isNotEmpty) {
// Mettre à jour l'abonnement existant
await _supabase
.from('subscriptions')
.update({
'subscription_id': purchaseDetails.purchaseID,
'platform': defaultTargetPlatform == TargetPlatform.iOS ? 'apple' : 'google',
'start_date': DateTime.now().toIso8601String(),
'expiry_date': _calculateExpiryDate(purchaseDetails.productID).toIso8601String(),
})
.eq('user_id', userId)
.eq('status', 'active');
print("Abonnement existant mis à jour dans Supabase");
} else {
// Insérer un nouvel abonnement
final subscriptionData = {
'user_id': userId,
'subscription_id': purchaseDetails.purchaseID,
'platform': defaultTargetPlatform == TargetPlatform.iOS ? 'apple' : 'google',
'status': 'active',
'start_date': DateTime.now().toIso8601String(),
'expiry_date': _calculateExpiryDate(purchaseDetails.productID).toIso8601String(),
};
final response = await _supabase
.from('subscriptions')
.insert(subscriptionData);
if (response.error != null) {
print("Failed to save subscription to Supabase. Error: ${response.error!.message}");
} else {
print("Nouvel abonnement sauvegardé dans Supabase avec succès");
}
}
} catch (e) {
print("Exception while saving subscription to Supabase: $e");
} finally {
_savingSubscriptions[purchaseDetails.purchaseID!] = false;
}
print("Fin de _saveSubscriptionToSupabase pour ${purchaseDetails.purchaseID}");
}
DateTime _calculateExpiryDate(String productId) {
// Implement your logic to calculate expiry date based on the product ID
// For example:
if (productId == GeneralVariables.storeKeyyYearly) {
return DateTime.now().add(const Duration(days: 365));
} else if (productId == GeneralVariables.storeKeyMonthly) {
return DateTime.now().add(const Duration(days: 30));
}
// Default to 1 month if unknown
return DateTime.now().add(const Duration(days: 30));
}
void _updateStreamOnDone() {
_subscription?.cancel();
}
void _updateStreamOnError(dynamic error) {
// Handle error here
}
Future<void> buy(ProductDetails product) async {
try {
print("Préparation de l'achat");
await clearTransactions(); // Nettoyer les transactions en attente avant un nouvel achat
final purchaseParam = PurchaseParam(productDetails: product);
print("PurchaseParam créé : ${purchaseParam.productDetails.id}");
print("Tentative d'achat pour ${product.id}");
var transactions = await SKPaymentQueueWrapper().transactions();
transactions.forEach((skPaymentTransactionWrapper) {
SKPaymentQueueWrapper().finishTransaction(skPaymentTransactionWrapper);
});
final bool success = await _inAppPurchase.buyNonConsumable(purchaseParam: purchaseParam);
if (success) {
print("Achat initié avec succès pour ${product.id}");
} else {
print("L'achat n'a pas été initié pour ${product.id}");
throw Exception("L'achat n'a pas pu être initié");
}
} catch (e) {
print('Exception dans buy : $e');
if (e is SKError) {
print('SKError code: ${e.code}, message: ${e.domain}');
}
rethrow;
}
}
Future<void> restorePurchases() async {
await _inAppPurchase.restorePurchases();
}
@override
void dispose() {
_subscription?.cancel();
_purchaseStatusController.close();
super.dispose();
}
}
class ExamplePaymentQueueDelegate implements SKPaymentQueueDelegateWrapper {
@override
bool shouldContinueTransaction(SKPaymentTransactionWrapper transaction, SKStorefrontWrapper storefront) {
return true;
}
@override
bool shouldShowPriceConsent() {
return false;
}
}
</code>
class BillingService extends ChangeNotifier {
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
List<ProductDetails> _products = [];
final SupabaseClient _supabase = Supabase.instance.client;
StreamSubscription<List<PurchaseDetails>>? _subscription;
bool _isAvailable = false;
final Map<String, bool> _savingSubscriptions = {};
Set<String> _completedTransactions = {};
List<ProductDetails> get products => _products;
bool get isAvailable => _isAvailable;
final StreamController<PurchaseStatus> _purchaseStatusController = StreamController<PurchaseStatus>.broadcast();
Stream<PurchaseStatus> get purchaseStatusStream => _purchaseStatusController.stream;
BillingService() {
_initialize();
}
Future<void> _initialize() async {
_isAvailable = await _inAppPurchase.isAvailable();
if (_isAvailable) {
await _getProducts();
await clearTransactions();
_subscription = _inAppPurchase.purchaseStream.listen(_onPurchaseUpdate, onDone: _updateStreamOnDone, onError: _updateStreamOnError);
}
notifyListeners();
}
Future<void> clearTransactions() async {
final transactions = await SKPaymentQueueWrapper().transactions();
for (var transaction in transactions) {
if (transaction.transactionState == SKPaymentTransactionStateWrapper.purchased ||
transaction.transactionState == SKPaymentTransactionStateWrapper.failed ||
transaction.transactionState == SKPaymentTransactionStateWrapper.restored) {
await SKPaymentQueueWrapper().finishTransaction(transaction);
} else {
print("Transaction ${transaction.transactionIdentifier} dans l'état ${transaction.transactionState} - ne peut pas être terminée");
}
}
}
Future<void> _handlePendingPurchases(PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.purchased ||
purchaseDetails.status == PurchaseStatus.restored) {
if (purchaseDetails.pendingCompletePurchase &&
!_completedTransactions.contains(purchaseDetails.purchaseID)) {
try {
await Future.delayed(const Duration(seconds: 1)); // Ajout d'un court délai
await _saveSubscriptionToSupabase(purchaseDetails);
if (purchaseDetails.pendingCompletePurchase) {
await InAppPurchase.instance.completePurchase(purchaseDetails);
_completedTransactions.add(purchaseDetails.purchaseID!);
}
} catch (e) {
print("Erreur lors de la finalisation de la transaction : $e");
}
}
}
}
Future<void> _getProducts() async {
Set<String> ids = {GeneralVariables.storeKeyyYearly, GeneralVariables.storeKeyMonthly};
ProductDetailsResponse response = await _inAppPurchase.queryProductDetails(ids);
_products = response.productDetails;
notifyListeners();
}
void _onPurchaseUpdate(List<PurchaseDetails> purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
print("Mise à jour de l'achat - Status: ${purchaseDetails.status}");
print("Mise à jour de l'achat - ProductID: ${purchaseDetails.productID}");
print("Mise à jour de l'achat - PurchaseID: ${purchaseDetails.purchaseID}");
print("Mise à jour de l'achat - TransactionDate: ${purchaseDetails.transactionDate}");
print("Mise à jour de l'achat - PendingCompletePurchase: ${purchaseDetails.pendingCompletePurchase}");
switch (purchaseDetails.status) {
case PurchaseStatus.pending:
print("Achat en attente");
break;
case PurchaseStatus.error:
print("Erreur d'achat: ${purchaseDetails.error}");
await _handlePendingPurchases(purchaseDetails);
break;
case PurchaseStatus.purchased:
case PurchaseStatus.restored:
print("Achat réussi ou restauré");
await _handlePendingPurchases(purchaseDetails);
break;
case PurchaseStatus.canceled:
print("Achat annulé");
break;
}
});
}
Future<void> _saveSubscriptionToSupabase(PurchaseDetails purchaseDetails) async {
print("Début de _saveSubscriptionToSupabase pour ${purchaseDetails.purchaseID}");
final userId = _supabase.auth.currentUser?.id;
if (userId == null) {
print("User is not authenticated");
return;
}
// Vérifier si on est déjà en train de sauvegarder cet abonnement
if (_savingSubscriptions[purchaseDetails.purchaseID] == true) {
print("Sauvegarde déjà en cours pour cet abonnement");
return;
}
_savingSubscriptions[purchaseDetails.purchaseID!] = true;
try {
// Vérifier si un abonnement existe déjà
final existingSubscriptions = await _supabase
.from('subscriptions')
.select()
.eq('user_id', userId)
.eq('status', 'active');
if (existingSubscriptions.isNotEmpty) {
// Mettre à jour l'abonnement existant
await _supabase
.from('subscriptions')
.update({
'subscription_id': purchaseDetails.purchaseID,
'platform': defaultTargetPlatform == TargetPlatform.iOS ? 'apple' : 'google',
'start_date': DateTime.now().toIso8601String(),
'expiry_date': _calculateExpiryDate(purchaseDetails.productID).toIso8601String(),
})
.eq('user_id', userId)
.eq('status', 'active');
print("Abonnement existant mis à jour dans Supabase");
} else {
// Insérer un nouvel abonnement
final subscriptionData = {
'user_id': userId,
'subscription_id': purchaseDetails.purchaseID,
'platform': defaultTargetPlatform == TargetPlatform.iOS ? 'apple' : 'google',
'status': 'active',
'start_date': DateTime.now().toIso8601String(),
'expiry_date': _calculateExpiryDate(purchaseDetails.productID).toIso8601String(),
};
final response = await _supabase
.from('subscriptions')
.insert(subscriptionData);
if (response.error != null) {
print("Failed to save subscription to Supabase. Error: ${response.error!.message}");
} else {
print("Nouvel abonnement sauvegardé dans Supabase avec succès");
}
}
} catch (e) {
print("Exception while saving subscription to Supabase: $e");
} finally {
_savingSubscriptions[purchaseDetails.purchaseID!] = false;
}
print("Fin de _saveSubscriptionToSupabase pour ${purchaseDetails.purchaseID}");
}
DateTime _calculateExpiryDate(String productId) {
// Implement your logic to calculate expiry date based on the product ID
// For example:
if (productId == GeneralVariables.storeKeyyYearly) {
return DateTime.now().add(const Duration(days: 365));
} else if (productId == GeneralVariables.storeKeyMonthly) {
return DateTime.now().add(const Duration(days: 30));
}
// Default to 1 month if unknown
return DateTime.now().add(const Duration(days: 30));
}
void _updateStreamOnDone() {
_subscription?.cancel();
}
void _updateStreamOnError(dynamic error) {
// Handle error here
}
Future<void> buy(ProductDetails product) async {
try {
print("Préparation de l'achat");
await clearTransactions(); // Nettoyer les transactions en attente avant un nouvel achat
final purchaseParam = PurchaseParam(productDetails: product);
print("PurchaseParam créé : ${purchaseParam.productDetails.id}");
print("Tentative d'achat pour ${product.id}");
var transactions = await SKPaymentQueueWrapper().transactions();
transactions.forEach((skPaymentTransactionWrapper) {
SKPaymentQueueWrapper().finishTransaction(skPaymentTransactionWrapper);
});
final bool success = await _inAppPurchase.buyNonConsumable(purchaseParam: purchaseParam);
if (success) {
print("Achat initié avec succès pour ${product.id}");
} else {
print("L'achat n'a pas été initié pour ${product.id}");
throw Exception("L'achat n'a pas pu être initié");
}
} catch (e) {
print('Exception dans buy : $e');
if (e is SKError) {
print('SKError code: ${e.code}, message: ${e.domain}');
}
rethrow;
}
}
Future<void> restorePurchases() async {
await _inAppPurchase.restorePurchases();
}
@override
void dispose() {
_subscription?.cancel();
_purchaseStatusController.close();
super.dispose();
}
}
class ExamplePaymentQueueDelegate implements SKPaymentQueueDelegateWrapper {
@override
bool shouldContinueTransaction(SKPaymentTransactionWrapper transaction, SKStorefrontWrapper storefront) {
return true;
}
@override
bool shouldShowPriceConsent() {
return false;
}
}
I need your help I understand the mistake but I can’t solve it