Files
jiu/client/lib/repositories/license_repository.dart
T
wangjia b3c9d98eae feat: ¥1 首月特惠套餐(新店专享,每店限购一次,官网+App 同步上线)
- 后端:payPlans 新增 promo_first_month(¥1/30 天/标准版权益);下单强校验每店限购一次(仅 paid 计数,弃单可重下),已享用返回 409;新增 GET /license/promo-status 供前端置灰
- 官网:checkout 套餐段第二位插「🔥 首月特惠 ¥1」+ 推广横幅 + 划线原价 ¥299;首页价格区第二位插特惠卡(5 列);已享用自动置灰
- App:购买弹窗套餐段改三档(标准/特惠/高级),特惠选中显示推广横幅与划线价,已享用置灰并禁用提交
- 实付金额以 pay 侧为准,pay 需 seed promo_first_month 产品后生效

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
2026-07-03 22:25:07 +08:00

96 lines
3.2 KiB
Dart

import 'package:dio/dio.dart';
import '../core/api/api_client.dart';
import '../core/device/device_id.dart';
import '../core/exceptions.dart';
import '../models/license.dart';
class LicenseRepository {
final ApiClient _client;
const LicenseRepository(this._client);
Future<LicenseInfo?> getInfo() async {
try {
final resp = await _client.get('/license/info');
final data = resp.data['data'];
if (data == null) return null;
return LicenseInfo.fromJson(data as Map<String, dynamic>);
} on DioException catch (e) {
throw AppException(
e.response?.data?['error'] as String? ?? '获取授权信息失败',
statusCode: e.response?.statusCode,
);
}
}
/// Activate a license key for this device.
Future<void> activate(String licenseKey, {String? deviceName}) async {
final deviceId = await DeviceId.get();
try {
await _client.post('/license/activate', data: {
'license_key': licenseKey,
'device_id': deviceId,
'device_name': deviceName ?? DeviceId.platformName,
'platform': DeviceId.platformName,
});
} on DioException catch (e) {
throw AppException(
e.response?.data?['error'] as String? ?? '激活失败',
statusCode: e.response?.statusCode,
);
}
}
/// 在线购买/续费下单(仅管理员),返回 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,
);
}
}
/// 本店首月特惠是否已享用(购买弹窗据此置灰特惠档)。
Future<bool> promoUsed() async {
try {
final resp = await _client.get('/license/promo-status');
final data = resp.data['data'] as Map<String, dynamic>?;
return data?['used'] as bool? ?? false;
} on DioException {
// 查询失败不阻塞弹窗:按未享用展示,下单时服务端仍会强校验
return false;
}
}
/// Deactivate (unbind) this device from its license.
Future<void> deactivate() async {
final deviceId = await DeviceId.get();
try {
await _client.post('/license/deactivate', data: {'device_id': deviceId});
} on DioException catch (e) {
throw AppException(
e.response?.data?['error'] as String? ?? '解绑失败',
statusCode: e.response?.statusCode,
);
}
}
}