feat(client): pay v2 支付渠道 switch + 订单列表/详情端到端

购买/支付(Phase B):
- 新 SegSwitch 段控 widget(镜像 .segswitch 原子)
- 购买页重构:顶部支付渠道 switch(默认 USDT→加密货币 / 人民币→支付宝),套餐价随渠道分币种显示,删底部弹窗选方式;点购买直达支付页
- 支付页加订单信息卡(套餐/渠道/金额按币种)
- models:PayChannel + PayCatalogItem.priceUsdtMicro/priceLabel(ch)/perMonthLabel(ch)

订单(P0,全新):
- payment_api.listOrders() + orders_provider(列表 + 详情 family)
- OrdersScreen 列表→详情(状态 pill / 可复制订单号 / 继续支付·重新购买)
- 导航:NavView.orders + desktop_shell 分支 + account 入口

配套:
- l10n +19 getter(支付渠道 4 + 订单 15)×6 语言,l10n 闸绿
- 图标单源迁移 lucide_icons_flutter + pangolin_icons.dart(codegen)
- 联系渠道订正(Telegram/邮件 support@yanmeiai.com/官网) + 前端去广告&时长
- 测试:购买 switch 分币种 + 订单列表/空态/详情 + 假 API listOrders;golden 重录
- flutter analyze 0 error,flutter test 226 全绿

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
This commit is contained in:
wangjia
2026-07-12 00:07:15 +08:00
parent f6ce5267d5
commit 272eca04ce
66 changed files with 1398 additions and 262 deletions
@@ -330,8 +330,9 @@ void main() {
await tester.pump();
});
// 免费版 10 分钟卡控:连接期倒计时,墙上时钟越过额度即自动切断 + 本地置耗尽(#21)。
testWidgets('免费额度倒计时归零 → 自动切断 + 置耗尽', (tester) async {
// 免费版 10 分钟卡控(enforcement):连接期锁定额度,墙上时钟越过额度即自动切断 +
// 本地置耗尽(#21;额度倒计时 UI 已移除,只保留到点即切的强制行为)。
testWidgets('免费额度到点 → 自动切断 + 置耗尽', (tester) async {
final bridge = _FakeBridge();
var fake = DateTime(2026, 7, 1, 12);
final c = makeContainer(bridge, now: () => fake); // 未登录默认免费,剩余 10 分钟
@@ -341,7 +342,7 @@ void main() {
c.read(nodesProvider);
await tester.pump();
// 免费默认剩余 10 分钟 → toggle 触发 _connect 锁定 600s 倒计时(fake api 抛错不影响锁定)。
// 免费默认剩余 10 分钟 → toggle 触发 _connect 锁定 600s 额度(fake api 抛错不影响锁定)。
expect(c.read(quotaProvider).remainingMinutes, 10);
c.read(connectionProvider.notifier).toggle();
await tester.pump();
@@ -349,7 +350,6 @@ void main() {
await tester.pump();
await tester.pump();
expect(c.read(connectionProvider).phase, VpnPhase.on);
expect(c.read(connectionProvider).freeCountdown, isNotNull, reason: '连接期应有倒计时');
// 快进墙上时钟越过 10 分钟 → 下一个 1s tick 触发 _refreshElapsed → 切断。
fake = fake.add(const Duration(seconds: 601));
+2 -1
View File
@@ -3,6 +3,7 @@ import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:pangolin_vpn/services/api_client.dart';
import 'package:pangolin_vpn/services/auth_api.dart';
import 'package:pangolin_vpn/models/payment.dart';
import 'package:pangolin_vpn/services/payment_api.dart';
ApiClient _client(MockClient mock) => ApiClient(
@@ -25,7 +26,7 @@ void main() {
final items = await api.catalog();
expect(items.length, 3);
expect(items.first.sku, 'pro_month');
expect(items.first.priceLabel(), '¥29.99');
expect(items.first.priceLabel(PayChannel.cny), '¥29.99');
});
test('createOrder 只传 sku+method+metadata,解析 session', () async {
+3
View File
@@ -15,6 +15,9 @@ class _FakePaymentApi implements PaymentApi {
@override
Future<List<PayCatalogItem>> catalog() async => const [];
@override
Future<List<PayOrderSummary>> listOrders() async => const [];
@override
Future<PayOrder> createOrder({required String sku, required String method, Map<String, String>? metadata}) async {
createCalls++;