import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../services/payment_service.dart'; /// 充值页状态:套餐 + 选中档位/自定义金额 + 选中支付方式 + 下单中 class PaymentState { const PaymentState({ this.result, this.selectedPackageId, this.customYuan, this.selectedMethod, this.isLoading = false, this.loadFailed = false, this.creating = false, }); final PackagesResult? result; /// null = 使用自定义金额 final int? selectedPackageId; /// 自定义金额(元);null 表示未输入 final int? customYuan; final int? selectedMethod; final bool isLoading; final bool loadFailed; final bool creating; /// 实际应付金额(元):优先选中档位,否则自定义金额 int? get amountYuan { final r = result; if (r == null) return null; final pkgId = selectedPackageId; if (pkgId != null) { for (final p in r.packages) { if (p.id == pkgId) return p.amountYuan; } } return customYuan; } PaymentState copyWith({ PackagesResult? result, int? Function()? selectedPackageId, int? Function()? customYuan, int? Function()? selectedMethod, bool? isLoading, bool? loadFailed, bool? creating, }) => PaymentState( result: result ?? this.result, selectedPackageId: selectedPackageId != null ? selectedPackageId() : this.selectedPackageId, customYuan: customYuan != null ? customYuan() : this.customYuan, selectedMethod: selectedMethod != null ? selectedMethod() : this.selectedMethod, isLoading: isLoading ?? this.isLoading, loadFailed: loadFailed ?? this.loadFailed, creating: creating ?? this.creating, ); } class PaymentNotifier extends StateNotifier { PaymentNotifier() : super(const PaymentState()) { load(); } Future load() async { state = state.copyWith(isLoading: true, loadFailed: false); try { final result = await PaymentService.fetchPackages(); state = state.copyWith( result: result, // 默认选中第一档 + 第一种支付方式 selectedPackageId: () => result.packages.isNotEmpty ? result.packages.first.id : null, customYuan: () => null, selectedMethod: () => result.payMethods.isNotEmpty ? result.payMethods.first.method : null, isLoading: false, ); } catch (_) { state = state.copyWith(isLoading: false, loadFailed: true); } } /// 选择档位(清空自定义金额) void selectPackage(int id) { state = state.copyWith(selectedPackageId: () => id, customYuan: () => null); } /// 输入自定义金额(取消档位选中) void setCustomYuan(int? yuan) { state = state.copyWith( customYuan: () => yuan, selectedPackageId: () => yuan == null ? state.selectedPackageId : null, ); } void selectMethod(int method) { state = state.copyWith(selectedMethod: () => method); } /// 创建充值订单。成功返回订单;失败抛异常由 UI 提示(1001 金额不在档位)。 Future createOrder() async { final amount = state.amountYuan; final method = state.selectedMethod; if (amount == null || method == null) { throw ArgumentError('未选择金额或支付方式'); } state = state.copyWith(creating: true); try { final order = await PaymentService.createRecharge( amountYuan: amount, payMethod: method, ); return order; } finally { if (mounted) state = state.copyWith(creating: false); } } } final paymentProvider = StateNotifierProvider( (_) => PaymentNotifier(), ); /// 订单轮询状态 enum OrderPollStatus { polling, paid, closed, timeout, failed } class OrderPollState { const OrderPollState({ this.status = OrderPollStatus.polling, required this.order, this.walletPoints, }); final OrderPollStatus status; final RechargeOrderInfo order; final int? walletPoints; OrderPollState copyWith({ OrderPollStatus? status, RechargeOrderInfo? order, int? walletPoints, }) => OrderPollState( status: status ?? this.status, order: order ?? this.order, walletPoints: walletPoints ?? this.walletPoints, ); } /// 订单轮询:每 2 秒查一次,直至 status != 0 或超过 30 次(《03-接口文档》8.3) class OrderPollNotifier extends StateNotifier { OrderPollNotifier(RechargeOrderInfo order) : super(OrderPollState(order: order)) { _start(); } Timer? _timer; int _count = 0; static const _maxCount = 30; void _start() { _timer = Timer.periodic(const Duration(seconds: 2), (_) => _poll()); } Future _poll() async { if (!mounted) return; _count++; try { final info = await PaymentService.fetchOrder(state.order.orderNo); if (!mounted) return; if (info.isPaid) { _finish(OrderPollStatus.paid, walletPoints: info.walletPoints); } else if (!info.isPending) { _finish(OrderPollStatus.closed); } else if (_count >= _maxCount) { _finish(OrderPollStatus.timeout); } } catch (_) { // 网络抖动不立即失败,继续下一轮;累计超限由 _count 兜底 if (_count >= _maxCount && mounted) _finish(OrderPollStatus.timeout); } } void _finish(OrderPollStatus status, {int? walletPoints}) { _timer?.cancel(); state = state.copyWith(status: status, walletPoints: walletPoints); } /// 取消订单(仅待支付):成功后结束轮询 Future cancel() async { _timer?.cancel(); try { await PaymentService.cancelOrder(state.order.orderNo); if (mounted) state = state.copyWith(status: OrderPollStatus.closed); } catch (_) { // 取消失败恢复轮询 if (mounted) _start(); rethrow; } } /// 主动停止轮询(离开页面时) void stop() => _timer?.cancel(); @override void dispose() { _timer?.cancel(); super.dispose(); } } final orderPollProvider = StateNotifierProvider.autoDispose .family( (ref, order) => OrderPollNotifier(order), );