This commit is contained in:
roxy 2026-06-12 14:34:12 +08:00
parent 142dcebaae
commit 3872dc03b1
7 changed files with 183 additions and 35 deletions

View File

@ -524,7 +524,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 161;
CURRENT_PROJECT_VERSION = 162;
DEVELOPMENT_TEAM = S9YG87C297;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -533,7 +533,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.6.1;
MARKETING_VERSION = 1.6.2;
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -715,7 +715,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 161;
CURRENT_PROJECT_VERSION = 162;
DEVELOPMENT_TEAM = S9YG87C297;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -724,7 +724,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.6.1;
MARKETING_VERSION = 1.6.2;
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -744,7 +744,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 161;
CURRENT_PROJECT_VERSION = 162;
DEVELOPMENT_TEAM = S9YG87C297;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -753,7 +753,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.6.1;
MARKETING_VERSION = 1.6.2;
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -6,6 +6,7 @@ import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:fluro/fluro.dart' as fluro;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -299,6 +300,7 @@ class _YumiApplicationState extends State<YumiApplication>
WidgetsBinding.instance.addPostFrameCallback((_) {
_initRouter();
unawaited(_initLink());
unawaited(_reconcilePendingGooglePurchases(force: true));
});
}
@ -325,6 +327,24 @@ class _YumiApplicationState extends State<YumiApplication>
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

View File

@ -15,6 +15,7 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:yumi/ui_kit/components/dialog/dialog_base.dart';
import 'package:yumi/ui_kit/components/sc_tts.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_config_repository_imp.dart';
import 'package:yumi/shared/business_logic/models/res/sc_google_pay_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_product_config_res.dart';
import 'package:yumi/modules/wallet/recharge/recharge_page.dart';
@ -33,6 +34,9 @@ class AndroidPaymentProcessor extends ChangeNotifier {
//
final Map<String, String> _productTypeMap = {};
final Set<String> _verifyingPurchaseKeys = {};
bool _isReconcilingPastPurchases = false;
DateTime? _lastPastPurchaseReconcileAt;
// 访
bool get isAvailable => _isAvailable;
@ -60,12 +64,7 @@ class AndroidPaymentProcessor extends ChangeNotifier {
SCTts.show("Google Play payment service is unavailable");
return;
}
_subscription?.cancel();
//
_subscription = iap.purchaseStream.listen(
_handlePurchase,
onError: (error, stackTrace) => _handleError(error, stackTrace),
);
_ensurePurchaseStreamListener();
//
await fetchProductConfiguration();
@ -75,6 +74,7 @@ class AndroidPaymentProcessor extends ChangeNotifier {
//
await recoverTransactions();
await reconcilePendingGooglePurchases(force: true);
} catch (e) {
SCTts.show("init fail: $e");
_errorMessage = 'init fail: ${e.toString()}';
@ -84,6 +84,13 @@ class AndroidPaymentProcessor extends ChangeNotifier {
}
}
void _ensurePurchaseStreamListener() {
_subscription ??= iap.purchaseStream.listen(
_handlePurchase,
onError: (error, stackTrace) => _handleError(error, stackTrace),
);
}
///
Future fetchProductConfiguration() async {
productMap.clear();
@ -155,6 +162,9 @@ class AndroidPaymentProcessor extends ChangeNotifier {
//
String _getProductType(String productId) {
if (productId.startsWith('coins.')) {
return 'consumable';
}
return _productTypeMap[productId] ?? 'nonConsumable';
}
@ -283,7 +293,7 @@ class AndroidPaymentProcessor extends ChangeNotifier {
case PurchaseStatus.restored:
//
_verifyPayment(purchase);
_verifyPayment(purchase, forceSubmit: true);
break;
default:
break;
@ -304,21 +314,20 @@ class AndroidPaymentProcessor extends ChangeNotifier {
listen: false,
);
try {
//
if (_getProductType(purchase.productID) == 'consumable') {
await consumePurchase(purchase);
//
profileManager.balance();
_refreshFirstRechargeRewardState(firstRechargeManager);
SCTts.show('outstanding purchases have been reinstated');
}
await reconcilePendingGooglePurchases(force: true, showToast: true);
profileManager.balance();
_refreshFirstRechargeRewardState(firstRechargeManager);
} catch (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>(
context,
listen: false,
@ -331,12 +340,22 @@ class AndroidPaymentProcessor extends ChangeNotifier {
// 1.
if (purchase.verificationData.serverVerificationData.isEmpty) {
_errorMessage = '无效的支付凭证';
SCTts.show("Invalid payment voucher");
if (showToast) {
SCTts.show("Invalid payment voucher");
}
return;
}
// 2.
if (!purchase.pendingCompletePurchase) {
// 2. pendingCompletePurchase/
// 退 App
if (!forceSubmit && !purchase.pendingCompletePurchase) {
return;
}
final purchaseKey =
purchase.purchaseID ??
purchase.verificationData.serverVerificationData;
if (purchaseKey.isNotEmpty && !_verifyingPurchaseKeys.add(purchaseKey)) {
return;
}
@ -349,7 +368,7 @@ class AndroidPaymentProcessor extends ChangeNotifier {
}
// 4.
await SCConfigRepositoryImp().googlePay(
await _submitGooglePayVerification(
purchase.productID,
signature,
purchaseData,
@ -371,16 +390,125 @@ class AndroidPaymentProcessor extends ChangeNotifier {
profileManager.balance();
_refreshFirstRechargeRewardState(firstRechargeManager);
SCTts.show('purchase successful');
if (showToast) {
SCTts.show('purchase successful');
}
} catch (e) {
SCTts.show("verification failed: $e");
if (showToast) {
SCTts.show("verification failed: $e");
}
_errorMessage = '验证失败: ${e.toString()}';
} finally {
final purchaseKey =
purchase.purchaseID ??
purchase.verificationData.serverVerificationData;
if (purchaseKey.isNotEmpty) {
_verifyingPurchaseKeys.remove(purchaseKey);
}
SCLoadingManager.hide();
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<SCGooglePayRes> _submitGooglePayVerification(
String product,
String signature,
String purchaseData,
) async {
const api = 'order/purchase-pay/google';
final paramsForLog = <String, dynamic>{
'product': product,
'signature': _clipForDebugLog(signature),
'purchaseData': _clipForDebugLog(purchaseData),
};
if (kDebugMode) {
debugPrint('[GooglePay] request api=$api params=$paramsForLog');
}
final result = await SCConfigRepositoryImp().googlePay(
product,
signature,
purchaseData,
);
if (kDebugMode) {
debugPrint('[GooglePay] response api=$api result=${result.toJson()}');
}
return result;
}
String _clipForDebugLog(String value) {
if (value.length <= 240) {
return value;
}
return '${value.substring(0, 120)}...${value.substring(value.length - 80)}';
}
//
Future<void> _deliverProduct(String productId) async {
//
@ -506,7 +634,7 @@ class AndroidPaymentProcessor extends ChangeNotifier {
if (productType == 'consumable') {
success = await iap.buyConsumable(
purchaseParam: purchaseParam,
autoConsume: kReleaseMode, //
autoConsume: false,
);
} else {
success = await iap.buyNonConsumable(purchaseParam: purchaseParam);
@ -528,6 +656,8 @@ class AndroidPaymentProcessor extends ChangeNotifier {
//
Future<void> _checkPendingPurchases() async {
try {
await reconcilePendingGooglePurchases();
//
await iap.restorePurchases();

View File

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

View File

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

View File

@ -42,6 +42,6 @@ android {
dependencies {
implementation "com.tencent.tav:libpag:4.3.68"
implementation "com.tencent.tav:libpag:4.5.70"
implementation 'com.jakewharton:disklrucache:2.0.2'
}

View File

@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.6.1+161
version: 1.6.2+162
environment: