From 301e13f4779b0003bdcdee0970422e752ead23b4 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Mon, 13 Jul 2026 15:53:30 +0800 Subject: [PATCH] =?UTF-8?q?fix(client/payment):=20switchMethod=20=E8=A1=A5?= =?UTF-8?q?=20catch-all=20=E5=85=9C=E7=BD=91=E7=BB=9C/=E8=B6=85=E6=97=B6?= =?UTF-8?q?=E9=94=99(=E4=B8=8E=20start/resume=20=E5=AF=B9=E9=BD=90)?= =?UTF-8?q?=E9=98=B2=E5=8D=A1=20awaitingPayment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit switchMethod 的首个 retry() 调用只 catch AuthApiException,网络/超时/未知异常会穿出到 无 try/catch 的 _pickAndSwitch,UI 卡在 awaitingPayment 无任何提示。补一个 catch-all, 与 start()/resume() 同一处理形状落 failed 相位(自动经 build() 切到 _failed() 呈现); payment_page.dart 的 _pickAndSwitch 再加一层防御性 try/catch + toast,防任何逃逸异常 变成无提示的 unhandled rejection。补单测覆盖非 Auth 异常路径。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01P9G7E3wmAYL9KeYCVZVsqu --- client/lib/screens/payment_page.dart | 9 ++++++++- client/lib/state/payment_provider.dart | 9 +++++++++ client/test/unit/payment_flow_test.dart | 14 ++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/client/lib/screens/payment_page.dart b/client/lib/screens/payment_page.dart index c8a339c..04637f9 100644 --- a/client/lib/screens/payment_page.dart +++ b/client/lib/screens/payment_page.dart @@ -245,9 +245,16 @@ class _PaymentScreenState extends ConsumerState { ]); } + // switchMethod 内部已把网络/超时/未知异常兜到 failed 相位(与 start/resume 对齐, + // 会经 build() 的 phase 分支自动切到 _failed() 呈现)——这里的 try/catch 是最后一道 + // 防线,防止任何逃逸异常变成无提示的 unhandled rejection(照抄 _openRedirectUrl 的写法)。 Future _pickAndSwitch(BuildContext context, WidgetRef ref, PaymentFlowState s) async { final other = s.method == 'crypto' ? 'nezha' : 'crypto'; - await ref.read(paymentFlowProvider.notifier).switchMethod(other); + try { + await ref.read(paymentFlowProvider.notifier).switchMethod(other); + } catch (_) { + if (context.mounted) showPangolinToast(context, t.orderCreateFailed); + } } Widget _succeeded(BuildContext context, PaymentFlowState s) { diff --git a/client/lib/state/payment_provider.dart b/client/lib/state/payment_provider.dart index b8ecfbf..394ab82 100644 --- a/client/lib/state/payment_provider.dart +++ b/client/lib/state/payment_provider.dart @@ -169,6 +169,15 @@ class PaymentFlowController extends StateNotifier { return; } state = state.copyWith(phase: PaymentPhase.failed, errorZh: e.messageZh, errorEn: e.messageEn); + } catch (_) { + // 网络/超时等非 Auth 异常也要落 failed —— 与 start()/resume() 同一形状,否则 + // 穿出到无 try/catch 的 _pickAndSwitch,UI 卡在 awaitingPayment 无提示。 + if (!mounted) return; + state = state.copyWith( + phase: PaymentPhase.failed, + errorZh: '下单失败,请检查网络后重试', + errorEn: 'Failed to create order, please check your network and retry', + ); } } diff --git a/client/test/unit/payment_flow_test.dart b/client/test/unit/payment_flow_test.dart index d9ff3de..91ac817 100644 --- a/client/test/unit/payment_flow_test.dart +++ b/client/test/unit/payment_flow_test.dart @@ -105,6 +105,20 @@ void main() { expect(c.read(paymentFlowProvider).order?.orderNo, 'pay2'); }); + test('switchMethod 遇非 Auth 异常(网络/超时)→ 落 failed 兜底,不卡 awaitingPayment', () async { + final api = _FakePaymentApi(); + final c = _container(api); + final ctl = c.read(paymentFlowProvider.notifier); + await ctl.start(item, 'crypto'); + api.retryError = Exception('network timeout'); + await ctl.switchMethod('alipay'); + expect(c.read(paymentFlowProvider).phase, PaymentPhase.failed); + expect(c.read(paymentFlowProvider).errorZh, isNotNull); + expect(c.read(paymentFlowProvider).errorZh, isNotEmpty); + expect(c.read(paymentFlowProvider).errorEn, isNotNull); + expect(c.read(paymentFlowProvider).errorEn, isNotEmpty); + }); + test('resume() 成功 → retry 恢复原单会话(不新下单),phase=awaitingPayment', () async { final api = _FakePaymentApi(); final c = _container(api);