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 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); } on DioException catch (e) { throw AppException( e.response?.data?['error'] as String? ?? '获取授权信息失败', statusCode: e.response?.statusCode, ); } } /// Activate a license key for this device. Future 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 收银台跳转信息。 /// [clientType] 终端类型("pc"/"mobile",可选):显式声明收银台形态—— /// mobile 走支付宝手机网站支付(H5 拉起 App),pc 走电脑扫码页; /// 不传时后端按请求 UA 判断(浏览器场景 UA 真实,App 场景须显式传)。 Future createPurchase(String bizCode, {String? clientType}) async { try { final resp = await _client.post('/license/purchase', data: { 'biz_code': bizCode, if (clientType != null) 'client_type': clientType, }); return PurchaseOrder.fromJson(resp.data['data'] as Map); } on DioException catch (e) { throw AppException( e.response?.data?['error'] as String? ?? '下单失败,请稍后重试', statusCode: e.response?.statusCode, ); } } /// 查询购买单状态(到账轮询)。到账(paid)后返回续期后的授权到期时间。 Future purchaseStatus(String outTradeNo) async { try { final resp = await _client.get('/license/purchase/$outTradeNo'); return PurchaseStatusInfo.fromJson( resp.data['data'] as Map); } on DioException catch (e) { throw AppException( e.response?.data?['error'] as String? ?? '查询订单状态失败', statusCode: e.response?.statusCode, ); } } /// 取消一笔未支付订单(防 pending 单堆积)。仅管理员可用。 /// 返回 true=已取消;订单不存在(404)或不可取消(409,如已支付/已取消) /// 视为业务性「取消不了」,返回 false 而非抛异常。 Future cancelPurchase(String outTradeNo) async { try { final resp = await _client.post('/license/purchase/$outTradeNo/cancel'); final data = resp.data['data'] as Map?; return data?['canceled'] as bool? ?? false; } on DioException catch (e) { final status = e.response?.statusCode; // 409 分支防御性保留:本后端实现里非 pending 单取消返回 200 + // canceled:false(业务性失败,非 HTTP 错误),故 409 实际永不触发; // 保留兜底以防后端未来改走标准 HTTP 语义时前端仍能优雅降级。 if (status == 404 || status == 409) return false; throw AppException( e.response?.data?['error'] as String? ?? '取消订单失败,请稍后重试', statusCode: status, ); } } /// 购买订单列表(订单管理 tab,分页 + 状态筛选)。仅管理员可用。 Future listPurchases({ int page = 1, int pageSize = 20, String? status, }) async { try { final resp = await _client.get('/license/purchases', params: { 'page': page, 'page_size': pageSize, if (status != null) 'status': status, }); return PurchaseListResult.fromJson( resp.data['data'] as Map); } on DioException catch (e) { throw AppException( e.response?.data?['error'] as String? ?? '获取订单列表失败', statusCode: e.response?.statusCode, ); } } /// 本店首月特惠是否已享用(购买弹窗据此置灰特惠档)。 Future promoUsed() async { try { final resp = await _client.get('/license/promo-status'); final data = resp.data['data'] as Map?; return data?['used'] as bool? ?? false; } on DioException { // 查询失败不阻塞弹窗:按未享用展示,下单时服务端仍会强校验 return false; } } /// Deactivate (unbind) this device from its license. Future 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, ); } } }