Compare commits

...

2 Commits

10 changed files with 550 additions and 366 deletions

View File

@ -37,6 +37,8 @@ import 'package:flutter/foundation.dart';
import 'package:kaer_with_panels/app/utils/kr_init_log_collector.dart'; // 🔧
import 'package:kaer_with_panels/app/utils/kr_latency_tester.dart'; // 🔧
import 'package:kaer_with_panels/app/utils/account_guard.dart';
import 'package:kaer_with_panels/app/services/iap/iap_service.dart';
class KRHomeController extends GetxController with WidgetsBindingObserver {
// 🔧

View File

@ -21,6 +21,9 @@ 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';
import 'package:kaer_with_panels/app/services/iap/iap_service.dart';
///
///
@ -445,6 +448,29 @@ class KRPurchaseMembershipController extends GetxController {
print('开始订阅流程,选定支付');
kr_errorMessage.value = '';
// 1. iOS
if (Platform.isIOS) {
final existingOrderNo = await IAPPendingOrderService.getPendingOrderNo();
if (existingOrderNo != null && existingOrderNo.isNotEmpty) {
print('🛑 [IAP] 拦截成功:发现未结单 $existingOrderNo');
await HIDialog.show(
title: '检测到待激活订单',
message: '您有一笔订单已支付成功,但尚未同步会员权益。请点击“立即激活”完成同步。',
confirmText: '立即激活',
cancelText: '取消',
barrierDismissible: false,
onConfirm: () {
// 🚀 Service
// PurchaseStream
KRIAPService.instance.retryUnfinishedTransactions();
},
);
return; // 🛑 return
}
}
try {
await kr_processPurchaseAndCheckout();
} catch (e) {
@ -472,7 +498,7 @@ class KRPurchaseMembershipController extends GetxController {
///
Future<void> kr_processPurchaseAndCheckout() async {
final selectedPlan = kr_plans[kr_selectedPlanIndex.value];
// =========================================================================
// =========================================================================
// 🔽 iOS 使 Stripe 🔽
// =========================================================================
final isIOS = Platform.isIOS;
@ -936,7 +962,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 +991,28 @@ 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<List<PurchaseDetails>> 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 ---');
try {
await IAPPendingOrderService.setPendingOrderNo(orderNo);
KRCommonUtil.kr_showLoading();
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 +1022,32 @@ class KRPurchaseMembershipController extends GetxController {
},
);
}
/// 🔴 pending
Future<void> _cleanPendingPurchase(
InAppPurchase iap,
String productId,
) async {
print('🔧 IAP 清理 pending 交易开始');
final completer = Completer<void>();
late StreamSubscription<List<PurchaseDetails>> tempSub;
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);
}
}
// 👇
await tempSub.cancel();
completer.complete();
});
// StoreKit
await Future.delayed(const Duration(milliseconds: 800));
if (!completer.isCompleted) {
await tempSub.cancel();
completer.complete();
Future<void> kr_restorePurchases({bool isShowLoading = true}) async {
print('重试');
if(isShowLoading) {
KRCommonUtil.kr_showLoading();
}
final iap = InAppPurchase.instance;
await iap.restorePurchases();
print('🔧 IAP 清理 pending 交易完成');
if(isShowLoading) {
KRCommonUtil.kr_hideLoading();
}
}
// 1.
int _restoreClickCount = 0;
Future<void> clearPendingOrderNo() async {
_restoreClickCount++;
// 2. 3
if (_restoreClickCount > 3) {
print('恢复购买点击次数超过3次清理待处理订单...');
await IAPPendingOrderService.clearPendingOrderNo();
_restoreClickCount = 0; //
KRCommonUtil.kr_hideLoading();
KRCommonUtil.kr_showToast('已清理缓存,请重新尝试购买');
return; //
}
}
}

View File

@ -17,6 +17,11 @@ 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';
import 'package:kaer_with_panels/app/services/iap/iap_service.dart';
///
class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController>
@ -29,135 +34,219 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController>
@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: () async{
//
await KRIAPService.instance.retryUnfinishedTransactions();
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(

View File

@ -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";
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";
}

View File

@ -145,7 +145,7 @@ class KRSubscribeApi {
data['order_no'] = orderNo;
BaseResponse<KROrderStatus> baseResponse =
await HttpUtil.getInstance().request<KROrderStatus>(
await HttpUtil.getInstance().request<KROrderStatus>(
Api.kr_queryOrderStatus,
data,
method: HttpMethod.GET,
@ -271,7 +271,8 @@ class KRSubscribeApi {
}
/// ,
Future<Either<HttpError, KRCheckoutResponse>> kr_checkout(String orderId) async {
Future<Either<HttpError, KRCheckoutResponse>> kr_checkout(
String orderId) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['orderNo'] = orderId;
data['returnUrl'] = AppConfig.getInstance().baseUrl;
@ -330,9 +331,10 @@ class KRSubscribeApi {
return right(baseResponse.model);
}
Future<Either<HttpError, bool>> kr_attachAppleIapTransaction(String transactionId, String orderNo) async {
Future<Either<HttpError, bool>> kr_attachAppleIapTransaction(
String jwsData, String orderNo) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['transaction_id'] = transactionId;
data['signed_transaction_jws'] = jwsData;
data['order_no'] = orderNo;
BaseResponse<KRStatus> baseResponse =
@ -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<Either<HttpError, bool>> kr_restoreAppleIapTransaction(
String transactionId, String orderNo) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['transaction_id'] = [transactionId];
data['order_no'] = orderNo;
BaseResponse<KRStatus> baseResponse =
await HttpUtil.getInstance().request<KRStatus>(
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);

View File

@ -0,0 +1,17 @@
import 'package:flutter_keychain/flutter_keychain.dart';
class IAPPendingOrderService {
static const _key = 'hi_iap_pending_order_no';
static Future<void> setPendingOrderNo(String orderNo) async {
await FlutterKeychain.put(key: _key, value: orderNo);
}
static Future<String?> getPendingOrderNo() async {
return await FlutterKeychain.get(key: _key);
}
static Future<void> clearPendingOrderNo() async {
await FlutterKeychain.remove(key: _key);
}
}

View File

@ -0,0 +1,175 @@
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();
}
}

View File

@ -5,6 +5,7 @@ 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/utils/kr_common_util.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 {
final app = KRAppRunData.getInstance();
@ -27,6 +28,7 @@ Future<bool> ensureAccountExists() async {
return false;
}
KRIAPService.instance.setup();
var crispId = config.kr_website_id;
var deviceLimitText = config.device_limit;
var deviceLimit = int.tryParse(deviceLimitText) ?? 0;

View File

@ -5,23 +5,18 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
url: "https://pub.dev"
source: hosted
version: "72.0.0"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.3.2"
version: "67.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
url: "https://pub.dev"
source: hosted
version: "6.7.0"
version: "6.4.1"
analyzer_plugin:
dependency: transitive
description:
@ -194,10 +189,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
url: "https://pub.dev"
source: hosted
version: "1.18.0"
version: "1.19.0"
color:
dependency: transitive
description:
@ -298,18 +293,10 @@ packages:
dependency: transitive
description:
name: custom_lint_core
sha256: "02450c3e45e2a6e8b26c4d16687596ab3c4644dd5792e3313aa9ceba5a49b7f5"
sha256: a85e8f78f4c52f6c63cdaf8c872eb573db0231dcdf3c3a5906d493c1f8bc20e6
url: "https://pub.dev"
source: hosted
version: "0.7.0"
custom_lint_visitor:
dependency: transitive
description:
name: custom_lint_visitor
sha256: "8aeb3b6ae2bb765e7716b93d1d10e8356d04e0ff6d7592de6ee04e0dd7d6587d"
url: "https://pub.dev"
source: hosted
version: "1.0.0+6.7.0"
version: "0.6.3"
dart_earcut:
dependency: transitive
description:
@ -338,10 +325,10 @@ packages:
dependency: transitive
description:
name: dart_style
sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab"
sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9"
url: "https://pub.dev"
source: hosted
version: "2.3.7"
version: "2.3.6"
dartx:
dependency: "direct main"
description:
@ -402,18 +389,18 @@ packages:
dependency: transitive
description:
name: drift
sha256: af3941e4d544727b2eb80590eb64e9cb8d77cd68c7690265502ea6a2427aa621
sha256: df027d168a2985a2e9da900adeba2ab0136f0d84436592cf3cd5135f82c8579c
url: "https://pub.dev"
source: hosted
version: "2.23.1"
version: "2.21.0"
drift_dev:
dependency: "direct dev"
description:
name: drift_dev
sha256: fa98fdbb7303a1b5b2dc110cb516eda2253a5d291680f8cbc72b1af24099f7f9
sha256: "623649abe932fc17bd32e578e7e05f7ac5e7dd0b33e6c8669a0634105d1389bf"
url: "https://pub.dev"
source: hosted
version: "2.23.1"
version: "2.21.2"
easy_refresh:
dependency: "direct main"
description:
@ -511,18 +498,18 @@ packages:
dependency: transitive
description:
name: flutter_gen_core
sha256: "3eaa2d3d8be58267ac4cd5e215ac965dd23cae0410dc073de2e82e227be32bfc"
sha256: "53890b653738f34363d9f0d40f82104c261716bd551d3ba65f648770b6764c21"
url: "https://pub.dev"
source: hosted
version: "5.10.0"
version: "5.9.0"
flutter_gen_runner:
dependency: "direct dev"
description:
name: flutter_gen_runner
sha256: e74b4ead01df3e8f02e73a26ca856759dbbe8cb3fd60941ba9f4005cd0cd19c9
sha256: de70b42eb5329f712c8b041069d081ad5fb5109f32d6d1ea9c1b39596786215d
url: "https://pub.dev"
source: hosted
version: "5.10.0"
version: "5.9.0"
flutter_hooks:
dependency: transitive
description:
@ -603,6 +590,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:
@ -697,10 +692,10 @@ packages:
dependency: "direct dev"
description:
name: freezed
sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e"
sha256: a434911f643466d78462625df76fd9eb13e57348ff43fe1f77bbe909522c67a1
url: "https://pub.dev"
source: hosted
version: "2.5.7"
version: "2.5.2"
freezed_annotation:
dependency: "direct main"
description:
@ -865,10 +860,10 @@ packages:
dependency: "direct main"
description:
name: in_app_purchase
sha256: "11a40f148eeb4f681a0572003e2b33432e110c90c1bbb4f9ef83b81ec0c4f737"
sha256: "5cddd7f463f3bddb1d37a72b95066e840d5822d66291331d7f8f05ce32c24b6c"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
version: "3.2.3"
in_app_purchase_android:
dependency: transitive
description:
@ -886,13 +881,13 @@ packages:
source: hosted
version: "1.4.0"
in_app_purchase_storekit:
dependency: transitive
dependency: "direct main"
description:
name: in_app_purchase_storekit
sha256: "6ce1361278cacc0481508989ba419b2c9f46a2b0dc54b3fe54f5ee63c2718fef"
sha256: aedbeea5beae10af3e5c380b65049842715b2bb014983e2a48b9006473f33cd9
url: "https://pub.dev"
source: hosted
version: "0.3.22+1"
version: "0.4.4"
intl:
dependency: transitive
description:
@ -953,10 +948,10 @@ packages:
dependency: "direct dev"
description:
name: json_serializable
sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c
sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b
url: "https://pub.dev"
source: hosted
version: "6.9.0"
version: "6.8.0"
latlong2:
dependency: "direct main"
description:
@ -969,18 +964,18 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
url: "https://pub.dev"
source: hosted
version: "10.0.5"
version: "10.0.7"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
version: "3.0.8"
leak_tracker_testing:
dependency: transitive
description:
@ -1037,14 +1032,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.3"
macros:
dependency: transitive
description:
name: macros
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
url: "https://pub.dev"
source: hosted
version: "0.1.2-main.4"
markdown:
dependency: transitive
description:
@ -1393,10 +1380,10 @@ packages:
dependency: transitive
description:
name: riverpod_analyzer_utils
sha256: c6b8222b2b483cb87ae77ad147d6408f400c64f060df7a225b127f4afef4f8c8
sha256: "8b71f03fc47ae27d13769496a1746332df4cec43918aeba9aff1e232783a780f"
url: "https://pub.dev"
source: hosted
version: "0.5.8"
version: "0.5.1"
riverpod_annotation:
dependency: "direct main"
description:
@ -1409,10 +1396,10 @@ packages:
dependency: "direct dev"
description:
name: riverpod_generator
sha256: "63546d70952015f0981361636bf8f356d9cfd9d7f6f0815e3c07789a41233188"
sha256: d451608bf17a372025fc36058863737636625dfdb7e3cbf6142e0dfeb366ab22
url: "https://pub.dev"
source: hosted
version: "2.6.3"
version: "2.4.0"
rxdart:
dependency: "direct main"
description:
@ -1505,7 +1492,7 @@ packages:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
version: "0.0.0"
slang:
dependency: "direct main"
description:
@ -1574,18 +1561,18 @@ packages:
dependency: transitive
description:
name: sqlparser
sha256: "4cad4b2c5f63dc9ea1a8dcffb58cf762322bea5dd8836870164a65e913bdae41"
sha256: d77749237609784e337ec36c979d41f6f38a7b279df98622ae23929c8eb954a4
url: "https://pub.dev"
source: hosted
version: "0.40.0"
version: "0.39.2"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
version: "1.12.0"
state_notifier:
dependency: transitive
description:
@ -1614,10 +1601,10 @@ packages:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.3.0"
stripe_android:
dependency: transitive
description:
@ -1654,10 +1641,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
version: "0.7.3"
time:
dependency: transitive
description:
@ -1822,10 +1809,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
url: "https://pub.dev"
source: hosted
version: "14.2.5"
version: "14.3.0"
watcher:
dependency: transitive
description:
@ -1955,5 +1942,5 @@ packages:
source: hosted
version: "2.2.2"
sdks:
dart: ">=3.5.0 <4.0.0"
flutter: ">=3.24.0"
dart: ">=3.6.0 <4.0.0"
flutter: ">=3.27.0"

View File

@ -70,6 +70,7 @@ dependencies:
# 存储和安全
flutter_udid: ^4.0.0
flutter_keychain: 2.5.0
# 平台集成
window_manager: ^0.4.3
@ -113,7 +114,8 @@ dependencies:
tray_manager: ^0.2.0
device_info_plus: ^11.3.0
flutter_stripe: ^10.1.0
in_app_purchase: 3.2.1
in_app_purchase: ^3.2.3
in_app_purchase_storekit: ^0.4.2
dev_dependencies:
flutter_test:
sdk: flutter