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 c5ecc37..2f92f55 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 @@ -620,6 +620,14 @@ class KRPurchaseMembershipController extends GetxController { ); } + /// iOS 固定价格映射表 (天数 : 价格) + static const Map _krIosPriceMap = { + 7: 3.29, + 30: 6.99, + 90: 14.99, + 365: 52.99, + }; + /// 获取套餐价格 /// discount: 0 表示无折扣(原价),其他值表示折扣百分比 double kr_getPlanPrice(KRPackageListItem plan, {int? discountIndex}) { @@ -628,6 +636,10 @@ class KRPurchaseMembershipController extends GetxController { discountIndex < plan.kr_discount.length) { // 计算折扣价格 final discount = plan.kr_discount[discountIndex]; + // --- iOS 环境:使用本地硬编码价格 --- + if (Platform.isIOS) { + return _krIosPriceMap[discount.kr_quantity] ?? (plan.kr_unitPrice / 100); + } // 如果 discount 是 0,则表示原价(100%) final discountRate = discount.kr_discount == 0 ? 100.0 : discount.kr_discount.toDouble(); @@ -645,6 +657,14 @@ class KRPurchaseMembershipController extends GetxController { discountIndex < plan.kr_discount.length) { // 计算折扣价格 final discount = plan.kr_discount[discountIndex]; + + // --- iOS 显式逻辑 --- + if (Platform.isIOS) { + // 1. 先从映射表取总价 + // 2. 直接除以数量 kr_quantity + return (_krIosPriceMap[discount.kr_quantity] ?? (plan.kr_unitPrice / 100)) / discount.kr_quantity; + } + return (plan.kr_unitPrice / 100) * (discount.kr_discount / 100); } return plan.kr_unitPrice / 100; @@ -701,6 +721,20 @@ class KRPurchaseMembershipController extends GetxController { /// 获取折扣文本 /// discount: 0 或 100 表示原价(无折扣),其他值表示折扣百分比 String kr_getDiscountText(KRPackageListItem plan, int discountIndex) { + if (Platform.isIOS) { + if (discountIndex >= 0 && discountIndex < plan.kr_discount.length) { + final discount = plan.kr_discount[discountIndex]; + if (discount.kr_quantity <= 7) return ''; + + double baseDayPrice = 3.29 / 7; + double currentDayPrice = (_krIosPriceMap[discount.kr_quantity] ?? 0.0) / discount.kr_quantity; + + if (currentDayPrice >= baseDayPrice - 0.001) return ''; + return '-${((1 - (currentDayPrice / baseDayPrice)) * 100).ceil()}%'; + } + return ''; + } + if (discountIndex >= 0 && discountIndex < plan.kr_discount.length) { final discount = plan.kr_discount[discountIndex]; // 折扣值为 0 或 100 都表示原价,不需要显示折扣 @@ -1025,7 +1059,18 @@ class KRPurchaseMembershipController extends GetxController { } } catch (e) { KRCommonUtil.kr_hideLoading(); - KRCommonUtil.kr_showToast('购买服务异常,请重试$e'); + // 识别是否是由于用户切出、弹窗被系统关闭引起的 + String errorMsg = e.toString(); + if (errorMsg.contains('storekit_error') || errorMsg.contains('canceled')) { + // 这种属于用户行为导致的异常,不需要显示过于严重的“服务异常” + print('ℹ️ [IAP] 支付流程被中断(用户退出或系统取消)'); + // 可以不报 Toast,或者报一个温和的提示 + KRCommonUtil.kr_showToast('支付已取消'); + } else { + // 真正的程序/网络异常 + KRCommonUtil.kr_showToast('购买服务暂不可用,请重试'); + print('❌ [IAP] 严重异常: $e'); + } } } else { print('⚠️ 未知的支付类型: ${checkoutResponse.type}'); diff --git a/lib/app/services/iap/iap_service.dart b/lib/app/services/iap/iap_service.dart index af49a8b..0e3c797 100644 --- a/lib/app/services/iap/iap_service.dart +++ b/lib/app/services/iap/iap_service.dart @@ -60,8 +60,13 @@ class KRIAPService extends GetxService { if (p.status == PurchaseStatus.pending) continue; if (p.status == PurchaseStatus.error) { - if (p.pendingCompletePurchase) await _iap.completePurchase(p); - KRCommonUtil.kr_showToast('购买失败'); + if (p.pendingCompletePurchase) { + await _iap.completePurchase(p); // ✅ 必须告诉苹果,这个错误我处理了 + } + // 如果是用户主动取消,在这里清理 orderNo 是安全的 + if (p.error?.message.contains('canceled') ?? false) { + await IAPPendingOrderService.clearPendingOrderNo(); + } KRCommonUtil.kr_hideLoading(); continue; } diff --git a/pubspec.yaml b/pubspec.yaml index f6778cc..ff6502f 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.0.4+108 +version: 0.0.4+109 environment: sdk: ">=3.5.0 <4.0.0"