/// 微信 Native 扫码支付弹窗(Web / 桌面端) /// /// 展示后端统一下单返回的 code_url 二维码,并轮询支付状态: /// - 未支付:持续轮询(每 3s) /// - 支付成功:显示对勾并自动关闭 /// - 已关闭/失败:显示结果并关闭 /// /// Mock 模式下 code_url 为占位串(不可真实扫码),提供「模拟支付成功」按钮 /// 触发后端 simulate 接口,使演示可在无真实商户密钥时端到端跑通。 library; import 'dart:async'; import 'package:flutter/material.dart'; import 'package:qr_flutter/qr_flutter.dart'; import '../service/api_service.dart'; import '../utils/toast.dart'; /// 微信扫码支付弹窗 class WechatQrDialog extends StatefulWidget { const WechatQrDialog({ super.key, required this.codeUrl, required this.paymentId, this.onClose, }); /// 后端统一下单返回的二维码内容(code_url) final String codeUrl; /// 支付单号(用于轮询状态) final String paymentId; /// 关闭回调(可选,支付结果确认时触发) final void Function(bool success)? onClose; @override State createState() => _WechatQrDialogState(); } class _WechatQrDialogState extends State { Timer? _timer; bool _paid = false; bool _simulating = false; bool _closed = false; @override void initState() { super.initState(); _startPolling(); } @override void dispose() { _timer?.cancel(); super.dispose(); } void _startPolling() { _timer = Timer.periodic(const Duration(seconds: 3), (_) async { try { final status = await APIServer().queryPaymentStatus(widget.paymentId); if (!mounted) return; if (status.success) { _finish(true); } else if ((status.note ?? '').contains('关闭')) { _finish(false); } } catch (e) { // 轮询失败静默重试(后端未就绪等情况) } }); } /// 支付结果确认:停轮询、更新 UI、通知外部并自动关闭 void _finish(bool success) { _timer?.cancel(); if (_closed) return; _closed = true; if (!mounted) return; setState(() => _paid = success); if (success) { showSuccessMessage('支付成功'); widget.onClose?.call(true); Future.delayed(const Duration(milliseconds: 600), () { if (mounted) Navigator.of(context).pop(); }); } else { showErrorMessage('订单已关闭,请重新下单'); widget.onClose?.call(false); Future.delayed(const Duration(milliseconds: 600), () { if (mounted) Navigator.of(context).pop(); }); } } /// Mock 模式:模拟支付成功(后端会幂等标记 PAID,轮询自动收尾) Future _simulate() async { if (_simulating) return; setState(() => _simulating = true); try { await APIServer().simulatePaymentSuccess(widget.paymentId); // 轮询会在下一次 tick 感知到 PAID;这里主动查一次加速 await Future.delayed(const Duration(milliseconds: 500)); final status = await APIServer().queryPaymentStatus(widget.paymentId); if (mounted && status.success) _finish(true); } on Exception catch (e) { if (mounted) { setState(() => _simulating = false); showErrorMessage(APIServer().resolveError(e)); } } } @override Widget build(BuildContext context) { return SizedBox( width: 420, child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text('请使用微信「扫一扫」完成支付', style: TextStyle(fontSize: 16)), const SizedBox(height: 20), if (_paid) const Icon(Icons.check_circle, color: Colors.green, size: 80) else Container( padding: const EdgeInsets.all(16), color: Colors.white, child: QrImageView( data: widget.codeUrl, version: QrVersions.auto, size: 220, backgroundColor: Colors.white, ), ), const SizedBox(height: 12), Text( _paid ? '支付成功' : '支付单号:${widget.paymentId}', style: const TextStyle(color: Colors.grey, fontSize: 12), ), if (!_paid) ...[ const SizedBox(height: 12), // Mock 演示:模拟支付成功 OutlinedButton.icon( onPressed: _simulating ? null : _simulate, icon: const Icon(Icons.flash_on, size: 18), label: Text(_simulating ? '处理中...' : '模拟支付成功(Mock)'), ), ], const SizedBox(height: 8), TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('关闭'), ), ], ), ); } }