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

175 lines
5.9 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();
// 如果没有本地挂起的订单号,说明不是通过 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) {
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);
}
}
// 3. 验证成功后的收尾工作
Future<void> _finalizeTransaction(PurchaseDetails p, String orderNo) async {
print('✅ [IAPService] 验证成功,清理流程');
// I. 结束苹果事务(关键:防止重复推送)
if (p.pendingCompletePurchase) {
await _iap.completePurchase(p);
}
// II. 清理本地拦截订单号
await IAPPendingOrderService.clearPendingOrderNo();
KRCommonUtil.kr_hideLoading();
// III. 跳转结果页
// 如果已经在结果页了就不跳,或者根据路由判断
if (!Get.currentRoute.contains(Routes.KR_ORDER_STATUS)) {
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 {
KRCommonUtil.kr_showLoading(message: '正在查找未完成订单...');
await _iap.restorePurchases();
// 如果 5 秒后没触发流,隐藏 loading
Future.delayed(const Duration(seconds: 5), () => KRCommonUtil.kr_hideLoading());
} catch (e) {
KRCommonUtil.kr_hideLoading();
KRCommonUtil.kr_showToast("请求失败,请稍后重试");
}
}
@override
void onClose() {
_subscription?.cancel();
super.onClose();
}
}