Files
slogan/app/lib/features/member/pay_page.dart
T
2026-08-04 17:04:18 +08:00

153 lines
4.9 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../shared/app_toast.dart';
import 'member_provider.dart';
class PayArgs {
final String orderNo;
final String payUrl;
const PayArgs({required this.orderNo, required this.payUrl});
}
enum PayPhase { launching, paying, paid, timeout, failed }
/// 支付页:打开系统浏览器收银台,2s 轮询订单状态(上限 60s)
class PayPage extends ConsumerStatefulWidget {
final PayArgs args;
const PayPage({super.key, required this.args});
@override
ConsumerState<PayPage> createState() => _PayPageState();
}
class _PayPageState extends ConsumerState<PayPage> {
PayPhase _phase = PayPhase.launching;
Timer? _timer;
int _elapsed = 0;
@override
void initState() {
super.initState();
_start();
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
Future<void> _start() async {
try {
final ok = await launchUrl(Uri.parse(widget.args.payUrl),
mode: LaunchMode.externalApplication);
if (!ok) {
setState(() => _phase = PayPhase.failed);
return;
}
setState(() => _phase = PayPhase.paying);
} catch (e) {
setState(() => _phase = PayPhase.failed);
return;
}
_timer = Timer.periodic(const Duration(seconds: 2), (_) => _check());
}
Future<void> _check() async {
_elapsed += 2;
try {
final status = await fetchOrderStatus(ref, widget.args.orderNo);
if (status == 'paid') {
_timer?.cancel();
if (!mounted) return;
setState(() => _phase = PayPhase.paid);
showToast('会员开通成功');
return;
}
if (_elapsed >= 60) {
_timer?.cancel();
if (!mounted) return;
setState(() => _phase = PayPhase.timeout);
}
} catch (_) {
// 轮询失败不中断,下次再试
}
}
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(title: const Text('会员支付')),
body: Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
switch (_phase) {
PayPhase.launching ||
PayPhase.paying => Column(children: [
const CircularProgressIndicator(),
const SizedBox(height: 16),
const Text('请在浏览器中完成支付,正在确认结果…'),
const SizedBox(height: 8),
Text('订单号 ${widget.args.orderNo}',
style: const TextStyle(color: Colors.grey, fontSize: 12)),
const SizedBox(height: 16),
OutlinedButton(
onPressed: () => _check(),
child: const Text('我已完成支付'),
),
]),
PayPhase.paid => Column(children: [
Icon(Icons.check_circle, size: 64, color: scheme.primary),
const SizedBox(height: 12),
const Text('支付成功,会员已开通!',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
FilledButton(
onPressed: () => context.pop(),
child: const Text('返回会员中心'),
),
]),
PayPhase.timeout => Column(children: [
Icon(Icons.hourglass_empty, size: 64, color: Colors.orange),
const SizedBox(height: 12),
const Text('支付结果确认中'),
const SizedBox(height: 8),
const Text('可稍后到会员中心查看开通状态,以支付结果为准',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey, fontSize: 12)),
const SizedBox(height: 16),
OutlinedButton(
onPressed: () => context.pop(),
child: const Text('返回'),
),
]),
PayPhase.failed => Column(children: [
Icon(Icons.error_outline, size: 64, color: scheme.error),
const SizedBox(height: 12),
const Text('无法打开支付页面'),
const SizedBox(height: 16),
OutlinedButton(
onPressed: () => context.pop(),
child: const Text('返回'),
),
]),
},
],
),
),
),
);
}
}