212 lines
7.2 KiB
Dart
212 lines
7.2 KiB
Dart
import 'dart:async';
|
||
import 'dart:io';
|
||
import 'package:get/get.dart';
|
||
import 'package:in_app_purchase/in_app_purchase.dart';
|
||
import 'package:kaer_with_panels/app/services/iap/iap_pending_order_service.dart';
|
||
import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
|
||
import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
|
||
import 'package:kaer_with_panels/app/services/api_service/kr_subscribe_api.dart';
|
||
import 'package:kaer_with_panels/app/routes/app_pages.dart';
|
||
|
||
class KRIAPService extends GetxService {
|
||
static KRIAPService get instance => Get.find<KRIAPService>();
|
||
|
||
final KRSubscribeApi _kr_subscribeApi = KRSubscribeApi();
|
||
final InAppPurchase _iap = InAppPurchase.instance;
|
||
StreamSubscription<List<PurchaseDetails>>? _subscription;
|
||
// 增加标志位,防止多次初始化
|
||
bool _hasStarted = false;
|
||
|
||
@override
|
||
void onInit() {
|
||
super.onInit();
|
||
// 不再这里自动启动,等待 Home 页面手动开启
|
||
}
|
||
|
||
/// 【外部调用】在 Home 页面加载完成后调用此方法
|
||
void setup() {
|
||
if (!Platform.isIOS) return;
|
||
if (_hasStarted) return;
|
||
_hasStarted = true;
|
||
_startGlobalListening();
|
||
}
|
||
|
||
void _startGlobalListening() {
|
||
print('💳 [IAPService] ========== 🚀 全局监听流已就绪 ==========');
|
||
|
||
// 确保不会重复监听
|
||
_subscription?.cancel();
|
||
|
||
_subscription = _iap.purchaseStream.listen(
|
||
_handleIapUpdates,
|
||
onDone: () => _subscription?.cancel(),
|
||
onError: (error) {
|
||
print('💳 [IAPService] ❌ Stream Error: $error');
|
||
// 如果发生错误,可以尝试重启流
|
||
},
|
||
);
|
||
}
|
||
|
||
// 1. 监听回调:只负责状态分流
|
||
void _handleIapUpdates(List<PurchaseDetails> purchases) async {
|
||
final pendingOrderNo = await IAPPendingOrderService.getPendingOrderNo();
|
||
print('🔄 [IAPService] 处理状态222: ${pendingOrderNo}');
|
||
// 如果没有本地挂起的订单号,说明不是通过 App 正常发起的支付(或是已处理完的补单)
|
||
if (pendingOrderNo == null || pendingOrderNo.isEmpty) return;
|
||
|
||
for (final p in purchases) {
|
||
print('🔄 [IAPService] 处理状态: ${p.status} | ID: ${p.purchaseID}');
|
||
|
||
if (p.status == PurchaseStatus.pending) continue;
|
||
|
||
if (p.status == PurchaseStatus.error) {
|
||
if (p.pendingCompletePurchase) await _iap.completePurchase(p);
|
||
KRCommonUtil.kr_showToast('购买失败');
|
||
KRCommonUtil.kr_hideLoading();
|
||
continue;
|
||
}
|
||
|
||
if (p.status == PurchaseStatus.purchased || p.status == PurchaseStatus.restored) {
|
||
// 核心:跳转到统一验证逻辑
|
||
_verifyWithServer(p, pendingOrderNo);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 2. 统一验证逻辑:负责与后端交互、补单、UI 引导
|
||
Future<void> _verifyWithServer(PurchaseDetails p, String orderNo) async {
|
||
final String jwsData = p.verificationData.serverVerificationData;
|
||
|
||
if (jwsData.isEmpty) {
|
||
print('❌ [IAPService] JWS 数据为空,跳过验证');
|
||
if (p.pendingCompletePurchase) {
|
||
await InAppPurchase.instance.completePurchase(p);
|
||
}
|
||
KRCommonUtil.kr_hideLoading();
|
||
return;
|
||
}
|
||
|
||
KRCommonUtil.kr_showLoading(message: '正在激活会员权益...');
|
||
bool ok = false;
|
||
|
||
try {
|
||
// 根据状态选择对应的后端接口
|
||
if (p.status == PurchaseStatus.purchased || p.status == PurchaseStatus.restored) {
|
||
await IAPPendingOrderService.setPendingJwsData(jwsData);
|
||
final either = await _kr_subscribeApi.kr_attachAppleIapTransaction(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) {
|
||
print('🌐 [IAPService] 网络请求异常');
|
||
}
|
||
|
||
if (ok) {
|
||
await _finalizeTransaction(p, orderNo);
|
||
} else {
|
||
_handleVerificationFailure(p, orderNo);
|
||
}
|
||
}
|
||
|
||
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. 验证成功后的收尾工作
|
||
Future<void> _finalizeTransaction(PurchaseDetails p, String orderNo) async {
|
||
print('✅ [IAPService] 验证成功,清理流程${p.pendingCompletePurchase}');
|
||
|
||
// I. 结束苹果事务(关键:防止重复推送)
|
||
if (p.pendingCompletePurchase) {
|
||
await _iap.completePurchase(p);
|
||
}
|
||
await _iap.completePurchase(p);
|
||
// II. 清理本地拦截订单号
|
||
await IAPPendingOrderService.clearPendingOrderNo();
|
||
KRCommonUtil.kr_hideLoading();
|
||
|
||
String? realRoute = findCurrentRoute();
|
||
print('🔍 [IAP Fix] Final Found Route: "$realRoute"');
|
||
// if (Get.currentRoute.contains(Routes.KR_ORDER_STATUS)) return;
|
||
//
|
||
// // 2. 判断是否需要自动跳转
|
||
// // 如果当前在“会员购买页”,说明是用户刚刚点的,直接跳
|
||
// if (Get.currentRoute.contains(Routes.KR_PURCHASE_MEMBERSHIP)) {
|
||
// _navigateToStatusPage(orderNo);
|
||
// } 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. 验证失败后的重试引导
|
||
void _handleVerificationFailure(PurchaseDetails p, String orderNo) {
|
||
KRCommonUtil.kr_hideLoading();
|
||
HIDialog.show(
|
||
title: '激活失败',
|
||
message: '您的支付已成功,但由于网络原因未能开启权益。',
|
||
confirmText: '重试激活',
|
||
cancelText: '关闭',
|
||
barrierDismissible: false,
|
||
preventBackDismiss: true,
|
||
onConfirm: () {
|
||
// 💡 重点:直接重新调用验证逻辑,不重复触发监听流
|
||
_verifyWithServer(p, orderNo);
|
||
},
|
||
);
|
||
}
|
||
|
||
// 5. 手动触发(用于 UI 上的恢复购买按钮)
|
||
Future<void> retryUnfinishedTransactions() async {
|
||
try {
|
||
await _iap.restorePurchases();
|
||
} catch (e) {
|
||
print('e');
|
||
}
|
||
}
|
||
|
||
@override
|
||
void onClose() {
|
||
_subscription?.cancel();
|
||
super.onClose();
|
||
}
|
||
} |