From 743631c4ce8c309e7017ca67e64b2312e53ecb5b Mon Sep 17 00:00:00 2001 From: speakeloudest Date: Wed, 17 Dec 2025 01:46:42 -0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0flutter=5Fkeychain?= =?UTF-8?q?=E6=9D=A5=E7=BC=93=E5=AD=98=E8=AE=A2=E5=8D=95=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kr_purchase_membership_controller.dart | 293 +++++++---------- .../views/kr_purchase_membership_view.dart | 305 +++++++++++------- lib/app/services/api_service/api.dart | 15 +- .../api_service/kr_subscribe_api.dart | 34 +- pubspec.lock | 8 + pubspec.yaml | 1 + 6 files changed, 365 insertions(+), 291 deletions(-) diff --git a/lib/app/modules/kr_purchase_membership/controllers/kr_purchase_membership_controller.dart b/lib/app/modules/kr_purchase_membership/controllers/kr_purchase_membership_controller.dart index e745bae..b3e60fc 100755 --- a/lib/app/modules/kr_purchase_membership/controllers/kr_purchase_membership_controller.dart +++ b/lib/app/modules/kr_purchase_membership/controllers/kr_purchase_membership_controller.dart @@ -21,6 +21,8 @@ import 'package:flutter_stripe/flutter_stripe.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; import 'dart:async'; import 'package:kaer_with_panels/app/model/response/kr_purchase_order_no.dart'; +import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart'; +import 'package:kaer_with_panels/app/services/iap/iap_pending_order_service.dart'; /// 会员购买控制器 /// 负责处理会员套餐选择、支付方式选择和订阅流程 @@ -53,6 +55,9 @@ class KRPurchaseMembershipController extends GetxController { /// 当前余额 RxInt _kr_balance = 0.obs; + // -------------------- IAP 全局监听 -------------------- + late StreamSubscription> _iapSubscription; + @override void onInit() async { super.onInit(); @@ -80,11 +85,22 @@ class KRPurchaseMembershipController extends GetxController { print('💳 [PurchaseMembership] ❌ 写入调试日志失败: $e'); } + // 初始化 IAP 全局监听 + if (Platform.isIOS) { + _iapSubscription = InAppPurchase.instance.purchaseStream.listen( + _handleIapUpdates, + onError: (error) { + print('IAP Stream Error: $error'); + }, + ); + } + kr_initializeData(); } @override void onClose() { + _iapSubscription.cancel(); _kr_eventWorker?.dispose(); super.onClose(); } @@ -445,6 +461,24 @@ class KRPurchaseMembershipController extends GetxController { print('开始订阅流程,选定支付'); kr_errorMessage.value = ''; + // ✅ 仅在 iOS 检查未完成订单 + if (Platform.isIOS) { + final existing = await IAPPendingOrderService.getPendingOrderNo(); + if (existing != null && existing.isNotEmpty) { + await HIDialog.show( + title: '存在未完成订单', + message: '检测到未完成订单,需要恢复购买以完成订阅。', + confirmText: '恢复购买', + barrierDismissible: false, + preventBackDismiss: true, + onConfirm: () { + kr_restorePurchases(); // 恢复未完成订单 + }, + ); + return; // 等待恢复完成,不再继续发起新订阅 + } + } + try { await kr_processPurchaseAndCheckout(); } catch (e) { @@ -472,7 +506,7 @@ class KRPurchaseMembershipController extends GetxController { /// 处理购买和结账流程 Future kr_processPurchaseAndCheckout() async { final selectedPlan = kr_plans[kr_selectedPlanIndex.value]; -// ========================================================================= + // ========================================================================= // 🔽 支付方式选择优化:iOS 优先使用 Stripe 🔽 // ========================================================================= final isIOS = Platform.isIOS; @@ -936,7 +970,7 @@ class KRPurchaseMembershipController extends GetxController { final KRIpaPayment iapParams = (checkoutResponse.ipa == null || (checkoutResponse.ipa?.productId.isEmpty ?? true)) ? KRIpaPayment( - productId: 'com.hifastvpn.vip.day${kr_getSelectedQuantity()}') + productId: 'com.hifastvpn.plan.day${kr_getSelectedQuantity()}') : checkoutResponse.ipa!; final iap = InAppPurchase.instance; @@ -965,161 +999,27 @@ class KRPurchaseMembershipController extends GetxController { } final product = productDetailsResponse.productDetails.first; + final kr_userId = KRAppRunData.getInstance().kr_userId.value; final purchaseParam = PurchaseParam( productDetails: product, - applicationUserName: iapParams.applicationUsername, + applicationUserName: 'uid_${kr_userId}|orderNo_${orderNo}', ); // ---------------------------------------------------------------------- - // 假设 ProductDetails 已成功获取,并已构造 purchaseParam。 + // ProductDetails 已成功获取,并已构造 purchaseParam。 // ---------------------------------------------------------------------- - - late StreamSubscription> subscription; - - // 【1】确认购买 Stream 监听启动 - print('--- IAP 流程:购买 Stream 监听即将启动 ---'); - - subscription = iap.purchaseStream.listen( - (purchases) async { - print('IAP Stream 收到 ${purchases.length} 个交易事件'); - - for (final p in purchases) { - if (p.productID != iapParams.productId) continue; - - final status = p.status.toString().split('.').last; - print('处理交易:产品ID ${p.productID}, 状态: $status'); - - // ======================= - // 1️⃣ Pending 状态 - // ======================= - if (p.status == PurchaseStatus.pending) { - print('交易状态:Pending,等待 App Store...'); - continue; - } - - // ======================= - // 2️⃣ Error 状态 - // ======================= - if (p.status == PurchaseStatus.error) { - print('交易失败'); - - if (p.error != null) { - print('IAP 错误 code: ${p.error!.code}'); - print('IAP 错误 message: ${p.error!.message}'); - } - - // ❗只在需要时 complete - if (p.pendingCompletePurchase) { - await iap.completePurchase(p); - print('Error 状态交易已 complete'); - } - - KRCommonUtil.kr_showToast('购买失败'); - continue; - } - - // ======================= - // 3️⃣ Purchased / Restored - // ======================= - if (p.status == PurchaseStatus.purchased || - p.status == PurchaseStatus.restored) { - - // ----------------------- - // ① 打印凭证(保留你原来的) - // ----------------------- - final receiptData = p.verificationData.serverVerificationData; - final transactionId = p.purchaseID; - final productID = p.productID; - - print('***************** IAP 交易凭证数据 *****************'); - print('产品 ID (ProductID): $productID'); - print('交易 ID (PurchaseID): $transactionId'); - print('Receipt(Base64):'); - print(receiptData); - print('**************************************************'); - - // ----------------------- - // ② 先 attach 到你服务器 - // ----------------------- - final attachEither = - await _kr_subscribeApi.kr_attachAppleIapTransaction( - transactionId!, - orderNo, - ); - - bool attachSuccess = false; - - attachEither.fold( - (error) { - print('IAP attach失败 code: ${error.code}'); - print('IAP attach失败 msg: ${error.msg}'); - }, - (ok) { - attachSuccess = true; - print('IAP attach成功: $ok'); - }, - ); - - // ❗attach 失败:不要 complete,让 Apple 重发 - if (!attachSuccess) { - print('attach 失败,暂不 complete,等待重试'); - continue; - } - - // ----------------------- - // ③ attach 成功后,才 complete - // ----------------------- - if (p.pendingCompletePurchase) { - await iap.completePurchase(p); - print('交易已 completePurchase'); - } - - // ----------------------- - // ④ 页面跳转(保留) - // ----------------------- - Get.toNamed( - Routes.KR_ORDER_STATUS, - arguments: { - 'order': orderNo, - 'payment_type': paymentPlatform, - 'checkout_type': 'ipa', - }, - ); - - // ❌ 不要在这里 cancel subscription - // Stream 需要保持监听,用于恢复交易 - } - } - }, - onError: (error) { - print('--- IAP Stream 监听发生错误: $error ---'); - }, - onDone: () { - print('--- IAP Stream 已关闭 ---'); - }, - ); - - // 🔴 关键:buy 之前先清理 - await _cleanPendingPurchase(iap, iapParams.productId); - - print('--- IAP 流程:即将调用 buyNonConsumable ---'); - + await IAPPendingOrderService.setPendingOrderNo(orderNo); + KRCommonUtil.kr_showLoading(); try { final result = await iap.buyNonConsumable(purchaseParam: purchaseParam); - print('--- IAP 流程:buyNonConsumable 调用返回。返回值:$result ---'); - if (!result) { - // 如果返回 false,说明本地请求失败 - print('IAP 购买请求本地失败:请检查 PurchaseParam 或设备设置。'); + KRCommonUtil.kr_hideLoading(); KRCommonUtil.kr_showToast('购买请求失败,请重试'); - // 注意:如果 Stream 此时没有收到 error 事件,这里可能需要手动清理。 } } catch (e) { - // 捕获任何意想不到的、未通过 Stream 报告的本地异常 - print('--- IAP 流程:buyNonConsumable 调用发生异常:$e ---'); - subscription.cancel(); - KRCommonUtil.kr_showToast('购买服务异常,请重试'); + KRCommonUtil.kr_hideLoading(); + KRCommonUtil.kr_showToast('购买服务异常,请重试$e'); } } else { print('⚠️ 未知的支付类型: ${checkoutResponse.type}'); @@ -1129,40 +1029,93 @@ class KRPurchaseMembershipController extends GetxController { }, ); } - /// 🔴【关键修复】在发起购买前,清理同一商品的 pending 交易 - Future _cleanPendingPurchase( - InAppPurchase iap, - String productId, - ) async { - print('🔧 IAP 清理 pending 交易开始'); - final completer = Completer(); + // -------------------- IAP 统一处理 -------------------- + void _handleIapUpdates(List purchases) async { + final iap = InAppPurchase.instance; + final pendingOrderNo = await IAPPendingOrderService.getPendingOrderNo(); + print('p.pendingOrderNo ${pendingOrderNo}'); + if (pendingOrderNo == null || pendingOrderNo.isEmpty) return; - late StreamSubscription> tempSub; + for (final p in purchases) { + print('p.status ${p.status}'); + if (p.status == PurchaseStatus.pending) continue; - tempSub = iap.purchaseStream.listen((purchases) async { - for (final p in purchases) { - if (p.productID != productId) continue; - - if (p.pendingCompletePurchase) { - print('🔧 发现 pending 交易,completePurchase'); - await iap.completePurchase(p); - } + if (p.status == PurchaseStatus.error) { + if (p.pendingCompletePurchase) await iap.completePurchase(p); + KRCommonUtil.kr_showToast('购买失败'); + continue; } - // 👇 只跑一次就够了 - await tempSub.cancel(); - completer.complete(); - }); + if (p.status == PurchaseStatus.purchased || p.status == PurchaseStatus.restored) { + KRCommonUtil.kr_showLoading(); + bool ok = false; + final either = await _kr_subscribeApi.kr_attachAppleIapTransaction(p.purchaseID!, pendingOrderNo); + either.fold((e) {}, (v) => ok = true); + // if (p.status == PurchaseStatus.purchased) { + // + // either.fold((e) {}, (v) => ok = true); + // } else if (p.status == PurchaseStatus.restored) { + // final either = await _kr_subscribeApi.kr_restoreAppleIapTransaction(p.purchaseID!, pendingOrderNo); + // either.fold((e) {}, (v) => ok = true); + // } - // 给 StoreKit 时间推送历史交易 - await Future.delayed(const Duration(milliseconds: 800)); + // ❌ 服务端失败,必须给用户反馈 + if (!ok) { + KRCommonUtil.kr_hideLoading(); + HIDialog.show( + title: '激活失败,请稍后重试', + message: '检测到未完成订单,需要恢复购买以完成订阅。', + confirmText: '恢复购买', + cancelText: '关闭', + barrierDismissible: false, + preventBackDismiss: true, + onConfirm: () { + kr_restorePurchases(); // 恢复未完成订单 + }, + ); + return; + } - if (!completer.isCompleted) { - await tempSub.cancel(); - completer.complete(); + if (p.pendingCompletePurchase) await iap.completePurchase(p); + await IAPPendingOrderService.clearPendingOrderNo(); + KRCommonUtil.kr_hideLoading(); + Get.toNamed( + Routes.KR_ORDER_STATUS, + arguments: { + 'order': pendingOrderNo, + 'payment_type': 'apple_iap', + 'checkout_type': 'ipa', + }, + ); + } } + } - print('🔧 IAP 清理 pending 交易完成'); + Future kr_restorePurchases({bool isShowLoading = true}) async { + print('重试'); + if(isShowLoading) { + KRCommonUtil.kr_showLoading(); + } + final iap = InAppPurchase.instance; + await iap.restorePurchases(); + + if(isShowLoading) { + KRCommonUtil.kr_hideLoading(); + } + } + // 1. 新增恢复购买点击计数器 + int _restoreClickCount = 0; + Future clearPendingOrderNo() async { + _restoreClickCount++; + // 2. 检查点击次数是否超过3次 + if (_restoreClickCount > 3) { + print('恢复购买点击次数超过3次,清理待处理订单...'); + await IAPPendingOrderService.clearPendingOrderNo(); + _restoreClickCount = 0; // 重置计数器 + KRCommonUtil.kr_hideLoading(); + KRCommonUtil.kr_showToast('已清理缓存,请重新尝试购买'); + return; // 终止后续操作 + } } } diff --git a/lib/app/modules/kr_purchase_membership/views/kr_purchase_membership_view.dart b/lib/app/modules/kr_purchase_membership/views/kr_purchase_membership_view.dart index c5265c5..43c3b1c 100755 --- a/lib/app/modules/kr_purchase_membership/views/kr_purchase_membership_view.dart +++ b/lib/app/modules/kr_purchase_membership/views/kr_purchase_membership_view.dart @@ -17,6 +17,9 @@ import 'package:kaer_with_panels/app/utils/kr_log_util.dart'; import 'package:kaer_with_panels/app/widgets/swipe/has_swipe_config.dart'; import 'package:kaer_with_panels/app/widgets/swipe/swipe_config.dart'; import 'dart:convert'; +import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart'; +import '../../../routes/app_pages.dart'; + /// 购买会员页面视图 class KRPurchaseMembershipView extends GetView @@ -29,135 +32,219 @@ class KRPurchaseMembershipView extends GetView @override Widget build(BuildContext context) { - return HIBaseScaffold( - showBackgroundImage: false, - title: '套餐选择', - subtitle: '*所有套餐均不限流量不限速度', - topContentAreaHeight: 110, - child: Stack( - children: [ - Positioned( - left: 0, - top: 0, - bottom: 0, - width: 20, - child: const _LeftEdgeSwipeBack(), - ), - Padding( - padding: const EdgeInsets.only(left: 20), - child: Stack( - children: [ - Obx(() { - return Positioned.fill( - bottom: 90.0, - child: SingleChildScrollView( - child: Column( - children: [ - /*_kr_buildAccountSection(context),*/ - if (controller.kr_isLoading.value) - Container( - height: MediaQuery.of(context).size.height * 0.5, - child: Center( - child: KRSimpleLoading( - color: Colors.white, - size: 50.0, - ), - ), - ) - else if (controller.kr_plans.isEmpty) - Container( - height: MediaQuery.of(context).size.height * 0.5, - child: Center( - child: Text( - AppTranslations.kr_purchaseMembership.noData, - style: KrAppTextStyle( - fontSize: 14, - color: Theme.of(context) - .textTheme - .bodyMedium - ?.color, + // 获取屏幕顶部的安全区域高度(状态栏高度) + final double topSafeArea = MediaQuery.of(context).padding.top; + final double buttonTopPosition = 80 + topSafeArea; + + // 在最外层包裹一个 Stack + return Stack( + children: [ + // 第一层:整个页面原来的内容 + HIBaseScaffold( + showBackgroundImage: false, + title: '套餐选择', + subtitle: '*所有套餐均不限流量不限速度', + topContentAreaHeight: 110, + child: Stack( + // 允许内部的 slogan 等元素超出边界 + clipBehavior: Clip.none, + children: [ + Positioned( + left: 0, + top: 0, + bottom: 0, + width: 20, + child: const _LeftEdgeSwipeBack(), + ), + Padding( + padding: const EdgeInsets.only(left: 20), + child: Stack( + children: [ + Obx(() { + return Positioned.fill( + bottom: 90.0, + child: SingleChildScrollView( + child: Column( + children: [ + /*_kr_buildAccountSection(context),*/ + if (controller.kr_isLoading.value) + Container( + height: + MediaQuery.of(context).size.height * 0.5, + child: Center( + child: KRSimpleLoading( + color: Colors.white, + size: 50.0, + ), ), - ), - ), - ) - else - Container( - margin: - EdgeInsets.only(left: 40.0 - 20, right: 40.0), - child: Column( - children: [ - // 套餐选择部分 - Container( - padding: EdgeInsets.all(0.0), - child: Column( - crossAxisAlignment: + ) + else if (controller.kr_plans.isEmpty) + Container( + height: + MediaQuery.of(context).size.height * 0.5, + child: Center( + child: Text( + AppTranslations + .kr_purchaseMembership.noData, + style: KrAppTextStyle( + fontSize: 14, + color: Theme.of(context) + .textTheme + .bodyMedium + ?.color, + ), + ), + ), + ) + else + Container( + margin: EdgeInsets.only( + left: 40.0 - 20, right: 40.0), + child: Column( + children: [ + // 套餐选择部分 + Container( + padding: EdgeInsets.all(0.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - // 使用 List.generate 动态创建卡片列表 - children: List.generate( - controller.kr_getTotalOptionsCount( - controller.kr_plans[controller - .kr_selectedPlanIndex - .value]), - (index) { - final plan = controller.kr_plans[ + children: [ + Column( + // 使用 List.generate 动态创建卡片列表 + children: List.generate( + controller + .kr_getTotalOptionsCount( + controller.kr_plans[ + controller + .kr_selectedPlanIndex + .value]), + (index) { + final plan = controller.kr_plans[ controller .kr_selectedPlanIndex .value]; - final discountIndex = + final discountIndex = plan.kr_discount.isEmpty ? null : index; - // 使用 Padding 来为每个卡片添加底部的间距,模拟 mainAxisSpacing - return Padding( - padding: EdgeInsets.only( - bottom: 8.0), - child: SizedBox( - height: 130.0, - child: + // 使用 Padding 来为每个卡片添加底部的间距,模拟 mainAxisSpacing + return Padding( + padding: EdgeInsets.only( + bottom: 8.0), + child: SizedBox( + height: 130.0, + child: _kr_buildPlanOptionCard( - plan, - controller - .kr_selectedPlanIndex - .value, - discountIndex, - context, - index, - ), - ), - ); - }, - ), + plan, + controller + .kr_selectedPlanIndex + .value, + discountIndex, + context, + index, + ), + ), + ); + }, + ), + ), + ], ), - ], - ), + ), + ], ), - ], - ), - ), - ], + ), + ], + ), + ), + ); + }), + Positioned( + top: 160.0, + right: 10.0, + child: GestureDetector( + onTap: () { + controller.clearPendingOrderNo(); + }, + child: KrLocalImage( + imageName: 'purchase_slogan', + imageType: ImageType.svg, + ), ), ), - ); - }), - Positioned( - top: 160.0, // 距离顶部的距离 - right: 10.0, // 固定在右侧 20.w 的位置 - child: KrLocalImage( - imageName: 'purchase_slogan', - imageType: ImageType.svg, - ), + const HIHelpEntrance(isLight: false) + ], ), - const HIHelpEntrance(isLight: false) - ], + ), + // --- 从这里移除按钮的 Positioned --- + ], + ), + ), + + // 第二层:恢复按钮,放在 HIBaseScaffold 的上层 + Positioned( + // top 值需要根据 HIBaseScaffold 的标题栏高度来精确调整 + // topContentAreaHeight 是 110,按钮高度是 20,让按钮下半部分贴着内容区顶部 + top: buttonTopPosition, // 110是标题区域高度, 10是按钮高度的一半 + left: 0, + right: 0, + child: _buildRestoreButton(), + ), + ], + ); + } + + /// 构建“恢复已购项目”按钮 + Widget _buildRestoreButton() { + if (!Platform.isIOS ) { + return const SizedBox.shrink(); + } + return Center( + child: GestureDetector( + onTap: () { + // 异步处理补单 + controller.kr_restorePurchases(isShowLoading: false); // 恢复未完成订单 + HIDialog.show( + title: '温馨提示', + message: '购买订单已确认,请耐心等待。若时长未到账,请联系在线客服处理。', + confirmText: '联系客服', + cancelText: '关闭', + autoClose: false, + showLoading: true, + barrierDismissible: false, + preventBackDismiss: true, + onConfirm: () async { + Get.toNamed(Routes.KR_CRISP); + }, + ); + }, + child: Container( + height: 20, + width: 94, // 保持固定宽度 94 + // 移除 padding 属性 + decoration: BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.circular(16), + ), + // 这个 Center 组件会使其子组件 Text 在 94x20 的空间内水平和垂直居中 + child: Center( + child: Text( + '恢复已购项目', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 12, + height: 1.1, + decoration: TextDecoration.none, + ), ), ), - ], + ), ), ); } + // 账号部分 Widget _kr_buildAccountSection(BuildContext context) { return Container( diff --git a/lib/app/services/api_service/api.dart b/lib/app/services/api_service/api.dart index 9364923..91d9588 100755 --- a/lib/app/services/api_service/api.dart +++ b/lib/app/services/api_service/api.dart @@ -2,12 +2,14 @@ abstract class Api { /// 游客登录查看是否已经注册 static const String kr_isRegister = "/v1/auth/check"; + /// 判断邮箱和当前设备是否已存在订阅 static const String kr_checkSubscription = "/v1/public/user/subscribe_status"; /// 注册 // static const String kr_register = "/v1/auth/register"; - static const String kr_register = "/v1/public/user/bind_email_with_verification"; + static const String kr_register = + "/v1/public/user/bind_email_with_verification"; /// 验证验证码 static const String kr_checkVerificationCode = "/v1/auth/check-code"; @@ -56,12 +58,10 @@ abstract class Api { static const String kr_getPackageList = "/v1/public/subscribe/list"; /// 获取用户已订阅套餐(用于判断是否购买过) - static const String kr_getAlreadySubscribe = - "/v1/public/user/subscribe"; + static const String kr_getAlreadySubscribe = "/v1/public/user/subscribe"; /// 获取用户可用订阅(与已订阅接口相同,OmnOem 项目中没有区分) - static const String kr_userAvailableSubscribe = - "/v1/public/user/subscribe"; + static const String kr_userAvailableSubscribe = "/v1/public/user/subscribe"; /// 续费 static const String kr_renewal = "/v1/public/order/renewal"; @@ -111,12 +111,13 @@ abstract class Api { /// 获取可用支付方式(公开接口) static const String kr_getPublicPaymentMethods = "/v1/public/payment/methods"; - static const String kr_attachAppleIapTransaction = "/v1/public/iap/apple/transactions/attach_by_id"; + static const String kr_attachAppleIapTransaction = + "/v1/public/iap/apple/transactions/attach_by_id"; + static const String kr_restoreAppleIap = "/v1/public/iap/apple/restore"; /// 获取用户信息(用于获取邀请码等) static const String kr_getUserInfo = "/v1/public/user/info"; /// 保存邀请码 static const String hi_invite_code = "/v1/public/user/bind_invite_code"; - } diff --git a/lib/app/services/api_service/kr_subscribe_api.dart b/lib/app/services/api_service/kr_subscribe_api.dart index 69abe27..528c59c 100755 --- a/lib/app/services/api_service/kr_subscribe_api.dart +++ b/lib/app/services/api_service/kr_subscribe_api.dart @@ -145,7 +145,7 @@ class KRSubscribeApi { data['order_no'] = orderNo; BaseResponse baseResponse = - await HttpUtil.getInstance().request( + await HttpUtil.getInstance().request( Api.kr_queryOrderStatus, data, method: HttpMethod.GET, @@ -271,7 +271,8 @@ class KRSubscribeApi { } /// 获取支付地址,跳转到付款地址(新版本:支持多种支付类型) - Future> kr_checkout(String orderId) async { + Future> kr_checkout( + String orderId) async { final Map data = {}; data['orderNo'] = orderId; data['returnUrl'] = AppConfig.getInstance().baseUrl; @@ -330,7 +331,8 @@ class KRSubscribeApi { return right(baseResponse.model); } - Future> kr_attachAppleIapTransaction(String transactionId, String orderNo) async { + Future> kr_attachAppleIapTransaction( + String transactionId, String orderNo) async { final Map data = {}; data['transaction_id'] = transactionId; data['order_no'] = orderNo; @@ -340,10 +342,32 @@ class KRSubscribeApi { Api.kr_attachAppleIapTransaction, data, method: HttpMethod.POST, - isShowLoading: true, + isShowLoading: false, ); if (!baseResponse.isSuccess) { - return left(HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode)); + return left( + HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode)); + } + + return right(baseResponse.model.kr_bl); + } + + Future> kr_restoreAppleIapTransaction( + String transactionId, String orderNo) async { + final Map data = {}; + data['transaction_id'] = [transactionId]; + data['order_no'] = orderNo; + + BaseResponse baseResponse = + await HttpUtil.getInstance().request( + Api.kr_restoreAppleIap, + data, + method: HttpMethod.POST, + isShowLoading: false, + ); + if (!baseResponse.isSuccess) { + return left( + HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode)); } return right(baseResponse.model.kr_bl); diff --git a/pubspec.lock b/pubspec.lock index c46b8f9..45b36cb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -603,6 +603,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" + flutter_keychain: + dependency: "direct main" + description: + name: flutter_keychain + sha256: "0d000c0e9b3c16fdec016df406b4e89e7195bf719ed0882157400f1e16323cf8" + url: "https://pub.dev" + source: hosted + version: "2.5.0" flutter_loggy: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index f56a83d..dbb9439 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -70,6 +70,7 @@ dependencies: # 存储和安全 flutter_udid: ^4.0.0 + flutter_keychain: 2.5.0 # 平台集成 window_manager: ^0.4.3