谷歌充值修复

This commit is contained in:
zhx 2026-06-10 15:50:36 +08:00
parent 5501345a7e
commit f9b7c53142
4 changed files with 141 additions and 26 deletions

View File

@ -6,6 +6,7 @@ import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:fluro/fluro.dart' as fluro; import 'package:fluro/fluro.dart' as fluro;
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -299,6 +300,7 @@ class _YumiApplicationState extends State<YumiApplication>
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_initRouter(); _initRouter();
unawaited(_initLink()); unawaited(_initLink());
unawaited(_reconcilePendingGooglePurchases(force: true));
}); });
} }
@ -325,6 +327,24 @@ class _YumiApplicationState extends State<YumiApplication>
context.read<RtcProvider>().releaseRtcEngineForAppTermination(), context.read<RtcProvider>().releaseRtcEngineForAppTermination(),
); );
} }
if (state == AppLifecycleState.resumed) {
unawaited(_reconcilePendingGooglePurchases());
}
}
Future<void> _reconcilePendingGooglePurchases({bool force = false}) async {
if (!Platform.isAndroid) {
return;
}
try {
await context
.read<AndroidPaymentProcessor>()
.reconcilePendingGooglePurchases(context: context, force: force);
} catch (error) {
if (kDebugMode) {
debugPrint('reconcile pending google purchases failed: $error');
}
}
} }
@override @override

View File

@ -33,6 +33,9 @@ class AndroidPaymentProcessor extends ChangeNotifier {
// //
final Map<String, String> _productTypeMap = {}; final Map<String, String> _productTypeMap = {};
final Set<String> _verifyingPurchaseKeys = {};
bool _isReconcilingPastPurchases = false;
DateTime? _lastPastPurchaseReconcileAt;
// 访 // 访
bool get isAvailable => _isAvailable; bool get isAvailable => _isAvailable;
@ -60,12 +63,7 @@ class AndroidPaymentProcessor extends ChangeNotifier {
SCTts.show("Google Play payment service is unavailable"); SCTts.show("Google Play payment service is unavailable");
return; return;
} }
_subscription?.cancel(); _ensurePurchaseStreamListener();
//
_subscription = iap.purchaseStream.listen(
_handlePurchase,
onError: (error, stackTrace) => _handleError(error, stackTrace),
);
// //
await fetchProductConfiguration(); await fetchProductConfiguration();
@ -75,6 +73,7 @@ class AndroidPaymentProcessor extends ChangeNotifier {
// //
await recoverTransactions(); await recoverTransactions();
await reconcilePendingGooglePurchases(force: true);
} catch (e) { } catch (e) {
SCTts.show("init fail: $e"); SCTts.show("init fail: $e");
_errorMessage = 'init fail: ${e.toString()}'; _errorMessage = 'init fail: ${e.toString()}';
@ -84,6 +83,13 @@ class AndroidPaymentProcessor extends ChangeNotifier {
} }
} }
void _ensurePurchaseStreamListener() {
_subscription ??= iap.purchaseStream.listen(
_handlePurchase,
onError: (error, stackTrace) => _handleError(error, stackTrace),
);
}
/// ///
Future fetchProductConfiguration() async { Future fetchProductConfiguration() async {
productMap.clear(); productMap.clear();
@ -155,6 +161,9 @@ class AndroidPaymentProcessor extends ChangeNotifier {
// //
String _getProductType(String productId) { String _getProductType(String productId) {
if (productId.startsWith('coins.')) {
return 'consumable';
}
return _productTypeMap[productId] ?? 'nonConsumable'; return _productTypeMap[productId] ?? 'nonConsumable';
} }
@ -283,7 +292,7 @@ class AndroidPaymentProcessor extends ChangeNotifier {
case PurchaseStatus.restored: case PurchaseStatus.restored:
// //
_verifyPayment(purchase); _verifyPayment(purchase, forceSubmit: true);
break; break;
default: default:
break; break;
@ -304,21 +313,20 @@ class AndroidPaymentProcessor extends ChangeNotifier {
listen: false, listen: false,
); );
try { try {
// await reconcilePendingGooglePurchases(force: true, showToast: true);
if (_getProductType(purchase.productID) == 'consumable') { profileManager.balance();
await consumePurchase(purchase); _refreshFirstRechargeRewardState(firstRechargeManager);
//
profileManager.balance();
_refreshFirstRechargeRewardState(firstRechargeManager);
SCTts.show('outstanding purchases have been reinstated');
}
} catch (e) { } catch (e) {
debugPrint('handle already-owned purchase failed: $e'); debugPrint('handle already-owned purchase failed: $e');
} }
} }
// //
Future<void> _verifyPayment(PurchaseDetails purchase) async { Future<void> _verifyPayment(
PurchaseDetails purchase, {
bool forceSubmit = false,
bool showToast = true,
}) async {
final profileManager = Provider.of<SocialChatUserProfileManager>( final profileManager = Provider.of<SocialChatUserProfileManager>(
context, context,
listen: false, listen: false,
@ -331,12 +339,22 @@ class AndroidPaymentProcessor extends ChangeNotifier {
// 1. // 1.
if (purchase.verificationData.serverVerificationData.isEmpty) { if (purchase.verificationData.serverVerificationData.isEmpty) {
_errorMessage = '无效的支付凭证'; _errorMessage = '无效的支付凭证';
SCTts.show("Invalid payment voucher"); if (showToast) {
SCTts.show("Invalid payment voucher");
}
return; return;
} }
// 2. // 2. pendingCompletePurchase/
if (!purchase.pendingCompletePurchase) { // 退 App
if (!forceSubmit && !purchase.pendingCompletePurchase) {
return;
}
final purchaseKey =
purchase.purchaseID ??
purchase.verificationData.serverVerificationData;
if (purchaseKey.isNotEmpty && !_verifyingPurchaseKeys.add(purchaseKey)) {
return; return;
} }
@ -371,16 +389,93 @@ class AndroidPaymentProcessor extends ChangeNotifier {
profileManager.balance(); profileManager.balance();
_refreshFirstRechargeRewardState(firstRechargeManager); _refreshFirstRechargeRewardState(firstRechargeManager);
SCTts.show('purchase successful'); if (showToast) {
SCTts.show('purchase successful');
}
} catch (e) { } catch (e) {
SCTts.show("verification failed: $e"); if (showToast) {
SCTts.show("verification failed: $e");
}
_errorMessage = '验证失败: ${e.toString()}'; _errorMessage = '验证失败: ${e.toString()}';
} finally { } finally {
final purchaseKey =
purchase.purchaseID ??
purchase.verificationData.serverVerificationData;
if (purchaseKey.isNotEmpty) {
_verifyingPurchaseKeys.remove(purchaseKey);
}
SCLoadingManager.hide(); SCLoadingManager.hide();
notifyListeners(); notifyListeners();
} }
} }
Future<void> reconcilePendingGooglePurchases({
BuildContext? context,
bool force = false,
bool showToast = false,
}) async {
if (!Platform.isAndroid) {
return;
}
if (context != null) {
this.context = context;
}
if (_isReconcilingPastPurchases) {
return;
}
final lastCheck = _lastPastPurchaseReconcileAt;
if (!force &&
lastCheck != null &&
DateTime.now().difference(lastCheck) < const Duration(seconds: 20)) {
return;
}
_isReconcilingPastPurchases = true;
_lastPastPurchaseReconcileAt = DateTime.now();
try {
_ensurePurchaseStreamListener();
_isAvailable = await iap.isAvailable();
if (!_isAvailable) {
return;
}
final InAppPurchaseAndroidPlatformAddition androidAddition =
iap.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>();
final response = await androidAddition.queryPastPurchases();
if (response.error != null) {
debugPrint('query past google purchases failed: ${response.error}');
return;
}
final purchases =
response.pastPurchases.where((purchase) {
final isPurchased =
purchase.status == PurchaseStatus.purchased ||
purchase.status == PurchaseStatus.restored;
final productType = _getProductType(purchase.productID);
final isUnacknowledged =
purchase.pendingCompletePurchase ||
!purchase.billingClientPurchase.isAcknowledged;
// Google 使
return isPurchased &&
(isUnacknowledged || productType == 'consumable');
}).toList();
if (purchases.isEmpty) {
return;
}
_purchases = purchases;
for (final purchase in purchases) {
await _verifyPayment(purchase, forceSubmit: true, showToast: showToast);
}
} catch (e) {
debugPrint('reconcile pending google purchases failed: $e');
} finally {
_isReconcilingPastPurchases = false;
notifyListeners();
}
}
// //
Future<void> _deliverProduct(String productId) async { Future<void> _deliverProduct(String productId) async {
// //
@ -506,7 +601,7 @@ class AndroidPaymentProcessor extends ChangeNotifier {
if (productType == 'consumable') { if (productType == 'consumable') {
success = await iap.buyConsumable( success = await iap.buyConsumable(
purchaseParam: purchaseParam, purchaseParam: purchaseParam,
autoConsume: kReleaseMode, // autoConsume: false,
); );
} else { } else {
success = await iap.buyNonConsumable(purchaseParam: purchaseParam); success = await iap.buyNonConsumable(purchaseParam: purchaseParam);
@ -528,6 +623,8 @@ class AndroidPaymentProcessor extends ChangeNotifier {
// //
Future<void> _checkPendingPurchases() async { Future<void> _checkPendingPurchases() async {
try { try {
await reconcilePendingGooglePurchases();
// //
await iap.restorePurchases(); await iap.restorePurchases();

View File

@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@ -1336,7 +1335,7 @@ class _RoomRedPacketRechargeSheetState
SCLoadingManager.show(); SCLoadingManager.show();
final success = await processor.iap.buyConsumable( final success = await processor.iap.buyConsumable(
purchaseParam: PurchaseParam(productDetails: product.produc), purchaseParam: PurchaseParam(productDetails: product.produc),
autoConsume: kReleaseMode, autoConsume: false,
); );
if (!success) { if (!success) {
SCTts.show('Purchase failed, please check your network connection'); SCTts.show('Purchase failed, please check your network connection');

View File

@ -1,6 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@ -367,7 +366,7 @@ class _RoomRechargeBottomSheetState extends State<RoomRechargeBottomSheet> {
SCLoadingManager.show(); SCLoadingManager.show();
final success = await processor.iap.buyConsumable( final success = await processor.iap.buyConsumable(
purchaseParam: PurchaseParam(productDetails: product.produc), purchaseParam: PurchaseParam(productDetails: product.produc),
autoConsume: kReleaseMode, autoConsume: false,
); );
if (!success) { if (!success) {
SCTts.show('Purchase failed, please check your network connection'); SCTts.show('Purchase failed, please check your network connection');