feat: 增加ios支付
This commit is contained in:
@@ -203,7 +203,7 @@ class KRLoginView extends GetView<KRLoginController> {
|
||||
controller: controller.accountController,
|
||||
hintText: 'Email',
|
||||
),
|
||||
SizedBox(height: 10.w),
|
||||
SizedBox(height: 10),
|
||||
_buildStandardInputField(
|
||||
controller: controller.psdController,
|
||||
hintText: '密码',
|
||||
@@ -226,7 +226,7 @@ class KRLoginView extends GetView<KRLoginController> {
|
||||
bool isPassword = false,
|
||||
}) {
|
||||
return SizedBox(
|
||||
height: 50, // 固定高度
|
||||
// height: 50, // 固定高度
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
obscureText: isPassword,
|
||||
|
||||
+60
-9
@@ -17,6 +17,7 @@ import '../../../services/api_service/kr_api.user.dart';
|
||||
import '../../../utils/kr_event_bus.dart';
|
||||
import '../../../network/http_util.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_stripe/flutter_stripe.dart';
|
||||
|
||||
/// 会员购买控制器
|
||||
/// 负责处理会员套餐选择、支付方式选择和订阅流程
|
||||
@@ -460,18 +461,54 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
|
||||
/// 处理购买和结账流程
|
||||
Future<void> kr_processPurchaseAndCheckout() async {
|
||||
|
||||
final selectedPlan = kr_plans[kr_selectedPlanIndex.value];
|
||||
// =========================================================================
|
||||
// 🔽 支付方式选择优化:iOS 优先使用 Stripe 🔽
|
||||
// =========================================================================
|
||||
final isIOS = Platform.isIOS;
|
||||
|
||||
// ⚠️ 支付方式接口暂未实现,使用默认支付方式 ID (1) 和平台 'alipay'
|
||||
final paymentMethodId = kr_paymentMethods.isNotEmpty && kr_selectedPaymentMethodIndex.value >= 0
|
||||
? kr_paymentMethods[kr_selectedPaymentMethodIndex.value].id
|
||||
: 1;
|
||||
final paymentPlatform = kr_paymentMethods.isNotEmpty && kr_selectedPaymentMethodIndex.value >= 0
|
||||
? kr_paymentMethods[kr_selectedPaymentMethodIndex.value].platform
|
||||
: 'alipay';
|
||||
// 1. 初始化为默认回退值
|
||||
int finalPaymentMethodId = 1;
|
||||
String finalPaymentPlatform = 'alipay';
|
||||
|
||||
// 获取选中的数量
|
||||
if (kr_paymentMethods.isNotEmpty) {
|
||||
// 检查是否有用户已选中且索引有效
|
||||
final bool isIndexValid = kr_selectedPaymentMethodIndex.value >= 0 &&
|
||||
kr_selectedPaymentMethodIndex.value < kr_paymentMethods.length;
|
||||
|
||||
if (isIOS) {
|
||||
// 2. iOS 专属:查找 'stripe'
|
||||
final stripeMethod = kr_paymentMethods.firstWhere(
|
||||
(method) => method.platform == 'Stripe',
|
||||
);
|
||||
|
||||
if (stripeMethod != null) {
|
||||
// 找到了 Stripe,使用它
|
||||
finalPaymentMethodId = stripeMethod.id;
|
||||
finalPaymentPlatform = stripeMethod.platform;
|
||||
} else if (isIndexValid) {
|
||||
// 找不到 Stripe,但用户有选中,使用选中的方式
|
||||
finalPaymentMethodId =
|
||||
kr_paymentMethods[kr_selectedPaymentMethodIndex.value].id;
|
||||
finalPaymentPlatform =
|
||||
kr_paymentMethods[kr_selectedPaymentMethodIndex.value].platform;
|
||||
}
|
||||
} else {
|
||||
// 3. 非 iOS 环境:如果用户有选中,使用选中的方式
|
||||
if (isIndexValid) {
|
||||
finalPaymentMethodId =
|
||||
kr_paymentMethods[kr_selectedPaymentMethodIndex.value].id;
|
||||
finalPaymentPlatform =
|
||||
kr_paymentMethods[kr_selectedPaymentMethodIndex.value].platform;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final paymentMethodId = finalPaymentMethodId;
|
||||
final paymentPlatform = finalPaymentPlatform;
|
||||
// =========================================================================
|
||||
// 🔼 支付方式选择优化结束 🔼
|
||||
// =========================================================================
|
||||
final quantity = kr_getSelectedQuantity();
|
||||
|
||||
// 判断是续订还是新购
|
||||
@@ -805,6 +842,20 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
print(' Stripe Client Secret: ${checkoutResponse.stripe?.clientSecret}');
|
||||
print('💳 显示 Stripe 支付表单...');
|
||||
print('═══════════════════════════════════════');
|
||||
await Stripe.instance.initPaymentSheet(
|
||||
paymentSheetParameters: SetupPaymentSheetParameters(
|
||||
// ⚠️ Stripe SDK 必须在 main() 中初始化过 publishableKey
|
||||
paymentIntentClientSecret: checkoutResponse.stripe?.clientSecret,
|
||||
merchantDisplayName: 'Hi快VPN',
|
||||
// 确保 customerId 和 ephemeralKeySecret 也被设置,如果需要保存卡片信息
|
||||
),
|
||||
);
|
||||
|
||||
// B. 显示支付表单给用户
|
||||
await Stripe.instance.presentPaymentSheet();
|
||||
|
||||
// C. 支付成功
|
||||
print('✅ Stripe 支付成功!');
|
||||
|
||||
Get.toNamed(
|
||||
Routes.KR_ORDER_STATUS,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
class HiFixedScrollbar extends StatefulWidget {
|
||||
@@ -6,21 +9,13 @@ class HiFixedScrollbar extends StatefulWidget {
|
||||
final ScrollController controller;
|
||||
|
||||
final bool isShowScrollbar;
|
||||
|
||||
/// 距离右边的间距(默认 18)
|
||||
final double right;
|
||||
|
||||
/// 滚动条宽度
|
||||
final double thickness;
|
||||
|
||||
/// 滚动条颜色(默认白色 30%)
|
||||
final Color thumbColor;
|
||||
|
||||
/// 背景轨道颜色(默认白色 15%)
|
||||
final Color trackColor;
|
||||
|
||||
/// 滚动条固定高度(默认 50)
|
||||
final double thumbHeight;
|
||||
final Duration fadeDuration;
|
||||
final Duration fadeDelay;
|
||||
|
||||
const HiFixedScrollbar({
|
||||
super.key,
|
||||
@@ -32,24 +27,81 @@ class HiFixedScrollbar extends StatefulWidget {
|
||||
this.thumbHeight = 50,
|
||||
this.thumbColor = const Color.fromRGBO(255, 255, 255, 0.3),
|
||||
this.trackColor = const Color.fromRGBO(255, 255, 255, 0.15),
|
||||
this.fadeDuration = const Duration(milliseconds: 300),
|
||||
this.fadeDelay = const Duration(milliseconds: 500),
|
||||
});
|
||||
|
||||
@override
|
||||
State<HiFixedScrollbar> createState() => _HiFixedScrollbarState();
|
||||
}
|
||||
|
||||
class _HiFixedScrollbarState extends State<HiFixedScrollbar> {
|
||||
class _HiFixedScrollbarState extends State<HiFixedScrollbar>
|
||||
with SingleTickerProviderStateMixin {
|
||||
double _thumbOffset = 0.0;
|
||||
bool _hasScrolled = false;
|
||||
late AnimationController _fadeController;
|
||||
Timer? _fadeTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fadeController = AnimationController(
|
||||
vsync: this,
|
||||
duration: widget.fadeDuration,
|
||||
);
|
||||
// 1. 监听位置变化 (仅更新 _thumbOffset)
|
||||
widget.controller.addListener(_updateThumbPosition);
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
if (widget.controller.hasClients) {
|
||||
_updateThumbPosition();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant HiFixedScrollbar oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (widget.controller != oldWidget.controller) {
|
||||
// 移除旧控制器的位置监听
|
||||
oldWidget.controller.removeListener(_updateThumbPosition);
|
||||
// 添加新控制器的位置监听
|
||||
widget.controller.addListener(_updateThumbPosition);
|
||||
_fadeTimer?.cancel();
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
if (widget.controller.hasClients) {
|
||||
_updateThumbPosition();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool _onScrollNotification(ScrollNotification notification) {
|
||||
if (!mounted) return false;
|
||||
if (_fadeTimer != null && _fadeTimer!.isActive) {
|
||||
_fadeTimer?.cancel();
|
||||
}
|
||||
if (notification is ScrollStartNotification ||
|
||||
notification is ScrollUpdateNotification) {
|
||||
_fadeController.forward();
|
||||
_updateThumbPosition();
|
||||
} else if (notification is ScrollEndNotification ||
|
||||
(notification is UserScrollNotification &&
|
||||
notification.direction == ScrollDirection.idle)) {
|
||||
_fadeTimer = Timer(widget.fadeDelay, () {
|
||||
if (mounted) {
|
||||
_fadeController.reverse();
|
||||
} else {
|
||||
_fadeTimer?.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void _updateThumbPosition() {
|
||||
if (!mounted) return;
|
||||
if (!mounted || !widget.controller.hasClients) return;
|
||||
final position = widget.controller.position;
|
||||
if (!position.hasPixels || !position.hasContentDimensions) return;
|
||||
|
||||
@@ -57,19 +109,43 @@ class _HiFixedScrollbarState extends State<HiFixedScrollbar> {
|
||||
final offset = position.pixels;
|
||||
final viewport = position.viewportDimension;
|
||||
|
||||
// ✅ 固定高度滚动条,只根据滚动比例移动位置
|
||||
final trackHeight = viewport - widget.thumbHeight;
|
||||
final scrollRatio = maxScrollExtent == 0 ? 0 : offset / maxScrollExtent;
|
||||
if (maxScrollExtent <= 0) {
|
||||
if (_thumbOffset != 0.0) {
|
||||
setState(() => _thumbOffset = 0.0);
|
||||
}
|
||||
if (_fadeController.status != AnimationStatus.dismissed) {
|
||||
_fadeController.reverse();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_thumbOffset = (trackHeight * scrollRatio).clamp(0, trackHeight);
|
||||
_hasScrolled = offset > 0;
|
||||
});
|
||||
final trackHeight = viewport - widget.thumbHeight.h;
|
||||
final scrollRatio = offset / maxScrollExtent;
|
||||
final newOffset = (trackHeight * scrollRatio).clamp(0.0, trackHeight);
|
||||
|
||||
if (_thumbOffset != newOffset) {
|
||||
setState(() {
|
||||
_thumbOffset = newOffset;
|
||||
});
|
||||
if (_fadeTimer != null && _fadeTimer!.isActive) {
|
||||
_fadeTimer?.cancel();
|
||||
}
|
||||
_fadeController.forward();
|
||||
_fadeTimer = Timer(widget.fadeDelay, () {
|
||||
if (mounted) {
|
||||
_fadeController.reverse();
|
||||
} else {
|
||||
_fadeTimer?.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.controller.removeListener(_updateThumbPosition);
|
||||
_fadeController.dispose();
|
||||
_fadeTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -77,44 +153,62 @@ class _HiFixedScrollbarState extends State<HiFixedScrollbar> {
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (_, constraints) {
|
||||
final showScrollbar = widget.isShowScrollbar &&
|
||||
widget.controller.hasClients &&
|
||||
widget.controller.position.maxScrollExtent > 0 &&
|
||||
_hasScrolled;
|
||||
final position =
|
||||
widget.controller.hasClients ? widget.controller.position : null;
|
||||
|
||||
if (position == null || !position.hasContentDimensions) {
|
||||
return widget.child;
|
||||
}
|
||||
|
||||
final maxScrollExtent = position.maxScrollExtent;
|
||||
final canShowScrollbar = widget.isShowScrollbar && maxScrollExtent > 0;
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
// 滚动内容
|
||||
widget.child,
|
||||
NotificationListener<ScrollNotification>(
|
||||
onNotification: _onScrollNotification,
|
||||
child: widget.child,
|
||||
),
|
||||
if (canShowScrollbar)
|
||||
AnimatedBuilder(
|
||||
animation: _fadeController,
|
||||
builder: (context, child) {
|
||||
return Opacity(
|
||||
opacity: _fadeController.value,
|
||||
child: Stack(
|
||||
children: [
|
||||
// 滚动条轨道 (Track)
|
||||
Positioned(
|
||||
right: widget.right.w.toDouble(),
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Container(
|
||||
width: widget.thickness.w.toDouble(),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.trackColor,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
if (showScrollbar) ...[
|
||||
// 滚动条轨道
|
||||
Positioned(
|
||||
right: widget.right.w,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Container(
|
||||
width: widget.thickness.w,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.trackColor,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
// 滚动条拇指 (Thumb)
|
||||
Positioned(
|
||||
right: widget.right.w.toDouble(),
|
||||
top: _thumbOffset,
|
||||
child: Container(
|
||||
width: widget.thickness.w.toDouble(),
|
||||
height: widget.thumbHeight.h.toDouble(),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.thumbColor,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// 滚动条拇指
|
||||
Positioned(
|
||||
right: widget.right.w,
|
||||
top: _thumbOffset,
|
||||
child: Container(
|
||||
width: widget.thickness.w,
|
||||
height: widget.thumbHeight,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.thumbColor,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:flutter/services.dart';
|
||||
// import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter_stripe/flutter_stripe.dart';
|
||||
|
||||
import 'package:kaer_with_panels/app/themes/kr_theme_service.dart';
|
||||
import 'package:kaer_with_panels/app/localization/getx_translations.dart';
|
||||
@@ -47,6 +48,8 @@ void main() async {
|
||||
// 初始化日志系统
|
||||
KRLogUtil.kr_init();
|
||||
|
||||
Stripe.publishableKey = 'pk_live_51SbuYxAawOMH8rEEkz6f4mnxAUjGC72eQ6qdm5tT6whC4hULkxxdbiPsB4gSCIMnNIGCsIgeASTXBakUcbOuUwQO00jSWjuufx';
|
||||
|
||||
// 初始化 Hive
|
||||
await KRSecureStorage().kr_initHive();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user