feat: 全局监听iap,在home初始化。避免过早处理,升级dart和flutter,支持storekit插件,强制指定storekit2版本
This commit is contained in:
parent
05d2c71cd0
commit
801a77f942
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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. 验证失败后的重试引导
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user