hi-client/lib/app/services/iap/iap_service.dart

212 lines
7.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
}
}