У меня есть проблема с тем, что iOS в покупке приложения продолжает вызывать метод восстановления в приложениях. Когда я инициализирую виджет, он восстанавливает, а затем вхожу в поток. Кроме того, когда начинается поток оформления заказа, он входит в поток из -за восстановления. Я просто очень запутался, почему это происходит. Есть ли что -то, что это сделать, это не полностью удаляет подписку, но все же ожидает, если в будущем она будет пересмотрена, даже если она давно отменена? Я ожидаю, что статус купленного, но я не получаю этого. Я хочу выслушать восстановленное и приобретенное состояние после того, как подписка приобретается или изменена, но сейчас я не могу этого сделать, из -за инициализации, которую она выполняет, прежде чем даже покупать или изменить подписку в потоке покупки. А также в каком случае, когда покупка восстановлена и при покупке? < /P>
Это мой код прямо сейчас: < /p>
import 'package:beabuddy_flutter/utils/device_util.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:in_app_purchase_android/billing_client_wrappers.dart';
import 'package:onepref/onepref.dart';
import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart';
import 'package:in_app_purchase_storekit/store_kit_wrappers.dart';
class BuyChatsBottomSheet extends StatefulWidget {
const BuyChatsBottomSheet({super.key});
@override
State createState() => _BuyChatsBottomSheetState();
}
class _BuyChatsBottomSheetState extends State {
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
late final Stream _purchaseStream;
late final StreamSubscription
_purchaseStreamSubscription;
final Set _processedTokens = {};
late final ChatCreditsBloc _chatCreditsBloc;
String _selectedProduct = proProductId;
late var currentSubscription = const SubscriptionResponseModel(
purchaseToken: '',
productId: '',
identifier: '',
platform: '',
);
late final List _products = [];
final List _productsIds = [
ProductId(id: proProductId, isConsumable: false),
ProductId(id: proPlusProductId, isConsumable: false),
];
PurchaseDetails? oldPurchaseDetails;
@override
void initState() {
super.initState();
_initializeState();
}
void _initializeState() async {
_chatCreditsBloc = context.read();
await _clearAllTransactions(); // Add this first
final deviceId = await _getDeviceId();
if (deviceId != null && mounted) {
context
.read()
.add(SubscriptionEvent.getSubscription(deviceId: deviceId));
}
_purchaseStream = InAppPurchase.instance.purchaseStream;
_purchaseStreamSubscription = _purchaseStream.listen((purchaseDetailsList) {
if (purchaseDetailsList.isNotEmpty &&
purchaseDetailsList.first.status != PurchaseStatus.canceled &&
purchaseDetailsList.first.status != PurchaseStatus.error) {
_listenToPurchaseUpdated(purchaseDetailsList);
if (Platform.isAndroid) {
oldPurchaseDetails = purchaseDetailsList.first;
}
}
}, onDone: () {
debugPrint('Stream is done');
}, onError: (error) {
debugPrint('Stream error: $error');
});
getProducts();
if (Platform.isAndroid) {
await InAppPurchase.instance.restorePurchases();
}
}
Future _clearAllTransactions() async {
if (!Platform.isIOS) return;
final queue = SKPaymentQueueWrapper();
final transactions = await queue.transactions();
for (final tx in transactions) {
await queue.finishTransaction(tx);
debugPrint('Cleared transaction: ${tx.payment.productIdentifier}');
}
}
@override
void dispose() {
_purchaseStreamSubscription.cancel();
super.dispose();
}
void _onClickCheckout() async {
if (Platform.isAndroid) {
await InAppPurchase.instance.restorePurchases().whenComplete(() {
Future.delayed(const Duration(seconds: 1));
});
}
final selectedProduct =
_products.firstWhere((p) => p.id == _selectedProduct);
PurchaseParam purchaseParam;
if (Platform.isAndroid && oldPurchaseDetails != null) {
purchaseParam = GooglePlayPurchaseParam(
productDetails: selectedProduct,
changeSubscriptionParam: ChangeSubscriptionParam(
oldPurchaseDetails: oldPurchaseDetails as GooglePlayPurchaseDetails,
replacementMode: ReplacementMode.deferred,
),
);
} else {
// iOS or fresh Android subscription
purchaseParam = PurchaseParam(productDetails: selectedProduct);
}
if (Platform.isIOS) {
final queue = SKPaymentQueueWrapper();
final transactions = await queue.transactions();
for (final tx in transactions) {
if (tx.transactionState != SKPaymentTransactionStateWrapper.purchased)
continue;
await queue.finishTransaction(tx);
}
}
await _inAppPurchase.buyNonConsumable(purchaseParam: purchaseParam);
}
bool _isVerifyingPurchase = false;
Future _verifyPurchase(PurchaseDetails purchaseDetails) async {
String purchaseToken = "";
if (Platform.isAndroid) {
purchaseToken = purchaseDetails.verificationData.serverVerificationData;
}
if (Platform.isIOS) {
// For iOS, we need the full App Store receipt
purchaseToken = purchaseDetails.verificationData.localVerificationData;
}
// Check if this purchase has already been processed or if verification is in progress
if (_processedTokens.contains(purchaseToken) || _isVerifyingPurchase) {
return;
}
// Set the lock to prevent other verifications from running
_isVerifyingPurchase = true;
try {
// Mark this token as processed to prevent duplicates
_processedTokens.add(purchaseToken);
final deviceId = await _getDeviceId();
// Fire the verification event in the bloc
_chatCreditsBloc.add(ChatCreditsEvent.verifyPurchase(
platform: DeviceUtil.getPlatform(),
purchaseToken: purchaseToken,
productId: purchaseDetails.productID,
deviceId: deviceId!,
));
await _inAppPurchase.completePurchase(purchaseDetails);
} catch (error) {
debugPrint("Error during purchase verification: $error");
} finally {
// Release the lock after verification is complete
_isVerifyingPurchase = false;
}
}
void _listenToPurchaseUpdated(
List purchaseDetailsList) async {
for (var purchaseDetails in purchaseDetailsList) {
if (purchaseDetails.status == PurchaseStatus.purchased) {
await _verifyPurchase(purchaseDetails);
}
}
}
void _listenToChanges(BuildContext context, ChatCreditsState state) {
state.whenOrNull(
purchaseVerified: (credits, lastPurchasedCredits) {
Navigator.pop(context);
context.go(
'/payment-successful/${credits.credits}/${credits.hasUnlimitedCredits}/${credits.hasUpdatedSubscription}?ts=${DateTime.now().millisecondsSinceEpoch}:${credits.hasUpdatedSubscription}',
);
},
);
}
Future _getDeviceId() async {
var deviceInfo = DeviceInfoPlugin();
if (Platform.isIOS) {
var iosDeviceInfo = await deviceInfo.iosInfo;
return iosDeviceInfo.identifierForVendor;
} else {
var androidDeviceInfo = await deviceInfo.androidInfo;
return androidDeviceInfo.id;
}
}
void getProducts() async {
if (await InAppPurchase.instance.isAvailable()) {
final productResponse = await InAppPurchase.instance.queryProductDetails(
_productsIds.map((product) => product.id).toSet(),
);
setState(() {
_products.addAll(productResponse.productDetails);
});
if (productResponse.notFoundIDs.isNotEmpty) {
debugPrint("Products not found: ${productResponse.notFoundIDs}");
}
} else {
debugPrint("In-app purchase is not available");
}
}
< /code>
Я попытался завершить транзакции до оформления заказа, и когда виджет инициализируется, но я не думаю, что это много. Я также долго ждал после того, как отменил свою подписку в среде песочницы, а затем снова подпишился, но также вместо того, чтобы дать мне приобретенную статус, он дает мне статус восстановления (как будто она все еще держит старую подписку еще долго после отмены).
Подробнее здесь: https://stackoverflow.com/questions/796 ... purchase-f
При покупке приложения iOS продолжает извлекать восстановленную покупку, когда инициализируется поток виджета или покупк ⇐ IOS
Программируем под IOS
1753389293
Anonymous
У меня есть проблема с тем, что iOS в покупке приложения продолжает вызывать метод восстановления в приложениях. Когда я инициализирую виджет, он восстанавливает, а затем вхожу в поток. Кроме того, когда начинается поток оформления заказа, он входит в поток из -за восстановления. Я просто очень запутался, почему это происходит. Есть ли что -то, что это сделать, это не полностью удаляет подписку, но все же ожидает, если в будущем она будет пересмотрена, даже если она давно отменена? Я ожидаю, что статус купленного, но я не получаю этого. Я хочу выслушать восстановленное и приобретенное состояние после того, как подписка приобретается или изменена, но сейчас я не могу этого сделать, из -за инициализации, которую она выполняет, прежде чем даже покупать или изменить подписку в потоке покупки. А также в каком случае, когда покупка восстановлена и при покупке? < /P>
Это мой код прямо сейчас: < /p>
import 'package:beabuddy_flutter/utils/device_util.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:in_app_purchase_android/billing_client_wrappers.dart';
import 'package:onepref/onepref.dart';
import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart';
import 'package:in_app_purchase_storekit/store_kit_wrappers.dart';
class BuyChatsBottomSheet extends StatefulWidget {
const BuyChatsBottomSheet({super.key});
@override
State createState() => _BuyChatsBottomSheetState();
}
class _BuyChatsBottomSheetState extends State {
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
late final Stream _purchaseStream;
late final StreamSubscription
_purchaseStreamSubscription;
final Set _processedTokens = {};
late final ChatCreditsBloc _chatCreditsBloc;
String _selectedProduct = proProductId;
late var currentSubscription = const SubscriptionResponseModel(
purchaseToken: '',
productId: '',
identifier: '',
platform: '',
);
late final List _products = [];
final List _productsIds = [
ProductId(id: proProductId, isConsumable: false),
ProductId(id: proPlusProductId, isConsumable: false),
];
PurchaseDetails? oldPurchaseDetails;
@override
void initState() {
super.initState();
_initializeState();
}
void _initializeState() async {
_chatCreditsBloc = context.read();
await _clearAllTransactions(); // Add this first
final deviceId = await _getDeviceId();
if (deviceId != null && mounted) {
context
.read()
.add(SubscriptionEvent.getSubscription(deviceId: deviceId));
}
_purchaseStream = InAppPurchase.instance.purchaseStream;
_purchaseStreamSubscription = _purchaseStream.listen((purchaseDetailsList) {
if (purchaseDetailsList.isNotEmpty &&
purchaseDetailsList.first.status != PurchaseStatus.canceled &&
purchaseDetailsList.first.status != PurchaseStatus.error) {
_listenToPurchaseUpdated(purchaseDetailsList);
if (Platform.isAndroid) {
oldPurchaseDetails = purchaseDetailsList.first;
}
}
}, onDone: () {
debugPrint('Stream is done');
}, onError: (error) {
debugPrint('Stream error: $error');
});
getProducts();
if (Platform.isAndroid) {
await InAppPurchase.instance.restorePurchases();
}
}
Future _clearAllTransactions() async {
if (!Platform.isIOS) return;
final queue = SKPaymentQueueWrapper();
final transactions = await queue.transactions();
for (final tx in transactions) {
await queue.finishTransaction(tx);
debugPrint('Cleared transaction: ${tx.payment.productIdentifier}');
}
}
@override
void dispose() {
_purchaseStreamSubscription.cancel();
super.dispose();
}
void _onClickCheckout() async {
if (Platform.isAndroid) {
await InAppPurchase.instance.restorePurchases().whenComplete(() {
Future.delayed(const Duration(seconds: 1));
});
}
final selectedProduct =
_products.firstWhere((p) => p.id == _selectedProduct);
PurchaseParam purchaseParam;
if (Platform.isAndroid && oldPurchaseDetails != null) {
purchaseParam = GooglePlayPurchaseParam(
productDetails: selectedProduct,
changeSubscriptionParam: ChangeSubscriptionParam(
oldPurchaseDetails: oldPurchaseDetails as GooglePlayPurchaseDetails,
replacementMode: ReplacementMode.deferred,
),
);
} else {
// iOS or fresh Android subscription
purchaseParam = PurchaseParam(productDetails: selectedProduct);
}
if (Platform.isIOS) {
final queue = SKPaymentQueueWrapper();
final transactions = await queue.transactions();
for (final tx in transactions) {
if (tx.transactionState != SKPaymentTransactionStateWrapper.purchased)
continue;
await queue.finishTransaction(tx);
}
}
await _inAppPurchase.buyNonConsumable(purchaseParam: purchaseParam);
}
bool _isVerifyingPurchase = false;
Future _verifyPurchase(PurchaseDetails purchaseDetails) async {
String purchaseToken = "";
if (Platform.isAndroid) {
purchaseToken = purchaseDetails.verificationData.serverVerificationData;
}
if (Platform.isIOS) {
// For iOS, we need the full App Store receipt
purchaseToken = purchaseDetails.verificationData.localVerificationData;
}
// Check if this purchase has already been processed or if verification is in progress
if (_processedTokens.contains(purchaseToken) || _isVerifyingPurchase) {
return;
}
// Set the lock to prevent other verifications from running
_isVerifyingPurchase = true;
try {
// Mark this token as processed to prevent duplicates
_processedTokens.add(purchaseToken);
final deviceId = await _getDeviceId();
// Fire the verification event in the bloc
_chatCreditsBloc.add(ChatCreditsEvent.verifyPurchase(
platform: DeviceUtil.getPlatform(),
purchaseToken: purchaseToken,
productId: purchaseDetails.productID,
deviceId: deviceId!,
));
await _inAppPurchase.completePurchase(purchaseDetails);
} catch (error) {
debugPrint("Error during purchase verification: $error");
} finally {
// Release the lock after verification is complete
_isVerifyingPurchase = false;
}
}
void _listenToPurchaseUpdated(
List purchaseDetailsList) async {
for (var purchaseDetails in purchaseDetailsList) {
if (purchaseDetails.status == PurchaseStatus.purchased) {
await _verifyPurchase(purchaseDetails);
}
}
}
void _listenToChanges(BuildContext context, ChatCreditsState state) {
state.whenOrNull(
purchaseVerified: (credits, lastPurchasedCredits) {
Navigator.pop(context);
context.go(
'/payment-successful/${credits.credits}/${credits.hasUnlimitedCredits}/${credits.hasUpdatedSubscription}?ts=${DateTime.now().millisecondsSinceEpoch}:${credits.hasUpdatedSubscription}',
);
},
);
}
Future _getDeviceId() async {
var deviceInfo = DeviceInfoPlugin();
if (Platform.isIOS) {
var iosDeviceInfo = await deviceInfo.iosInfo;
return iosDeviceInfo.identifierForVendor;
} else {
var androidDeviceInfo = await deviceInfo.androidInfo;
return androidDeviceInfo.id;
}
}
void getProducts() async {
if (await InAppPurchase.instance.isAvailable()) {
final productResponse = await InAppPurchase.instance.queryProductDetails(
_productsIds.map((product) => product.id).toSet(),
);
setState(() {
_products.addAll(productResponse.productDetails);
});
if (productResponse.notFoundIDs.isNotEmpty) {
debugPrint("Products not found: ${productResponse.notFoundIDs}");
}
} else {
debugPrint("In-app purchase is not available");
}
}
< /code>
Я попытался завершить транзакции до оформления заказа, и когда виджет инициализируется, но я не думаю, что это много. Я также долго ждал после того, как отменил свою подписку в среде песочницы, а затем снова подпишился, но также вместо того, чтобы дать мне приобретенную статус, он дает мне статус восстановления (как будто она все еще держит старую подписку еще долго после отмены).
Подробнее здесь: [url]https://stackoverflow.com/questions/79698830/in-app-purchase-ios-keeps-retrieving-restored-purchase-when-widget-or-purchase-f[/url]
Ответить
1 сообщение
• Страница 1 из 1
Перейти
- Кемерово-IT
- ↳ Javascript
- ↳ C#
- ↳ JAVA
- ↳ Elasticsearch aggregation
- ↳ Python
- ↳ Php
- ↳ Android
- ↳ Html
- ↳ Jquery
- ↳ C++
- ↳ IOS
- ↳ CSS
- ↳ Excel
- ↳ Linux
- ↳ Apache
- ↳ MySql
- Детский мир
- Для души
- ↳ Музыкальные инструменты даром
- ↳ Печатная продукция даром
- Внешняя красота и здоровье
- ↳ Одежда и обувь для взрослых даром
- ↳ Товары для здоровья
- ↳ Физкультура и спорт
- Техника - даром!
- ↳ Автомобилистам
- ↳ Компьютерная техника
- ↳ Плиты: газовые и электрические
- ↳ Холодильники
- ↳ Стиральные машины
- ↳ Телевизоры
- ↳ Телефоны, смартфоны, плашеты
- ↳ Швейные машинки
- ↳ Прочая электроника и техника
- ↳ Фототехника
- Ремонт и интерьер
- ↳ Стройматериалы, инструмент
- ↳ Мебель и предметы интерьера даром
- ↳ Cантехника
- Другие темы
- ↳ Разное даром
- ↳ Давай меняться!
- ↳ Отдам\возьму за копеечку
- ↳ Работа и подработка в Кемерове
- ↳ Давай с тобой поговорим...
Мобильная версия