Files
jiu/client/lib/models/license.dart
T

251 lines
8.0 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import '../core/config/app_constants.dart';
import '../core/utils/money.dart';
class LicenseInfo {
final int id;
final String type; // trial | monthly | annual | lifetime
final bool isActive;
final int maxDevices;
final DateTime? expiresAt;
final String phase; // normal | grace | readonly | locked
const LicenseInfo({
required this.id,
required this.type,
required this.isActive,
required this.maxDevices,
required this.phase,
this.expiresAt,
});
factory LicenseInfo.fromJson(Map<String, dynamic> json) {
return LicenseInfo(
id: (json['id'] as num?)?.toInt() ?? 0,
type: json['type'] as String? ?? 'trial',
isActive: json['is_active'] as bool? ?? false,
maxDevices: (json['max_devices'] as num?)?.toInt() ??
AppConstants.defaultMaxDevices,
expiresAt: json['expires_at'] != null
? DateTime.tryParse(json['expires_at'] as String)
: null,
phase: json['phase'] as String? ?? 'normal',
);
}
String get typeLabel {
switch (type) {
case 'monthly':
return '月度授权';
case 'annual':
return '年度授权';
case 'lifetime':
return '永久授权';
default:
return '试用版';
}
}
bool get isExpired => expiresAt != null && DateTime.now().isAfter(expiresAt!);
int? get daysRemaining {
if (expiresAt == null) return null;
final diff = expiresAt!.difference(DateTime.now()).inDays;
return diff < 0 ? 0 : diff;
}
/// 已过期天数(未过期或永久授权返回 0)。
int get daysExpired {
if (expiresAt == null) return 0;
final diff = DateTime.now().difference(expiresAt!).inDays;
return diff < 0 ? 0 : diff;
}
bool get isReadOnlyPhase => phase == 'readonly' || phase == 'locked';
bool get isLockedPhase => phase == 'locked';
bool get needsAttention =>
phase == 'grace' || phase == 'readonly' || phase == 'locked';
}
/// POST /license/purchase 响应:pay v2 收银台会话信息(render_type 多态)。
class PurchaseOrder {
final String outTradeNo;
final String renderType; // redirect | qr | ...
final Map<String, dynamic> payload;
final int amountMinor;
final String currency;
final String subject;
const PurchaseOrder({
required this.outTradeNo,
required this.renderType,
required this.payload,
required this.amountMinor,
required this.currency,
required this.subject,
});
/// render_type == redirect 时的跳转链接(payload['url'])。
String get redirectUrl => payload['url'] as String? ?? '';
/// render_type == qr 时的二维码内容(payload['qr_content'])。
String get qrContent => payload['qr_content'] as String? ?? '';
/// 兼容旧字段:purchase_card.dartTask 7 改造前)仍读 payUrl/amount。
String get payUrl => redirectUrl;
String get amount => yuanFromMinor(amountMinor);
factory PurchaseOrder.fromJson(Map<String, dynamic> json) => PurchaseOrder(
outTradeNo: json['out_trade_no'] as String? ?? '',
renderType: json['render_type'] as String? ?? 'redirect',
payload:
(json['payload'] as Map?)?.cast<String, dynamic>() ?? const {},
amountMinor: (json['amount_minor'] as num?)?.toInt() ?? 0,
currency: json['currency'] as String? ?? 'CNY',
subject: json['subject'] as String? ?? '',
);
}
/// GET /license/purchase/:otn 响应:购买单状态(到账轮询用)。
class PurchaseStatusInfo {
final String outTradeNo;
final String status; // pending | paid | failed
final int amountMinor;
final String currency;
final String amount; // Deprecated:后端 formatMinor 串回退,串失败时用 amountMinor
final DateTime? paidAt;
final DateTime? expiresAt; // 续期后的门店授权到期时间(仅 paid 时有值)
const PurchaseStatusInfo({
required this.outTradeNo,
required this.status,
required this.amountMinor,
required this.currency,
required this.amount,
this.paidAt,
this.expiresAt,
});
bool get isPaid => status == 'paid';
/// 展示用金额:优先按 amount_minor 换算,退回旧 amount 串兜底。
String get displayAmount =>
amountMinor > 0 ? yuanFromMinor(amountMinor) : amount;
factory PurchaseStatusInfo.fromJson(Map<String, dynamic> json) =>
PurchaseStatusInfo(
outTradeNo: json['out_trade_no'] as String? ?? '',
status: json['status'] as String? ?? 'pending',
amountMinor: (json['amount_minor'] as num?)?.toInt() ?? 0,
currency: json['currency'] as String? ?? 'CNY',
amount: json['amount'] as String? ?? '',
paidAt: json['paid_at'] != null
? DateTime.tryParse(json['paid_at'] as String)
: null,
expiresAt: json['expires_at'] != null
? DateTime.tryParse(json['expires_at'] as String)
: null,
);
}
/// GET /license/purchases 列表项。
class PurchaseRecord {
final String outTradeNo;
final String productBizCode;
final int amountMinor;
final String currency;
final String amount; // Deprecated:后端 formatMinor 串回退
final String status; // pending | paid | failed
final String payUrl;
final String userName;
final DateTime? createdAt;
final DateTime? paidAt;
final DateTime? renewedTo;
const PurchaseRecord({
required this.outTradeNo,
required this.productBizCode,
required this.amountMinor,
required this.currency,
required this.amount,
required this.status,
required this.payUrl,
required this.userName,
this.createdAt,
this.paidAt,
this.renewedTo,
});
bool get isPaid => status == 'paid';
/// 展示用金额:优先按 amount_minor 换算,退回旧 amount 串兜底。
String get displayAmount =>
amountMinor > 0 ? yuanFromMinor(amountMinor) : amount;
factory PurchaseRecord.fromJson(Map<String, dynamic> json) =>
PurchaseRecord(
outTradeNo: json['out_trade_no'] as String? ?? '',
productBizCode: json['product_biz_code'] as String? ?? '',
amountMinor: (json['amount_minor'] as num?)?.toInt() ?? 0,
currency: json['currency'] as String? ?? 'CNY',
amount: json['amount'] as String? ?? '',
status: json['status'] as String? ?? 'pending',
payUrl: json['pay_url'] as String? ?? '',
userName: json['user_name'] as String? ?? '',
createdAt: json['created_at'] != null
? DateTime.tryParse(json['created_at'] as String)
: null,
paidAt: json['paid_at'] != null
? DateTime.tryParse(json['paid_at'] as String)
: null,
renewedTo: json['renewed_to'] != null
? DateTime.tryParse(json['renewed_to'] as String)
: null,
);
}
/// GET /license/purchases 汇总(订单管理 tab KPI)。
class PurchaseSummary {
final int paidTotalMinor;
final int paidCount;
final int pendingCount;
final int totalCount;
const PurchaseSummary({
required this.paidTotalMinor,
required this.paidCount,
required this.pendingCount,
required this.totalCount,
});
factory PurchaseSummary.fromJson(Map<String, dynamic> json) =>
PurchaseSummary(
paidTotalMinor: (json['paid_total_minor'] as num?)?.toInt() ?? 0,
paidCount: (json['paid_count'] as num?)?.toInt() ?? 0,
pendingCount: (json['pending_count'] as num?)?.toInt() ?? 0,
totalCount: (json['total_count'] as num?)?.toInt() ?? 0,
);
}
/// GET /license/purchases 响应整体。
class PurchaseListResult {
final List<PurchaseRecord> items;
final int total;
final PurchaseSummary summary;
const PurchaseListResult({
required this.items,
required this.total,
required this.summary,
});
factory PurchaseListResult.fromJson(Map<String, dynamic> json) =>
PurchaseListResult(
items: (json['items'] as List<dynamic>? ?? const [])
.map((e) => PurchaseRecord.fromJson(e as Map<String, dynamic>))
.toList(),
total: (json['total'] as num?)?.toInt() ?? 0,
summary: PurchaseSummary.fromJson(
(json['summary'] as Map<String, dynamic>?) ?? const {}),
);
}