feat(client): App 内在线购买/续费授权(管理员支付宝下单+到账轮询)

- 新增套餐目录 license_plans.dart(标准/高级 × 月付/年付,与官网 checkout 一致)
- license 模型/仓库新增 PurchaseOrder/PurchaseStatusInfo 与下单、查单接口
- 系统设置授权面板:管理员显示「在线购买 / 续费」按钮,弹窗选套餐→
  外部浏览器打开支付宝收银台→3s 轮询到账→自动刷新授权并展示新到期日
- 用户手册(docs.md + user-manual.html)同步补充在线购买入口说明

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
This commit is contained in:
wangjia
2026-07-03 21:57:03 +08:00
parent e0f721117f
commit 24123eb93c
7 changed files with 460 additions and 12 deletions
@@ -40,6 +40,34 @@ class LicenseRepository {
}
}
/// 在线购买/续费下单(仅管理员),返回 pay 收银台跳转信息。
Future<PurchaseOrder> createPurchase(String bizCode) async {
try {
final resp =
await _client.post('/license/purchase', data: {'biz_code': bizCode});
return PurchaseOrder.fromJson(resp.data['data'] as Map<String, dynamic>);
} on DioException catch (e) {
throw AppException(
e.response?.data?['error'] as String? ?? '下单失败,请稍后重试',
statusCode: e.response?.statusCode,
);
}
}
/// 查询购买单状态(到账轮询)。到账(paid)后返回续期后的授权到期时间。
Future<PurchaseStatusInfo> purchaseStatus(String outTradeNo) async {
try {
final resp = await _client.get('/license/purchase/$outTradeNo');
return PurchaseStatusInfo.fromJson(
resp.data['data'] as Map<String, dynamic>);
} on DioException catch (e) {
throw AppException(
e.response?.data?['error'] as String? ?? '查询订单状态失败',
statusCode: e.response?.statusCode,
);
}
}
/// Deactivate (unbind) this device from its license.
Future<void> deactivate() async {
final deviceId = await DeviceId.get();