| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- /// 微信 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<WechatQrDialog> createState() => _WechatQrDialogState();
- }
- class _WechatQrDialogState extends State<WechatQrDialog> {
- 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<void> _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('关闭'),
- ),
- ],
- ),
- );
- }
- }
|