feat: iap支付方式
This commit is contained in:
parent
f888b772c2
commit
60de644637
@ -1,5 +1,5 @@
|
||||
class KRPurchaseOrderNo {
|
||||
final String orderNo;
|
||||
final String orderNo;
|
||||
|
||||
KRPurchaseOrderNo({required this.orderNo});
|
||||
|
||||
@ -8,8 +8,6 @@ class KRPurchaseOrderNo {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class KRPurchaseOrderUrl {
|
||||
final String url;
|
||||
|
||||
@ -22,14 +20,16 @@ class KRPurchaseOrderUrl {
|
||||
|
||||
/// Checkout 响应(参考 Tauri 项目)
|
||||
class KRCheckoutResponse {
|
||||
final String type; // "url" | "qr" | "stripe"
|
||||
final String type; // "url" | "qr" | "stripe" | "apple_iap"
|
||||
final String? checkoutUrl;
|
||||
final KRStripePayment? stripe;
|
||||
final KRIpaPayment? ipa;
|
||||
|
||||
KRCheckoutResponse({
|
||||
required this.type,
|
||||
this.checkoutUrl,
|
||||
this.stripe,
|
||||
this.ipa,
|
||||
});
|
||||
|
||||
factory KRCheckoutResponse.fromJson(Map<String, dynamic> json) {
|
||||
@ -39,6 +39,7 @@ class KRCheckoutResponse {
|
||||
stripe: json['stripe'] != null
|
||||
? KRStripePayment.fromJson(json['stripe'])
|
||||
: null,
|
||||
ipa: json['ipa'] != null ? KRIpaPayment.fromJson(json['ipa']) : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -62,4 +63,25 @@ class KRStripePayment {
|
||||
publishableKey: json['publishable_key'] ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class KRIpaPayment {
|
||||
final String productId;
|
||||
final String? applicationUsername;
|
||||
final String? orderNo;
|
||||
|
||||
KRIpaPayment({
|
||||
required this.productId,
|
||||
this.applicationUsername,
|
||||
this.orderNo,
|
||||
});
|
||||
|
||||
factory KRIpaPayment.fromJson(Map<String, dynamic> json) {
|
||||
return KRIpaPayment(
|
||||
productId: json['product_id'] ?? json['productId'] ?? '',
|
||||
applicationUsername:
|
||||
json['application_username'] ?? json['applicationUsername'],
|
||||
orderNo: json['order_no'] ?? json['orderNo'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ class KRUserAvailableSubscribeList {
|
||||
});
|
||||
|
||||
factory KRUserAvailableSubscribeList.fromJson(Map<String, dynamic> json) {
|
||||
KRLogUtil.kr_i('订阅json列表: ${json}', tag: 'KRUserAvailableSubscribeList');
|
||||
// KRLogUtil.kr_i('订阅json列表: ${json}', tag: 'KRUserAvailableSubscribeList');
|
||||
final List<dynamic> listData = (json['list'] as List<dynamic>?) ?? const [];
|
||||
return KRUserAvailableSubscribeList(
|
||||
list: listData
|
||||
|
||||
@ -86,7 +86,7 @@ class _KRHomeViewState extends State<KRHomeView> {
|
||||
'Hi快VPN-网在我在,网快我快',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14.sp,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
@ -102,7 +102,7 @@ class _KRHomeViewState extends State<KRHomeView> {
|
||||
// --- 定义统一的文本样式,并设置行高 ---
|
||||
final normalStyle = TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12.sp,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.2,
|
||||
);
|
||||
|
||||
@ -18,6 +18,9 @@ import '../../../utils/kr_event_bus.dart';
|
||||
import '../../../network/http_util.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
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';
|
||||
|
||||
/// 会员购买控制器
|
||||
/// 负责处理会员套餐选择、支付方式选择和订阅流程
|
||||
@ -53,7 +56,8 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
print('💳 [PurchaseMembership] ========== Controller.onInit 被调用 ==========');
|
||||
print(
|
||||
'💳 [PurchaseMembership] ========== Controller.onInit 被调用 ==========');
|
||||
print('💳 [PurchaseMembership] 当前时间: ${DateTime.now()}');
|
||||
|
||||
// 🔧 紧急诊断:写文件确认购买页面Controller被初始化
|
||||
@ -61,11 +65,14 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final debugFile = File('${dir.path}/PURCHASE_CONTROLLER_DEBUG.txt');
|
||||
await debugFile.writeAsString(
|
||||
'=' * 60 + '\n'
|
||||
'💳 PurchaseMembershipController.onInit 被调用!\n'
|
||||
'时间: ${DateTime.now()}\n'
|
||||
'版本标识: Android15_Fix_v6_Final\n'
|
||||
'=' * 60 + '\n',
|
||||
'=' * 60 +
|
||||
'\n'
|
||||
'💳 PurchaseMembershipController.onInit 被调用!\n'
|
||||
'时间: ${DateTime.now()}\n'
|
||||
'版本标识: Android15_Fix_v6_Final\n'
|
||||
'=' *
|
||||
60 +
|
||||
'\n',
|
||||
mode: FileMode.append,
|
||||
);
|
||||
print('💳 [PurchaseMembership] ✅ 调试日志已写入文件: ${debugFile.path}');
|
||||
@ -178,7 +185,8 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
KRLogUtil.kr_w('获取支付方式超时', tag: 'PurchaseMembership');
|
||||
},
|
||||
);
|
||||
print('💳 [PurchaseMembership] ✓ 步骤4完成,支付方式数量: ${kr_paymentMethods.length}');
|
||||
print(
|
||||
'💳 [PurchaseMembership] ✓ 步骤4完成,支付方式数量: ${kr_paymentMethods.length}');
|
||||
|
||||
// 根据套餐数量决定是否显示套餐选择器
|
||||
kr_showPlanSelector.value = kr_plans.length > 1;
|
||||
@ -193,7 +201,8 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
bool domainSwitched = await KRDomain.kr_switchToNextDomain();
|
||||
|
||||
if (domainSwitched) {
|
||||
print('💳 [PurchaseMembership] ✓ 域名切换成功,当前域名: ${KRDomain.kr_currentDomain}');
|
||||
print(
|
||||
'💳 [PurchaseMembership] ✓ 域名切换成功,当前域名: ${KRDomain.kr_currentDomain}');
|
||||
print('💳 [PurchaseMembership] 🔄 使用新域名重试...');
|
||||
|
||||
// 更新 HttpUtil 的 baseUrl
|
||||
@ -300,7 +309,8 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
final either = await _kr_subscribeApi.kr_getPublicPaymentMethods();
|
||||
either.fold(
|
||||
(error) {
|
||||
KRLogUtil.kr_e('获取公开支付方式失败: ${error.msg}', tag: 'PurchaseMembershipController');
|
||||
KRLogUtil.kr_e('获取公开支付方式失败: ${error.msg}',
|
||||
tag: 'PurchaseMembershipController');
|
||||
},
|
||||
(responseData) {
|
||||
KRLogUtil.kr_i('✅ 获取公开支付方式成功', tag: 'PurchaseMembershipController');
|
||||
@ -310,14 +320,14 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
final paymentMethodsData = KRPaymentMethods.fromJson(responseData);
|
||||
kr_paymentMethods.value = paymentMethodsData.list;
|
||||
|
||||
KRLogUtil.kr_i('📊 支付方式数据已加载,共 ${kr_paymentMethods.length} 种', tag: 'PurchaseMembershipController');
|
||||
KRLogUtil.kr_i('📊 支付方式数据已加载,共 ${kr_paymentMethods.length} 种',
|
||||
tag: 'PurchaseMembershipController');
|
||||
|
||||
// 打印支付方式信息
|
||||
for (var method in kr_paymentMethods) {
|
||||
KRLogUtil.kr_i(
|
||||
'💳 支付方式: ${method.name} (ID: ${method.id}, Platform: ${method.platform})',
|
||||
tag: 'PurchaseMembershipController'
|
||||
);
|
||||
'💳 支付方式: ${method.name} (ID: ${method.id}, Platform: ${method.platform})',
|
||||
tag: 'PurchaseMembershipController');
|
||||
}
|
||||
|
||||
print('═══════════════════════════════════════');
|
||||
@ -479,7 +489,7 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
if (isIOS) {
|
||||
// 2. iOS 专属:查找 'stripe'
|
||||
final stripeMethod = kr_paymentMethods.firstWhere(
|
||||
(method) => method.platform == 'Stripe',
|
||||
(method) => method.platform == 'apple_iap',
|
||||
);
|
||||
|
||||
if (stripeMethod != null) {
|
||||
@ -516,11 +526,12 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
.any((subscribe) => subscribe.subscribeId == selectedPlan.kr_id);
|
||||
final subscribeId = isRenewal
|
||||
? _kr_alreadySubscribe
|
||||
.firstWhere(
|
||||
(subscribe) => subscribe.subscribeId == selectedPlan.kr_id,
|
||||
orElse: () => KRAlreadySubscribe(userSubscribeId: 0, subscribeId: 0), // 默认值
|
||||
)
|
||||
.userSubscribeId
|
||||
.firstWhere(
|
||||
(subscribe) => subscribe.subscribeId == selectedPlan.kr_id,
|
||||
orElse: () =>
|
||||
KRAlreadySubscribe(userSubscribeId: 0, subscribeId: 0), // 默认值
|
||||
)
|
||||
.userSubscribeId
|
||||
: 0;
|
||||
|
||||
// 根据判断结果调用不同的接口
|
||||
@ -539,7 +550,7 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
);
|
||||
|
||||
purchaseEither.fold(
|
||||
(error) {
|
||||
(error) {
|
||||
print('❌ 请求失败:');
|
||||
print(' 错误码: ${error.code}');
|
||||
print(' 错误信息: ${error.msg}');
|
||||
@ -579,7 +590,8 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
// 计算折扣价格
|
||||
final discount = plan.kr_discount[discountIndex];
|
||||
// 如果 discount 是 0,则表示原价(100%)
|
||||
final discountRate = discount.kr_discount == 0 ? 100.0 : discount.kr_discount.toDouble();
|
||||
final discountRate =
|
||||
discount.kr_discount == 0 ? 100.0 : discount.kr_discount.toDouble();
|
||||
return (plan.kr_unitPrice / 100) *
|
||||
discount.kr_quantity *
|
||||
(discountRate / 100);
|
||||
@ -594,8 +606,7 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
discountIndex < plan.kr_discount.length) {
|
||||
// 计算折扣价格
|
||||
final discount = plan.kr_discount[discountIndex];
|
||||
return (plan.kr_unitPrice / 100) *
|
||||
(discount.kr_discount / 100);
|
||||
return (plan.kr_unitPrice / 100) * (discount.kr_discount / 100);
|
||||
}
|
||||
return plan.kr_unitPrice / 100;
|
||||
}
|
||||
@ -776,20 +787,21 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
final checkoutEither = await _kr_subscribeApi.kr_checkout(orderNo);
|
||||
|
||||
checkoutEither.fold(
|
||||
(error) {
|
||||
(error) {
|
||||
print('❌ Checkout 失败:');
|
||||
print(' 错误码: ${error.code}');
|
||||
print(' 错误信息: ${error.msg}');
|
||||
print('═══════════════════════════════════════');
|
||||
KRCommonUtil.kr_showToast(error.msg);
|
||||
},
|
||||
(checkoutResponse) async {
|
||||
(checkoutResponse) async {
|
||||
print('✅ Checkout 成功:');
|
||||
print(' 支付类型: ${checkoutResponse.type}');
|
||||
|
||||
if (checkoutResponse.type == 'url') {
|
||||
// URL 类型:在浏览器中打开支付链接,同时跳转到订单状态页面
|
||||
if (checkoutResponse.checkoutUrl != null && checkoutResponse.checkoutUrl!.isNotEmpty) {
|
||||
if (checkoutResponse.checkoutUrl != null &&
|
||||
checkoutResponse.checkoutUrl!.isNotEmpty) {
|
||||
print(' 支付链接: ${checkoutResponse.checkoutUrl}');
|
||||
print('🌐 正在用外部浏览器打开支付链接...');
|
||||
print('═══════════════════════════════════════');
|
||||
@ -839,7 +851,8 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
} else if (checkoutResponse.type == 'stripe') {
|
||||
// Stripe 类型:显示 Stripe 支付表单
|
||||
print(' Stripe Method: ${checkoutResponse.stripe?.method}');
|
||||
print(' Stripe Client Secret: ${checkoutResponse.stripe?.clientSecret}');
|
||||
print(
|
||||
' Stripe Client Secret: ${checkoutResponse.stripe?.clientSecret}');
|
||||
print('💳 显示 Stripe 支付表单...');
|
||||
print('═══════════════════════════════════════');
|
||||
|
||||
@ -854,7 +867,8 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
KRCommonUtil.kr_showToast('Apple Pay 仅在 iOS 可用');
|
||||
return;
|
||||
}
|
||||
print('Platform: ${Platform.operatingSystem} ${Platform.operatingSystemVersion}');
|
||||
print(
|
||||
'Platform: ${Platform.operatingSystem} ${Platform.operatingSystemVersion}');
|
||||
print('Merchant identifier: ${Stripe.merchantIdentifier}');
|
||||
// 检查设备是否支持 Platform Pay(Apple Pay)
|
||||
final supported = await Stripe.instance.isPlatformPaySupported();
|
||||
@ -870,10 +884,9 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
cartItems: [
|
||||
ApplePayCartSummaryItem.immediate(
|
||||
label: 'Hi快VPN 服务',
|
||||
amount: kr_getPlanPrice(
|
||||
kr_plans[kr_selectedPlanIndex.value],
|
||||
discountIndex: kr_selectedDiscountIndex.value
|
||||
).toStringAsFixed(2),
|
||||
amount: kr_getPlanPrice(kr_plans[kr_selectedPlanIndex.value],
|
||||
discountIndex: kr_selectedDiscountIndex.value)
|
||||
.toStringAsFixed(2),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -897,10 +910,11 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
'stripe': checkoutResponse.stripe,
|
||||
},
|
||||
);
|
||||
} else if (checkoutResponse.type == 'balance') {
|
||||
} else if (checkoutResponse.type == 'balance') {
|
||||
// Stripe 类型:显示 Stripe 支付表单
|
||||
print(' Stripe Method: ${checkoutResponse.stripe?.method}');
|
||||
print(' Stripe Client Secret: ${checkoutResponse.stripe?.clientSecret}');
|
||||
print(
|
||||
' Stripe Client Secret: ${checkoutResponse.stripe?.clientSecret}');
|
||||
print('💳 显示 Stripe 支付表单...');
|
||||
print('═══════════════════════════════════════');
|
||||
|
||||
@ -913,13 +927,242 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
'checkout_type': 'balance',
|
||||
},
|
||||
);
|
||||
} else if (checkoutResponse.type == 'apple_iap') {
|
||||
if (!Platform.isIOS) {
|
||||
KRCommonUtil.kr_showToast('仅限在 iOS 设备上购买');
|
||||
return;
|
||||
}
|
||||
|
||||
final KRIpaPayment iapParams = (checkoutResponse.ipa == null ||
|
||||
(checkoutResponse.ipa?.productId.isEmpty ?? true))
|
||||
? KRIpaPayment(
|
||||
productId: 'com.hifastvpn.vip.day${kr_getSelectedQuantity()}')
|
||||
: checkoutResponse.ipa!;
|
||||
|
||||
final iap = InAppPurchase.instance;
|
||||
final available = await iap.isAvailable();
|
||||
if (!available) {
|
||||
print('IAP 不可用');
|
||||
KRCommonUtil.kr_showToast('App Store 不可用');
|
||||
return;
|
||||
}
|
||||
|
||||
final productIds = {iapParams.productId};
|
||||
final productDetailsResponse =
|
||||
await iap.queryProductDetails(productIds);
|
||||
if (productDetailsResponse.error != null) {
|
||||
final e = productDetailsResponse.error!;
|
||||
print('IAP 查询商品错误 code: ${e.code}');
|
||||
print('IAP 查询商品错误 message: ${e.message}');
|
||||
print('IAP 查询商品错误 iapParams.productId: ${iapParams.productId}');
|
||||
KRCommonUtil.kr_showToast('无法获取商品信息: ${e.message}');
|
||||
return;
|
||||
}
|
||||
if (productDetailsResponse.productDetails.isEmpty) {
|
||||
print('IAP 商品列表为空 productId: ${iapParams.productId}');
|
||||
KRCommonUtil.kr_showToast('未找到对应商品');
|
||||
return;
|
||||
}
|
||||
final product = productDetailsResponse.productDetails.first;
|
||||
|
||||
final purchaseParam = PurchaseParam(
|
||||
productDetails: product,
|
||||
applicationUserName: iapParams.applicationUsername,
|
||||
);
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// 假设 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 {
|
||||
final result =
|
||||
await iap.buyNonConsumable(purchaseParam: purchaseParam);
|
||||
print('--- IAP 流程:buyNonConsumable 调用返回。返回值:$result ---');
|
||||
|
||||
if (!result) {
|
||||
// 如果返回 false,说明本地请求失败
|
||||
print('IAP 购买请求本地失败:请检查 PurchaseParam 或设备设置。');
|
||||
KRCommonUtil.kr_showToast('购买请求失败,请重试');
|
||||
// 注意:如果 Stream 此时没有收到 error 事件,这里可能需要手动清理。
|
||||
}
|
||||
} catch (e) {
|
||||
// 捕获任何意想不到的、未通过 Stream 报告的本地异常
|
||||
print('--- IAP 流程:buyNonConsumable 调用发生异常:$e ---');
|
||||
subscription.cancel();
|
||||
KRCommonUtil.kr_showToast('购买服务异常,请重试');
|
||||
}
|
||||
} else {
|
||||
print('⚠️ 未知的支付类型: ${checkoutResponse.type}');
|
||||
print('═══════════════════════════════════════');
|
||||
// KRCommonUtil.kr_showToast('不支持的支付类型: ${checkoutResponse.type}');
|
||||
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
/// 🔴【关键修复】在发起购买前,清理同一商品的 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();
|
||||
}
|
||||
|
||||
print('🔧 IAP 清理 pending 交易完成');
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,8 +30,8 @@ class BaseResponse<T> {
|
||||
if (cipherText.isNotEmpty && nonce.isNotEmpty) {
|
||||
try {
|
||||
if (kDebugMode) {
|
||||
print('═══════════════════════════════════════');
|
||||
print('🔐 检测到加密响应,开始解密...');
|
||||
// print('═══════════════════════════════════════');
|
||||
// print('🔐 检测到加密响应,开始解密...');
|
||||
print('📥 加密数据长度: ${cipherText.length} 字符');
|
||||
print('⏰ 时间戳: $nonce');
|
||||
}
|
||||
|
||||
@ -431,9 +431,6 @@ class _KRSimpleHttpInterceptor extends Interceptor {
|
||||
if (kDebugMode) {
|
||||
print('');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('🔐 检测到加密请求,正在解密...');
|
||||
}
|
||||
// 尝试解密并打印原始数据
|
||||
final encryptedData = data['data'] as String;
|
||||
final nonce = data['time'] as String;
|
||||
|
||||
@ -111,6 +111,8 @@ 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_getUserInfo = "/v1/public/user/info";
|
||||
|
||||
|
||||
@ -329,4 +329,23 @@ class KRSubscribeApi {
|
||||
|
||||
return right(baseResponse.model);
|
||||
}
|
||||
|
||||
Future<Either<HttpError, bool>> kr_attachAppleIapTransaction(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_attachAppleIapTransaction,
|
||||
data,
|
||||
method: HttpMethod.POST,
|
||||
isShowLoading: true,
|
||||
);
|
||||
if (!baseResponse.isSuccess) {
|
||||
return left(HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
|
||||
}
|
||||
|
||||
return right(baseResponse.model.kr_bl);
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,15 +250,15 @@ class KRSubscribeService {
|
||||
}
|
||||
|
||||
kr_currentSubscribe.value = updatedSubscribe;
|
||||
KRLogUtil.kr_i('更新当前订阅信息', tag: 'SubscribeService');
|
||||
// KRLogUtil.kr_i('更新当前订阅信息', tag: 'SubscribeService');
|
||||
|
||||
// 更新可用订阅列表
|
||||
kr_availableSubscribes.assignAll(subscribes);
|
||||
}
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('获取可用订阅列表成功: ${subscribes.length} 个订阅',
|
||||
tag: 'SubscribeService');
|
||||
// KRLogUtil.kr_i('获取可用订阅列表成功: ${subscribes.length} 个订阅',
|
||||
// tag: 'SubscribeService');
|
||||
KRLogUtil.kr_i(
|
||||
'订阅列表: ${subscribes.map((s) => '${s.name}(${s.id})').join(', ')}',
|
||||
tag: 'SubscribeService');
|
||||
|
||||
32
pubspec.lock
32
pubspec.lock
@ -861,6 +861,38 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
in_app_purchase:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: in_app_purchase
|
||||
sha256: "11a40f148eeb4f681a0572003e2b33432e110c90c1bbb4f9ef83b81ec0c4f737"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
in_app_purchase_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: in_app_purchase_android
|
||||
sha256: "45ae4fe253f85b4fcc58b421fe137f6e48aca16bf8a618cd760cb0542e7f854e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.0"
|
||||
in_app_purchase_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: in_app_purchase_platform_interface
|
||||
sha256: "1d353d38251da5b9fea6635c0ebfc6bb17a2d28d0e86ea5e083bf64244f1fb4c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
in_app_purchase_storekit:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: in_app_purchase_storekit
|
||||
sha256: "6ce1361278cacc0481508989ba419b2c9f46a2b0dc54b3fe54f5ee63c2718fef"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.22+1"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@ -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+107
|
||||
version: 0.0.4+108
|
||||
|
||||
environment:
|
||||
sdk: ">=3.5.0 <4.0.0"
|
||||
@ -113,6 +113,7 @@ dependencies:
|
||||
tray_manager: ^0.2.0
|
||||
device_info_plus: ^11.3.0
|
||||
flutter_stripe: ^10.1.0
|
||||
in_app_purchase: 3.2.1
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user