新增支付流程,新增支付页面等待倒计时,自动检查订单状态等
This commit is contained in:
parent
15d2e1047b
commit
2e7e85fb27
@ -43,6 +43,8 @@ abstract class EntityFromJsonUtil {
|
|||||||
return KRPurchaseOrderNo.fromJson(json) as T;
|
return KRPurchaseOrderNo.fromJson(json) as T;
|
||||||
case "KRPurchaseOrderUrl":
|
case "KRPurchaseOrderUrl":
|
||||||
return KRPurchaseOrderUrl.fromJson(json) as T;
|
return KRPurchaseOrderUrl.fromJson(json) as T;
|
||||||
|
case "KRCheckoutResponse":
|
||||||
|
return KRCheckoutResponse.fromJson(json) as T;
|
||||||
case "KROrderStatus":
|
case "KROrderStatus":
|
||||||
return KROrderStatus.fromJson(json) as T;
|
return KROrderStatus.fromJson(json) as T;
|
||||||
case "KRAlreadySubscribeList":
|
case "KRAlreadySubscribeList":
|
||||||
|
|||||||
@ -19,3 +19,47 @@ class KRPurchaseOrderUrl {
|
|||||||
return KRPurchaseOrderUrl(url: json['checkout_url'] ?? '');
|
return KRPurchaseOrderUrl(url: json['checkout_url'] ?? '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checkout 响应(参考 Tauri 项目)
|
||||||
|
class KRCheckoutResponse {
|
||||||
|
final String type; // "url" | "qr" | "stripe"
|
||||||
|
final String? checkoutUrl;
|
||||||
|
final KRStripePayment? stripe;
|
||||||
|
|
||||||
|
KRCheckoutResponse({
|
||||||
|
required this.type,
|
||||||
|
this.checkoutUrl,
|
||||||
|
this.stripe,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory KRCheckoutResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return KRCheckoutResponse(
|
||||||
|
type: json['type'] ?? 'url',
|
||||||
|
checkoutUrl: json['checkout_url'],
|
||||||
|
stripe: json['stripe'] != null
|
||||||
|
? KRStripePayment.fromJson(json['stripe'])
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stripe 支付信息
|
||||||
|
class KRStripePayment {
|
||||||
|
final String method;
|
||||||
|
final String clientSecret;
|
||||||
|
final String publishableKey;
|
||||||
|
|
||||||
|
KRStripePayment({
|
||||||
|
required this.method,
|
||||||
|
required this.clientSecret,
|
||||||
|
required this.publishableKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory KRStripePayment.fromJson(Map<String, dynamic> json) {
|
||||||
|
return KRStripePayment(
|
||||||
|
method: json['method'] ?? '',
|
||||||
|
clientSecret: json['client_secret'] ?? '',
|
||||||
|
publishableKey: json['publishable_key'] ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import '../../../utils/kr_common_util.dart';
|
|||||||
import '../../../localization/app_translations.dart';
|
import '../../../localization/app_translations.dart';
|
||||||
import '../../../utils/kr_log_util.dart';
|
import '../../../utils/kr_log_util.dart';
|
||||||
|
|
||||||
/// 订单状态控制器
|
/// 订单状态控制器(参考 Tauri 项目实现)
|
||||||
class KROrderStatusController extends GetxController {
|
class KROrderStatusController extends GetxController {
|
||||||
/// API服务
|
/// API服务
|
||||||
final KRSubscribeApi kr_subscribeApi = KRSubscribeApi();
|
final KRSubscribeApi kr_subscribeApi = KRSubscribeApi();
|
||||||
@ -23,7 +23,7 @@ class KROrderStatusController extends GetxController {
|
|||||||
final RxBool kr_isLoading = true.obs;
|
final RxBool kr_isLoading = true.obs;
|
||||||
|
|
||||||
/// 支付URL
|
/// 支付URL
|
||||||
final String kr_paymentUrl = Get.arguments['url'] as String;
|
final String kr_paymentUrl = Get.arguments['url'] as String? ?? '';
|
||||||
|
|
||||||
/// 订单信息
|
/// 订单信息
|
||||||
final String kr_order = Get.arguments['order'];
|
final String kr_order = Get.arguments['order'];
|
||||||
@ -31,9 +31,27 @@ class KROrderStatusController extends GetxController {
|
|||||||
/// 支付方式类型
|
/// 支付方式类型
|
||||||
final String kr_paymentType = Get.arguments['payment_type'] as String;
|
final String kr_paymentType = Get.arguments['payment_type'] as String;
|
||||||
|
|
||||||
|
/// Checkout 类型(url, qr, stripe)
|
||||||
|
final String kr_checkoutType = Get.arguments['checkout_type'] as String? ?? 'url';
|
||||||
|
|
||||||
/// 定时器
|
/// 定时器
|
||||||
Timer? kr_timer;
|
Timer? kr_timer;
|
||||||
|
|
||||||
|
/// 倒计时定时器
|
||||||
|
Timer? kr_countdownTimer;
|
||||||
|
|
||||||
|
/// 15分钟倒计时(毫秒)
|
||||||
|
final RxInt kr_countdown = (15 * 60 * 1000).obs;
|
||||||
|
|
||||||
|
/// 格式化的倒计时字符串
|
||||||
|
final RxString kr_formattedCountdown = '15:00'.obs;
|
||||||
|
|
||||||
|
/// 订单创建时间(用于计算倒计时)
|
||||||
|
DateTime? kr_orderCreatedAt;
|
||||||
|
|
||||||
|
/// 上次刷新时间
|
||||||
|
int kr_lastRefreshTime = 0;
|
||||||
|
|
||||||
/// 订单状态常量
|
/// 订单状态常量
|
||||||
static const int kr_statusPending = 1; // 待支付
|
static const int kr_statusPending = 1; // 待支付
|
||||||
static const int kr_statusPaid = 2; // 已支付
|
static const int kr_statusPaid = 2; // 已支付
|
||||||
@ -53,129 +71,221 @@ class KROrderStatusController extends GetxController {
|
|||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
print('═══════════════════════════════════════');
|
||||||
|
print('📊 订单状态页面初始化');
|
||||||
|
print(' 订单号: $kr_order');
|
||||||
|
print(' 支付方式: $kr_paymentType');
|
||||||
|
print(' Checkout类型: $kr_checkoutType');
|
||||||
|
print('═══════════════════════════════════════');
|
||||||
|
|
||||||
|
// 立即查询一次订单状态,获取创建时间
|
||||||
|
kr_checkPaymentStatus();
|
||||||
|
// 启动轮询和倒计时
|
||||||
kr_startCheckingPaymentStatus();
|
kr_startCheckingPaymentStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onReady() {
|
void onReady() {
|
||||||
super.onReady();
|
super.onReady();
|
||||||
// 只有在非余额支付且有支付URL时才处理支付跳转
|
// 注意:支付链接已经在购买流程中打开了,这里不再重复打开
|
||||||
if (kr_paymentUrl.isNotEmpty && kr_paymentType != 'balance') {
|
|
||||||
if (Platform.isAndroid || Platform.isIOS) {
|
|
||||||
// 移动端使用 WebView
|
|
||||||
Get.toNamed(
|
|
||||||
Routes.KR_WEBVIEW,
|
|
||||||
arguments: {
|
|
||||||
'url': kr_paymentUrl,
|
|
||||||
'order': kr_order,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// 桌面端使用外部浏览器
|
|
||||||
final Uri uri = Uri.parse(kr_paymentUrl);
|
|
||||||
launchUrl(uri, mode: LaunchMode.externalApplication);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
|
print('🔚 订单状态页面关闭,清理定时器');
|
||||||
kr_timer?.cancel();
|
kr_timer?.cancel();
|
||||||
|
kr_countdownTimer?.cancel();
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 开始检查支付状态
|
/// 开始检查支付状态(参考 Tauri 项目:5秒轮询一次)
|
||||||
void kr_startCheckingPaymentStatus() {
|
void kr_startCheckingPaymentStatus() {
|
||||||
// 根据支付方式类型设置不同的查询间隔
|
print('🔄 启动支付状态轮询(每5秒检查一次)');
|
||||||
final Duration interval = kr_paymentType == 'balance'
|
|
||||||
? const Duration(seconds: 2) // 余额支付每2秒查询一次
|
|
||||||
: const Duration(seconds: 5); // 其他支付方式每5秒查询一次
|
|
||||||
|
|
||||||
kr_timer = Timer.periodic(interval, (timer) {
|
// 状态轮询:每5秒检查一次订单状态
|
||||||
|
kr_timer = Timer.periodic(const Duration(seconds: 5), (timer) {
|
||||||
kr_checkPaymentStatus();
|
kr_checkPaymentStatus();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 倒计时更新:每1秒更新一次UI显示
|
||||||
|
kr_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||||
|
kr_updateCountdown();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 检查支付状态
|
/// 更新倒计时(参考 Tauri 项目)
|
||||||
|
void kr_updateCountdown() {
|
||||||
|
if (kr_orderCreatedAt == null) {
|
||||||
|
// 订单创建时间还未获取,显示默认倒计时
|
||||||
|
kr_formattedCountdown.value = '15:00';
|
||||||
|
print('⏱️ 倒计时更新: 等待订单创建时间...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final now = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
final createdAt = kr_orderCreatedAt!.millisecondsSinceEpoch;
|
||||||
|
final targetTime = createdAt + (15 * 60 * 1000); // 15分钟后
|
||||||
|
|
||||||
|
final timeLeft = targetTime - now;
|
||||||
|
|
||||||
|
print('⏱️ 倒计时调试信息:');
|
||||||
|
print(' 当前时间(ms): $now');
|
||||||
|
print(' 创建时间(ms): $createdAt');
|
||||||
|
print(' 目标时间(ms): $targetTime');
|
||||||
|
print(' 剩余时间(ms): $timeLeft');
|
||||||
|
print(' 剩余时间(秒): ${(timeLeft / 1000).floor()}');
|
||||||
|
|
||||||
|
if (timeLeft > 0) {
|
||||||
|
kr_countdown.value = timeLeft;
|
||||||
|
// 格式化倒计时:MM:SS
|
||||||
|
final minutes = (timeLeft / 60000).floor();
|
||||||
|
final seconds = ((timeLeft % 60000) / 1000).floor();
|
||||||
|
kr_formattedCountdown.value = '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
|
||||||
|
print(' 格式化倒计时: ${kr_formattedCountdown.value}');
|
||||||
|
} else {
|
||||||
|
// 倒计时结束,订单超时
|
||||||
|
kr_countdown.value = 0;
|
||||||
|
kr_formattedCountdown.value = '00:00';
|
||||||
|
kr_countdownTimer?.cancel();
|
||||||
|
kr_timer?.cancel();
|
||||||
|
|
||||||
|
print('⏱️ 订单支付超时(15分钟)');
|
||||||
|
kr_statusTitle.value = AppTranslations.kr_orderStatus.closedTitle;
|
||||||
|
kr_statusDescription.value = '订单已超时,请重新下单';
|
||||||
|
kr_isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 检查支付状态(使用公开接口,参考 Tauri 项目)
|
||||||
Future<void> kr_checkPaymentStatus() async {
|
Future<void> kr_checkPaymentStatus() async {
|
||||||
|
final now = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final result = await kr_subscribeApi.kr_orderDetail(kr_order);
|
print('🔍 检查订单状态 [${kr_order}]');
|
||||||
|
|
||||||
|
// 使用公开接口查询订单状态
|
||||||
|
final result = await kr_subscribeApi.kr_queryOrderStatus(kr_order);
|
||||||
|
|
||||||
result.fold(
|
result.fold(
|
||||||
(error) {
|
(error) {
|
||||||
KRLogUtil.kr_e('检查支付状态失败: $error', tag: 'OrderStatusController');
|
print('❌ 查询失败: ${error.msg}');
|
||||||
|
KRLogUtil.kr_e('检查支付状态失败: ${error.msg}', tag: 'OrderStatusController');
|
||||||
kr_isLoading.value = false;
|
kr_isLoading.value = false;
|
||||||
kr_statusTitle.value = AppTranslations.kr_orderStatus.checkFailedTitle;
|
kr_statusTitle.value = AppTranslations.kr_orderStatus.checkFailedTitle;
|
||||||
kr_statusDescription.value = AppTranslations.kr_orderStatus.checkFailedDescription;
|
kr_statusDescription.value = AppTranslations.kr_orderStatus.checkFailedDescription;
|
||||||
kr_statusIcon.value = 'payment_success';
|
|
||||||
},
|
},
|
||||||
(kr_orderStatus) {
|
(kr_orderStatus) {
|
||||||
KRLogUtil.kr_i('检查支付状态: ${kr_orderStatus.toJson()}', tag: 'OrderStatusController');
|
// 保存订单创建时间(用于倒计时)
|
||||||
|
if (kr_orderCreatedAt == null && kr_orderStatus.kr_createdAt > 0) {
|
||||||
|
// 判断是秒级还是毫秒级时间戳
|
||||||
|
// 如果时间戳大于10位数字,说明是毫秒级
|
||||||
|
final timestamp = kr_orderStatus.kr_createdAt;
|
||||||
|
final isMilliseconds = timestamp > 10000000000; // 大于10位数说明是毫秒级
|
||||||
|
|
||||||
|
kr_orderCreatedAt = DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
isMilliseconds ? timestamp : timestamp * 1000
|
||||||
|
);
|
||||||
|
|
||||||
|
print('📅 订单创建时间: ${kr_orderCreatedAt}');
|
||||||
|
print('📅 原始时间戳: $timestamp');
|
||||||
|
print('📅 时间戳类型: ${isMilliseconds ? "毫秒级" : "秒级"}');
|
||||||
|
print('📅 转换后时间戳(ms): ${kr_orderCreatedAt!.millisecondsSinceEpoch}');
|
||||||
|
}
|
||||||
|
|
||||||
|
print('📊 订单状态: ${kr_orderStatus.kr_status} (${_getStatusName(kr_orderStatus.kr_status)})');
|
||||||
|
|
||||||
switch (kr_orderStatus.kr_status) {
|
switch (kr_orderStatus.kr_status) {
|
||||||
case kr_statusPending:
|
case kr_statusPending:
|
||||||
// 待支付状态,继续轮询
|
// 待支付状态,继续轮询
|
||||||
kr_statusTitle.value = AppTranslations.kr_orderStatus.pendingTitle;
|
kr_statusTitle.value = AppTranslations.kr_orderStatus.pendingTitle;
|
||||||
kr_statusDescription.value = AppTranslations.kr_orderStatus.pendingDescription;
|
kr_statusDescription.value = '${AppTranslations.kr_orderStatus.pendingDescription}\n剩余时间: ${kr_formattedCountdown.value}';
|
||||||
kr_statusIcon.value = 'payment_success';
|
kr_statusIcon.value = 'payment_success';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case kr_statusPaid:
|
case kr_statusPaid:
|
||||||
// 已支付状态,继续轮询直到完成
|
// 已支付状态,继续轮询直到完成
|
||||||
|
print('✅ 订单已支付,等待确认...');
|
||||||
kr_statusTitle.value = AppTranslations.kr_orderStatus.paidTitle;
|
kr_statusTitle.value = AppTranslations.kr_orderStatus.paidTitle;
|
||||||
kr_statusDescription.value = AppTranslations.kr_orderStatus.paidDescription;
|
kr_statusDescription.value = AppTranslations.kr_orderStatus.paidDescription;
|
||||||
kr_statusIcon.value = 'payment_success';
|
kr_statusIcon.value = 'payment_success';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case kr_statusFinished:
|
case kr_statusFinished:
|
||||||
// 订单完成
|
// 订单完成
|
||||||
|
print('🎉 订单完成!停止轮询');
|
||||||
kr_isPaymentSuccess.value = true;
|
kr_isPaymentSuccess.value = true;
|
||||||
kr_isLoading.value = false;
|
kr_isLoading.value = false;
|
||||||
kr_timer?.cancel();
|
kr_timer?.cancel();
|
||||||
|
kr_countdownTimer?.cancel();
|
||||||
kr_statusTitle.value = AppTranslations.kr_orderStatus.successTitle;
|
kr_statusTitle.value = AppTranslations.kr_orderStatus.successTitle;
|
||||||
kr_statusDescription.value = AppTranslations.kr_orderStatus.successDescription;
|
kr_statusDescription.value = AppTranslations.kr_orderStatus.successDescription;
|
||||||
kr_statusIcon.value = 'payment_success';
|
kr_statusIcon.value = 'payment_success';
|
||||||
|
// 发送支付成功事件
|
||||||
KREventBus().kr_sendMessage(KRMessageType.kr_payment);
|
KREventBus().kr_sendMessage(KRMessageType.kr_payment);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case kr_statusClose:
|
case kr_statusClose:
|
||||||
// 订单已关闭
|
// 订单已关闭
|
||||||
|
print('❌ 订单已关闭');
|
||||||
kr_isLoading.value = false;
|
kr_isLoading.value = false;
|
||||||
kr_timer?.cancel();
|
kr_timer?.cancel();
|
||||||
|
kr_countdownTimer?.cancel();
|
||||||
kr_statusTitle.value = AppTranslations.kr_orderStatus.closedTitle;
|
kr_statusTitle.value = AppTranslations.kr_orderStatus.closedTitle;
|
||||||
kr_statusDescription.value = AppTranslations.kr_orderStatus.closedDescription;
|
kr_statusDescription.value = AppTranslations.kr_orderStatus.closedDescription;
|
||||||
kr_statusIcon.value = 'payment_success';
|
kr_statusIcon.value = 'payment_success';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case kr_statusFailed:
|
case kr_statusFailed:
|
||||||
// 支付失败
|
// 支付失败
|
||||||
|
print('❌ 支付失败');
|
||||||
kr_isLoading.value = false;
|
kr_isLoading.value = false;
|
||||||
kr_timer?.cancel();
|
kr_timer?.cancel();
|
||||||
|
kr_countdownTimer?.cancel();
|
||||||
kr_statusTitle.value = AppTranslations.kr_orderStatus.failedTitle;
|
kr_statusTitle.value = AppTranslations.kr_orderStatus.failedTitle;
|
||||||
kr_statusDescription.value = AppTranslations.kr_orderStatus.failedDescription;
|
kr_statusDescription.value = AppTranslations.kr_orderStatus.failedDescription;
|
||||||
kr_statusIcon.value = 'payment_success';
|
kr_statusIcon.value = 'payment_success';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// 未知状态
|
// 未知状态
|
||||||
|
print('⚠️ 未知状态: ${kr_orderStatus.kr_status}');
|
||||||
kr_isLoading.value = false;
|
kr_isLoading.value = false;
|
||||||
kr_timer?.cancel();
|
kr_timer?.cancel();
|
||||||
|
kr_countdownTimer?.cancel();
|
||||||
kr_statusTitle.value = AppTranslations.kr_orderStatus.unknownTitle;
|
kr_statusTitle.value = AppTranslations.kr_orderStatus.unknownTitle;
|
||||||
kr_statusDescription.value = AppTranslations.kr_orderStatus.unknownDescription;
|
kr_statusDescription.value = AppTranslations.kr_orderStatus.unknownDescription;
|
||||||
kr_statusIcon.value = 'payment_success';
|
kr_statusIcon.value = 'payment_success';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kr_lastRefreshTime = now;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
print('❌ 异常: $error');
|
||||||
KRLogUtil.kr_e('检查支付状态失败: $error', tag: 'OrderStatusController');
|
KRLogUtil.kr_e('检查支付状态失败: $error', tag: 'OrderStatusController');
|
||||||
kr_isLoading.value = false;
|
kr_isLoading.value = false;
|
||||||
kr_statusTitle.value = AppTranslations.kr_orderStatus.checkFailedTitle;
|
kr_statusTitle.value = AppTranslations.kr_orderStatus.checkFailedTitle;
|
||||||
kr_statusDescription.value = AppTranslations.kr_orderStatus.checkFailedDescription;
|
kr_statusDescription.value = AppTranslations.kr_orderStatus.checkFailedDescription;
|
||||||
kr_statusIcon.value = 'payment_success';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 检查支付状态
|
/// 获取状态名称(用于日志)
|
||||||
Future<void> kr_checkPaymentStatusWithRetry() async {
|
String _getStatusName(int status) {
|
||||||
try {
|
switch (status) {
|
||||||
// ... 其他代码 ...
|
case kr_statusPending:
|
||||||
} catch (err) {
|
return '待支付';
|
||||||
KRLogUtil.kr_e('检查支付状态失败: $err', tag: 'OrderStatusController');
|
case kr_statusPaid:
|
||||||
|
return '已支付';
|
||||||
|
case kr_statusClose:
|
||||||
|
return '已关闭';
|
||||||
|
case kr_statusFailed:
|
||||||
|
return '支付失败';
|
||||||
|
case kr_statusFinished:
|
||||||
|
return '已完成';
|
||||||
|
default:
|
||||||
|
return '未知';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import 'package:kaer_with_panels/app/model/response/kr_package_list.dart';
|
|||||||
import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
|
import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
|
||||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../../../common/app_run_data.dart';
|
import '../../../common/app_run_data.dart';
|
||||||
import '../../../common/app_config.dart';
|
import '../../../common/app_config.dart';
|
||||||
@ -399,22 +400,31 @@ class KRPurchaseMembershipController extends GetxController {
|
|||||||
'',
|
'',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 打印下单请求和响应
|
||||||
|
print('═══════════════════════════════════════');
|
||||||
|
print('🔗 下单接口: /v1/public/order/purchase');
|
||||||
|
print('📤 请求参数:');
|
||||||
|
print(' subscribe_id: ${selectedPlan.kr_id}');
|
||||||
|
print(' quantity: $quantity');
|
||||||
|
print(' payment: $paymentMethodId');
|
||||||
|
print(' coupon: ""');
|
||||||
|
print('');
|
||||||
|
|
||||||
purchaseEither.fold(
|
purchaseEither.fold(
|
||||||
(error) => KRCommonUtil.kr_showToast(error.msg),
|
(error) {
|
||||||
|
print('❌ 请求失败:');
|
||||||
|
print(' 错误码: ${error.code}');
|
||||||
|
print(' 错误信息: ${error.msg}');
|
||||||
|
print('═══════════════════════════════════════');
|
||||||
|
KRCommonUtil.kr_showToast(error.msg);
|
||||||
|
},
|
||||||
(order) async {
|
(order) async {
|
||||||
// 所有支付方式都需要调用 checkout 接口
|
print('✅ 请求成功:');
|
||||||
final checkoutEither = await _kr_subscribeApi.kr_checkout(order);
|
print(' 订单号: $order');
|
||||||
checkoutEither.fold(
|
print('═══════════════════════════════════════');
|
||||||
(error) => KRCommonUtil.kr_showToast(error.msg),
|
|
||||||
(uri) => Get.toNamed(
|
// 调用 checkout 接口获取支付链接
|
||||||
Routes.KR_ORDER_STATUS,
|
await _kr_handleCheckout(order, paymentPlatform);
|
||||||
arguments: {
|
|
||||||
'url': uri,
|
|
||||||
'order': order,
|
|
||||||
'payment_type': paymentPlatform,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -597,4 +607,102 @@ class KRPurchaseMembershipController extends GetxController {
|
|||||||
return AppTranslations.kr_purchaseMembership
|
return AppTranslations.kr_purchaseMembership
|
||||||
.devices(plan.kr_deviceLimit.toString());
|
.devices(plan.kr_deviceLimit.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 处理 checkout 并打开支付链接(参考 Tauri 项目)
|
||||||
|
Future<void> _kr_handleCheckout(String orderNo, String paymentPlatform) async {
|
||||||
|
print('');
|
||||||
|
print('═══════════════════════════════════════');
|
||||||
|
print('🔗 调用 Checkout 接口');
|
||||||
|
print('📤 请求参数:');
|
||||||
|
print(' orderNo: $orderNo');
|
||||||
|
print(' returnUrl: ${AppConfig.getInstance().baseUrl}');
|
||||||
|
print('');
|
||||||
|
|
||||||
|
final checkoutEither = await _kr_subscribeApi.kr_checkout(orderNo);
|
||||||
|
|
||||||
|
checkoutEither.fold(
|
||||||
|
(error) {
|
||||||
|
print('❌ Checkout 失败:');
|
||||||
|
print(' 错误码: ${error.code}');
|
||||||
|
print(' 错误信息: ${error.msg}');
|
||||||
|
print('═══════════════════════════════════════');
|
||||||
|
KRCommonUtil.kr_showToast(error.msg);
|
||||||
|
},
|
||||||
|
(checkoutResponse) async {
|
||||||
|
print('✅ Checkout 成功:');
|
||||||
|
print(' 支付类型: ${checkoutResponse.type}');
|
||||||
|
|
||||||
|
if (checkoutResponse.type == 'url') {
|
||||||
|
// URL 类型:在浏览器中打开支付链接,同时跳转到订单状态页面
|
||||||
|
if (checkoutResponse.checkoutUrl != null && checkoutResponse.checkoutUrl!.isNotEmpty) {
|
||||||
|
print(' 支付链接: ${checkoutResponse.checkoutUrl}');
|
||||||
|
print('🌐 正在用外部浏览器打开支付链接...');
|
||||||
|
print('═══════════════════════════════════════');
|
||||||
|
|
||||||
|
final url = Uri.parse(checkoutResponse.checkoutUrl!);
|
||||||
|
if (await canLaunchUrl(url)) {
|
||||||
|
// 在外部浏览器打开支付链接
|
||||||
|
await launchUrl(
|
||||||
|
url,
|
||||||
|
mode: LaunchMode.externalApplication,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 跳转到订单状态页面进行轮询检查
|
||||||
|
Get.toNamed(
|
||||||
|
Routes.KR_ORDER_STATUS,
|
||||||
|
arguments: {
|
||||||
|
'url': checkoutResponse.checkoutUrl,
|
||||||
|
'order': orderNo,
|
||||||
|
'payment_type': paymentPlatform,
|
||||||
|
'checkout_type': 'url',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
print('❌ 无法打开URL: ${checkoutResponse.checkoutUrl}');
|
||||||
|
KRCommonUtil.kr_showToast('无法打开支付链接');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print('⚠️ 支付链接为空');
|
||||||
|
print('═══════════════════════════════════════');
|
||||||
|
KRCommonUtil.kr_showToast('支付链接为空');
|
||||||
|
}
|
||||||
|
} else if (checkoutResponse.type == 'qr') {
|
||||||
|
// QR 类型:显示二维码(跳转到订单状态页面)
|
||||||
|
print(' 二维码内容: ${checkoutResponse.checkoutUrl}');
|
||||||
|
print('📱 显示二维码支付...');
|
||||||
|
print('═══════════════════════════════════════');
|
||||||
|
|
||||||
|
Get.toNamed(
|
||||||
|
Routes.KR_ORDER_STATUS,
|
||||||
|
arguments: {
|
||||||
|
'url': checkoutResponse.checkoutUrl,
|
||||||
|
'order': orderNo,
|
||||||
|
'payment_type': paymentPlatform,
|
||||||
|
'checkout_type': 'qr',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (checkoutResponse.type == 'stripe') {
|
||||||
|
// Stripe 类型:显示 Stripe 支付表单
|
||||||
|
print(' Stripe Method: ${checkoutResponse.stripe?.method}');
|
||||||
|
print(' Stripe Client Secret: ${checkoutResponse.stripe?.clientSecret}');
|
||||||
|
print('💳 显示 Stripe 支付表单...');
|
||||||
|
print('═══════════════════════════════════════');
|
||||||
|
|
||||||
|
Get.toNamed(
|
||||||
|
Routes.KR_ORDER_STATUS,
|
||||||
|
arguments: {
|
||||||
|
'order': orderNo,
|
||||||
|
'payment_type': paymentPlatform,
|
||||||
|
'checkout_type': 'stripe',
|
||||||
|
'stripe': checkoutResponse.stripe,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
print('⚠️ 未知的支付类型: ${checkoutResponse.type}');
|
||||||
|
print('═══════════════════════════════════════');
|
||||||
|
KRCommonUtil.kr_showToast('不支持的支付类型: ${checkoutResponse.type}');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,16 +30,28 @@ class BaseResponse<T> {
|
|||||||
|
|
||||||
if (shouldDecrypt && cipherText.isNotEmpty && nonce.isNotEmpty) {
|
if (shouldDecrypt && cipherText.isNotEmpty && nonce.isNotEmpty) {
|
||||||
try {
|
try {
|
||||||
KRLogUtil.kr_i('🔓 开始解密响应数据', tag: 'BaseResponse');
|
print('═══════════════════════════════════════');
|
||||||
|
print('🔐 检测到加密响应,开始解密...');
|
||||||
|
print('📥 加密数据长度: ${cipherText.length} 字符');
|
||||||
|
print('⏰ 时间戳: $nonce');
|
||||||
|
|
||||||
final decrypted = KRAesUtil.decryptData(cipherText, nonce, AppConfig.kr_encryptionKey);
|
final decrypted = KRAesUtil.decryptData(cipherText, nonce, AppConfig.kr_encryptionKey);
|
||||||
body = jsonDecode(decrypted);
|
body = jsonDecode(decrypted);
|
||||||
KRLogUtil.kr_i('✅ 解密成功', tag: 'BaseResponse');
|
|
||||||
|
|
||||||
// 打印完整的解密后数据,方便调试
|
print('✅ 解密成功');
|
||||||
final bodyStr = jsonEncode(body);
|
print('');
|
||||||
KRLogUtil.kr_i('📦 解密后数据(完整): $bodyStr', tag: 'BaseResponse');
|
print('📦 解密后的完整数据:');
|
||||||
|
// 格式化打印 JSON,方便调试
|
||||||
|
final bodyStr = JsonEncoder.withIndent(' ').convert(body);
|
||||||
|
print(bodyStr);
|
||||||
|
print('═══════════════════════════════════════');
|
||||||
|
|
||||||
|
KRLogUtil.kr_i('🔓 解密成功', tag: 'BaseResponse');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
KRLogUtil.kr_e('❌ 解密失败: $e,使用原始数据', tag: 'BaseResponse');
|
print('❌ 解密失败: $e');
|
||||||
|
print('⚠️ 将使用原始数据');
|
||||||
|
print('═══════════════════════════════════════');
|
||||||
|
KRLogUtil.kr_e('❌ 解密失败: $e', tag: 'BaseResponse');
|
||||||
body = dataMap;
|
body = dataMap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -282,6 +282,36 @@ class _KRSimpleHttpInterceptor extends Interceptor {
|
|||||||
print('>>> Request │ ${options.method} │ ${options.uri}');
|
print('>>> Request │ ${options.method} │ ${options.uri}');
|
||||||
if (options.data != null) {
|
if (options.data != null) {
|
||||||
print('Body: ${options.data}');
|
print('Body: ${options.data}');
|
||||||
|
|
||||||
|
// 检查是否是加密数据(包含 data 和 time 字段)
|
||||||
|
if (options.data is Map<String, dynamic>) {
|
||||||
|
final data = options.data as Map<String, dynamic>;
|
||||||
|
if (data.containsKey('data') && data.containsKey('time')) {
|
||||||
|
try {
|
||||||
|
print('');
|
||||||
|
print('🔐 检测到加密请求,正在解密...');
|
||||||
|
// 尝试解密并打印原始数据
|
||||||
|
final encryptedData = data['data'] as String;
|
||||||
|
final nonce = data['time'] as String;
|
||||||
|
final decrypted = KRAesUtil.decryptData(
|
||||||
|
encryptedData,
|
||||||
|
nonce,
|
||||||
|
AppConfig.kr_encryptionKey,
|
||||||
|
);
|
||||||
|
print('🔓 解密后的原始请求数据:');
|
||||||
|
// 尝试格式化 JSON
|
||||||
|
try {
|
||||||
|
final jsonData = jsonDecode(decrypted);
|
||||||
|
final prettyJson = JsonEncoder.withIndent(' ').convert(jsonData);
|
||||||
|
print(prettyJson);
|
||||||
|
} catch (_) {
|
||||||
|
print(decrypted);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('⚠️ 请求解密失败: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
handler.next(options);
|
handler.next(options);
|
||||||
}
|
}
|
||||||
@ -291,6 +321,41 @@ class _KRSimpleHttpInterceptor extends Interceptor {
|
|||||||
print('<<< Response │ ${response.requestOptions.method} │ ${response.statusCode} ${response.statusMessage} │ ${response.requestOptions.uri}');
|
print('<<< Response │ ${response.requestOptions.method} │ ${response.statusCode} ${response.statusMessage} │ ${response.requestOptions.uri}');
|
||||||
if (response.data != null) {
|
if (response.data != null) {
|
||||||
print('Body: ${response.data}');
|
print('Body: ${response.data}');
|
||||||
|
|
||||||
|
// 检查响应是否是加密数据(包含 data 和 time 字段)
|
||||||
|
if (response.data is Map<String, dynamic>) {
|
||||||
|
final dataMap = response.data as Map<String, dynamic>;
|
||||||
|
|
||||||
|
// 检查是否包含嵌套的 data 字段(加密数据格式)
|
||||||
|
final nestedData = dataMap['data'];
|
||||||
|
if (nestedData is Map<String, dynamic> &&
|
||||||
|
nestedData.containsKey('data') &&
|
||||||
|
nestedData.containsKey('time')) {
|
||||||
|
try {
|
||||||
|
print('');
|
||||||
|
print('🔐 检测到加密响应,正在解密...');
|
||||||
|
// 尝试解密并打印原始响应数据
|
||||||
|
final encryptedData = nestedData['data'] as String;
|
||||||
|
final nonce = nestedData['time'] as String;
|
||||||
|
final decrypted = KRAesUtil.decryptData(
|
||||||
|
encryptedData,
|
||||||
|
nonce,
|
||||||
|
AppConfig.kr_encryptionKey,
|
||||||
|
);
|
||||||
|
print('🔓 解密后的原始响应数据:');
|
||||||
|
// 尝试格式化 JSON
|
||||||
|
try {
|
||||||
|
final jsonData = jsonDecode(decrypted);
|
||||||
|
final prettyJson = JsonEncoder.withIndent(' ').convert(jsonData);
|
||||||
|
print(prettyJson);
|
||||||
|
} catch (_) {
|
||||||
|
print(decrypted);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('⚠️ 响应解密失败: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
handler.next(response);
|
handler.next(response);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,10 +38,10 @@ abstract class Api {
|
|||||||
static const String kr_getPaymentMethods = "/v1/app/payment/methods";
|
static const String kr_getPaymentMethods = "/v1/app/payment/methods";
|
||||||
|
|
||||||
/// 进行下单
|
/// 进行下单
|
||||||
static const String kr_purchase = "/v1/app/order/purchase";
|
static const String kr_purchase = "/v1/public/order/purchase";
|
||||||
|
|
||||||
/// 获取支付地址,跳转到付款地址
|
/// 获取支付地址,跳转到付款地址(参考 Tauri 项目)
|
||||||
static const String kr_checkout = "/v1/app/order/checkout";
|
static const String kr_checkout = "/v1/public/portal/order/checkout";
|
||||||
|
|
||||||
/// 获取可购买套餐
|
/// 获取可购买套餐
|
||||||
static const String kr_getPackageList = "/v1/public/subscribe/list";
|
static const String kr_getPackageList = "/v1/public/subscribe/list";
|
||||||
@ -61,6 +61,9 @@ abstract class Api {
|
|||||||
/// 通过该接口判断订单状态
|
/// 通过该接口判断订单状态
|
||||||
static const String kr_orderDetail = "/v1/app/order/detail";
|
static const String kr_orderDetail = "/v1/app/order/detail";
|
||||||
|
|
||||||
|
/// 查询订单状态(公开接口,参考 Tauri 项目)
|
||||||
|
static const String kr_queryOrderStatus = "/v1/public/order/detail";
|
||||||
|
|
||||||
/// 获取消息列表
|
/// 获取消息列表
|
||||||
static const String kr_getMessageList = "/v1/public/announcement/list";
|
static const String kr_getMessageList = "/v1/public/announcement/list";
|
||||||
|
|
||||||
|
|||||||
@ -120,6 +120,27 @@ class KRSubscribeApi {
|
|||||||
return right(baseResponse.model);
|
return right(baseResponse.model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 查询订单状态(公开接口,用于支付轮询)
|
||||||
|
Future<Either<HttpError, KROrderStatus>> kr_queryOrderStatus(
|
||||||
|
String orderNo) async {
|
||||||
|
final Map<String, dynamic> data = <String, dynamic>{};
|
||||||
|
data['order_no'] = orderNo;
|
||||||
|
|
||||||
|
BaseResponse<KROrderStatus> baseResponse =
|
||||||
|
await HttpUtil.getInstance().request<KROrderStatus>(
|
||||||
|
Api.kr_queryOrderStatus,
|
||||||
|
data,
|
||||||
|
method: HttpMethod.GET,
|
||||||
|
isShowLoading: false,
|
||||||
|
);
|
||||||
|
if (!baseResponse.isSuccess) {
|
||||||
|
return left(
|
||||||
|
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
return right(baseResponse.model);
|
||||||
|
}
|
||||||
|
|
||||||
/// 获取支付方式
|
/// 获取支付方式
|
||||||
Future<Either<HttpError, List<KRPaymentMethod>>>
|
Future<Either<HttpError, List<KRPaymentMethod>>>
|
||||||
kr_getPaymentMethods() async {
|
kr_getPaymentMethods() async {
|
||||||
@ -231,14 +252,14 @@ class KRSubscribeApi {
|
|||||||
return right(baseResponse.model.orderNo);
|
return right(baseResponse.model.orderNo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取支付地址,跳转到付款地址
|
/// 获取支付地址,跳转到付款地址(新版本:支持多种支付类型)
|
||||||
Future<Either<HttpError, String>> kr_checkout(String orderId) async {
|
Future<Either<HttpError, KRCheckoutResponse>> kr_checkout(String orderId) async {
|
||||||
final Map<String, dynamic> data = <String, dynamic>{};
|
final Map<String, dynamic> data = <String, dynamic>{};
|
||||||
data['orderNo'] = orderId;
|
data['orderNo'] = orderId;
|
||||||
data['returnUrl'] = AppConfig.getInstance().baseUrl;
|
data['returnUrl'] = AppConfig.getInstance().baseUrl;
|
||||||
|
|
||||||
BaseResponse<KRPurchaseOrderUrl> baseResponse =
|
BaseResponse<KRCheckoutResponse> baseResponse =
|
||||||
await HttpUtil.getInstance().request<KRPurchaseOrderUrl>(
|
await HttpUtil.getInstance().request<KRCheckoutResponse>(
|
||||||
Api.kr_checkout,
|
Api.kr_checkout,
|
||||||
data,
|
data,
|
||||||
method: HttpMethod.POST,
|
method: HttpMethod.POST,
|
||||||
@ -249,7 +270,7 @@ class KRSubscribeApi {
|
|||||||
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
|
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
|
||||||
}
|
}
|
||||||
|
|
||||||
return right(baseResponse.model.url);
|
return right(baseResponse.model);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 重置订阅周期
|
/// 重置订阅周期
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user