feat: 全局监听iap,在home初始化。避免过早处理,升级dart和flutter,支持storekit插件,强制指定storekit2版本

This commit is contained in:
speakeloudest 2025-12-17 20:34:59 -08:00
parent 05d2c71cd0
commit 801a77f942
6 changed files with 113 additions and 37 deletions

View File

@ -1,15 +1,21 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../controllers/kr_home_controller.dart'; import '../controllers/kr_home_controller.dart';
// IAP Service
import 'package:kaer_with_panels/app/services/iap/iap_service.dart';
class KRHomeBinding extends Bindings { class KRHomeBinding extends Bindings {
@override @override
void dependencies() { void dependencies() {
// 1. Home ()
Get.lazyPut<KRHomeController>( Get.lazyPut<KRHomeController>(
() => KRHomeController(), () => KRHomeController(),
); );
Get.lazyPut<KRHomeController>(
() => KRHomeController(), // 2. Home IAP Service
// 使 fenix: true Service
Get.lazyPut<KRIAPService>(
() => KRIAPService(),
fenix: true,
); );
} }
} }

View File

@ -1879,6 +1879,12 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
print('ok'); print('ok');
}); });
}); });
Future.delayed(const Duration(milliseconds: 500), () async {
if (Get.isRegistered<KRIAPService>()) {
KRIAPService.instance.setup();
}
});
} }
@override @override

View File

@ -450,10 +450,11 @@ class KRPurchaseMembershipController extends GetxController {
// 1. iOS // 1. iOS
if (Platform.isIOS) { if (Platform.isIOS) {
final existingOrderNo = await IAPPendingOrderService.getPendingOrderNo(); final getPendingJwsData = await IAPPendingOrderService.getPendingJwsData();
final getPendingOrderNo = await IAPPendingOrderService.getPendingOrderNo();
if (existingOrderNo != null && existingOrderNo.isNotEmpty) { if (getPendingJwsData != null && getPendingJwsData.isNotEmpty!) {
print('🛑 [IAP] 拦截成功:发现未结单 $existingOrderNo'); print('🛑 [IAP] 拦截成功:发现未结单 $getPendingJwsData');
await HIDialog.show( await HIDialog.show(
title: '检测到待激活订单', title: '检测到待激活订单',
@ -461,10 +462,22 @@ class KRPurchaseMembershipController extends GetxController {
confirmText: '立即激活', confirmText: '立即激活',
cancelText: '取消', cancelText: '取消',
barrierDismissible: false, barrierDismissible: false,
onConfirm: () { onConfirm: () async{
// 🚀 Service // 🚀 Service
// PurchaseStream try{
KRIAPService.instance.retryUnfinishedTransactions(); await _kr_subscribeApi.kr_attachAppleIapTransaction(getPendingJwsData, getPendingOrderNo!);
Get.offNamed(
Routes.KR_ORDER_STATUS,
arguments: {
'order': getPendingOrderNo,
'payment_type': 'apple_iap',
'checkout_type': 'ipa',
},
);
}catch (e){
KRCommonUtil.kr_showToast('激活失败,请重试');
}
}, },
); );
return; // 🛑 return return; // 🛑 return
@ -962,7 +975,7 @@ class KRPurchaseMembershipController extends GetxController {
final KRIpaPayment iapParams = (checkoutResponse.ipa == null || final KRIpaPayment iapParams = (checkoutResponse.ipa == null ||
(checkoutResponse.ipa?.productId.isEmpty ?? true)) (checkoutResponse.ipa?.productId.isEmpty ?? true))
? KRIpaPayment( ? KRIpaPayment(
productId: 'com.hifastvpn.plan.day${kr_getSelectedQuantity()}') productId: 'com.hifastvpn.vip.day${kr_getSelectedQuantity()}')
: checkoutResponse.ipa!; : checkoutResponse.ipa!;
final iap = InAppPurchase.instance; final iap = InAppPurchase.instance;

View File

@ -1,17 +1,29 @@
import 'package:flutter_keychain/flutter_keychain.dart'; import 'package:flutter_keychain/flutter_keychain.dart';
class IAPPendingOrderService { class IAPPendingOrderService {
static const _key = 'hi_iap_pending_order_no'; static const _keyOrderNo = 'hi_iap_pending_order_no';
static const _keyJwsData = 'hi_iap_pending_jws_data'; // Key
static Future<void> setPendingOrderNo(String orderNo) async { static Future<void> setPendingOrderNo(String orderNo) async {
await FlutterKeychain.put(key: _key, value: orderNo); await FlutterKeychain.put(key: _keyOrderNo, value: orderNo);
} }
static Future<String?> getPendingOrderNo() async { static Future<String?> getPendingOrderNo() async {
return await FlutterKeychain.get(key: _key); return await FlutterKeychain.get(key: _keyOrderNo);
}
/// JWS
static Future<void> setPendingJwsData(String jwsData) async {
await FlutterKeychain.put(key: _keyJwsData, value: jwsData);
}
/// JWS
static Future<String?> getPendingJwsData() async {
return await FlutterKeychain.get(key: _keyJwsData);
} }
static Future<void> clearPendingOrderNo() async { static Future<void> clearPendingOrderNo() async {
await FlutterKeychain.remove(key: _key); await FlutterKeychain.remove(key: _keyOrderNo);
await FlutterKeychain.remove(key: _keyJwsData);
} }
} }

View File

@ -50,7 +50,7 @@ class KRIAPService extends GetxService {
// 1. // 1.
void _handleIapUpdates(List<PurchaseDetails> purchases) async { void _handleIapUpdates(List<PurchaseDetails> purchases) async {
final pendingOrderNo = await IAPPendingOrderService.getPendingOrderNo(); final pendingOrderNo = await IAPPendingOrderService.getPendingOrderNo();
print('🔄 [IAPService] 处理状态222: ${pendingOrderNo}');
// App // App
if (pendingOrderNo == null || pendingOrderNo.isEmpty) return; if (pendingOrderNo == null || pendingOrderNo.isEmpty) return;
@ -92,13 +92,15 @@ class KRIAPService extends GetxService {
try { try {
// //
if (p.status == PurchaseStatus.purchased || p.status == PurchaseStatus.restored) { if (p.status == PurchaseStatus.purchased || p.status == PurchaseStatus.restored) {
await IAPPendingOrderService.setPendingJwsData(jwsData);
final either = await _kr_subscribeApi.kr_attachAppleIapTransaction(jwsData, orderNo); final either = await _kr_subscribeApi.kr_attachAppleIapTransaction(jwsData, orderNo);
either.fold((e) => print('❌ [IAPService] 支付激活失败: ${e.msg}'), (v) => ok = true); either.fold((e) => print('❌ [IAPService] 支付激活失败: ${e.msg}'), (v) => ok = true);
} else {
// restore JWS
// final either = await _kr_subscribeApi.kr_restoreAppleIapTransaction(jwsData, orderNo);
// either.fold((e) => print('❌ [IAPService] 恢复激活失败: ${e.msg}'), (v) => ok = true);
} }
// else {
// // restore JWS
// // final either = await _kr_subscribeApi.kr_restoreAppleIapTransaction(jwsData, orderNo);
// // either.fold((e) => print('❌ [IAPService] 恢复激活失败: ${e.msg}'), (v) => ok = true);
// }
} catch (e) { } catch (e) {
print('🌐 [IAPService] 网络请求异常'); print('🌐 [IAPService] 网络请求异常');
} }
@ -110,31 +112,70 @@ class KRIAPService extends GetxService {
} }
} }
String? findCurrentRoute() {
String? routeName;
try {
// Navigator context
Get.key.currentState?.popUntil((route) {
routeName = route.settings.name;
//
print('层级检查: $routeName');
// null
if (routeName != null && routeName != '/') return true;
return false;
});
} catch (e) {
print('路由查找异常: $e');
}
return routeName;
}
// 3. // 3.
Future<void> _finalizeTransaction(PurchaseDetails p, String orderNo) async { Future<void> _finalizeTransaction(PurchaseDetails p, String orderNo) async {
print('✅ [IAPService] 验证成功,清理流程'); print('✅ [IAPService] 验证成功,清理流程${p.pendingCompletePurchase}');
// I. // I.
if (p.pendingCompletePurchase) { if (p.pendingCompletePurchase) {
await _iap.completePurchase(p); await _iap.completePurchase(p);
} }
await _iap.completePurchase(p);
// II. // II.
await IAPPendingOrderService.clearPendingOrderNo(); await IAPPendingOrderService.clearPendingOrderNo();
KRCommonUtil.kr_hideLoading(); KRCommonUtil.kr_hideLoading();
// III. String? realRoute = findCurrentRoute();
// print('🔍 [IAP Fix] Final Found Route: "$realRoute"');
if (!Get.currentRoute.contains(Routes.KR_ORDER_STATUS)) { // if (Get.currentRoute.contains(Routes.KR_ORDER_STATUS)) return;
Get.offNamed( //
Routes.KR_ORDER_STATUS, // // 2.
arguments: { // //
'order': orderNo, // if (Get.currentRoute.contains(Routes.KR_PURCHASE_MEMBERSHIP)) {
'payment_type': 'apple_iap', // _navigateToStatusPage(orderNo);
'checkout_type': 'ipa', // } else {
}, // // 3. Home
); // //
} // HIDialog.show(
// title: '订单处理成功',
// message: '检测到您有一笔未完成的订单已激活成功,会员权益已到账。',
// confirmText: '查看订单',
// cancelText: '我知道了',
// onConfirm: () => _navigateToStatusPage(orderNo),
// );
// }
//
_navigateToStatusPage(orderNo);
}
void _navigateToStatusPage(String orderNo) {
Get.offNamed(
Routes.KR_ORDER_STATUS,
arguments: {
'order': orderNo,
'payment_type': 'apple_iap',
'checkout_type': 'ipa',
},
);
} }
// 4. // 4.

View File

@ -5,7 +5,6 @@ import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
import 'package:kaer_with_panels/app/routes/app_pages.dart'; import 'package:kaer_with_panels/app/routes/app_pages.dart';
import 'package:kaer_with_panels/app/utils/kr_common_util.dart'; import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
import 'package:kaer_with_panels/app/services/kr_site_config_service.dart'; import 'package:kaer_with_panels/app/services/kr_site_config_service.dart';
import 'package:kaer_with_panels/app/services/iap/iap_service.dart';
Future<bool> ensureAccountExists() async { Future<bool> ensureAccountExists() async {
final app = KRAppRunData.getInstance(); final app = KRAppRunData.getInstance();
@ -28,7 +27,6 @@ Future<bool> ensureAccountExists() async {
return false; return false;
} }
KRIAPService.instance.setup();
var crispId = config.kr_website_id; var crispId = config.kr_website_id;
var deviceLimitText = config.device_limit; var deviceLimitText = config.device_limit;
var deviceLimit = int.tryParse(deviceLimitText) ?? 0; var deviceLimit = int.tryParse(deviceLimitText) ?? 0;