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);